This commit is contained in:
谢宇宁
2026-10-22 15:30:49 +07:00
parent 3639d2184a
commit 6dd567a07e
25 changed files with 16 additions and 1100 deletions
-160
View File
@@ -1,160 +0,0 @@
(function () {
const DESKTOP_COLLAPSE_LIMIT = 10;
const MOBILE_COLLAPSE_LIMIT = 8;
function initApplicationPage() {
const page = document.querySelector(".application-page");
if (!page) return;
const tabsNav = page.querySelector(".app-category-tabs");
const tabs = page.querySelectorAll(".app-category-tab");
const sections = page.querySelectorAll(".app-list-section[data-total]");
if (!tabs.length || !sections.length) return;
let isProgrammaticScroll = false;
let scrollTimer = null;
function isDesktop() {
return window.matchMedia("(min-width: 768px)").matches;
}
function getCollapseLimit() {
return isDesktop() ? DESKTOP_COLLAPSE_LIMIT : MOBILE_COLLAPSE_LIMIT;
}
function getScrollOffset() {
const header = document.querySelector(".site-header");
const barHeight = (header && header.getBoundingClientRect().height) || (isDesktop() ? 72 : 56);
return barHeight + (isDesktop() ? 24 : 12);
}
function getSectionAnchor(section) {
return section.querySelector(".app-list-section__head") || section;
}
function setActiveTab(activeTab) {
tabs.forEach((tab) => {
const isActive = tab === activeTab;
tab.classList.toggle("app-category-tab--active", isActive);
tab.setAttribute("aria-selected", isActive ? "true" : "false");
});
}
function updateActionButton(button, expanded, limit) {
const label = button.querySelector(".app-list-section__more-label");
const icon = button.querySelector(".app-list-section__more-icon");
const total = Number(button.dataset.total) || 0;
const hiddenCount = Math.max(total - limit, 0);
if (label) {
label.textContent = expanded ? "收起" : `显示全部${hiddenCount}个站点`;
}
if (icon) {
icon.classList.toggle("app-list-section__more-icon--up", expanded);
}
button.setAttribute("aria-expanded", expanded ? "true" : "false");
}
function updateCollapseSections() {
const limit = getCollapseLimit();
page.querySelectorAll(".app-list-section[data-total]").forEach((section) => {
const total = Number(section.dataset.total) || 0;
const button = section.querySelector(".app-list-section__more");
const needsCollapse = total > limit;
section.classList.toggle("app-list-section--collapsible", needsCollapse);
if (!needsCollapse) {
section.classList.remove("app-list-section--expanded");
}
if (button) {
button.hidden = !needsCollapse;
if (needsCollapse) {
updateActionButton(button, section.classList.contains("app-list-section--expanded"), limit);
}
}
});
}
function scrollTabIntoView(tab) {
if (!tabsNav || !tab) return;
const navRect = tabsNav.getBoundingClientRect();
const tabRect = tab.getBoundingClientRect();
const nextLeft = tabsNav.scrollLeft + (tabRect.left - navRect.left) - (navRect.width - tabRect.width) / 2;
tabsNav.scrollTo({ left: Math.max(0, nextLeft), behavior: "smooth" });
}
function scrollToSection(section) {
if (!section) return;
const anchor = getSectionAnchor(section);
const top = anchor.getBoundingClientRect().top + window.scrollY - getScrollOffset();
isProgrammaticScroll = true;
clearTimeout(scrollTimer);
window.scrollTo({ top: Math.max(0, top), behavior: "smooth" });
scrollTimer = window.setTimeout(() => {
isProgrammaticScroll = false;
}, 800);
}
tabs.forEach((tab, index) => {
if (!tab.hasAttribute("aria-selected")) {
tab.setAttribute("aria-selected", index === 0 ? "true" : "false");
}
tab.addEventListener("click", () => {
const targetId = tab.dataset.target;
if (!targetId) return;
setActiveTab(tab);
scrollTabIntoView(tab);
scrollToSection(document.getElementById(targetId));
});
});
page.querySelectorAll(".app-list-section__more").forEach((button) => {
button.addEventListener("click", () => {
const section = button.closest(".app-list-section");
if (!section || button.hidden) return;
const expanded = section.classList.toggle("app-list-section--expanded");
updateActionButton(button, expanded, getCollapseLimit());
});
});
updateCollapseSections();
window.addEventListener("resize", updateCollapseSections);
if (!("IntersectionObserver" in window)) return;
const observer = new IntersectionObserver(
(entries) => {
if (isProgrammaticScroll) return;
const visible = entries
.filter((entry) => entry.isIntersecting)
.sort((a, b) => b.intersectionRatio - a.intersectionRatio);
if (!visible.length) return;
const activeTab = page.querySelector(`.app-category-tab[data-target="${visible[0].target.id}"]`);
if (activeTab) {
setActiveTab(activeTab);
}
},
{
rootMargin: `-${getScrollOffset()}px 0px -55% 0px`,
threshold: [0, 0.25, 0.5, 0.75, 1],
}
);
sections.forEach((section) => observer.observe(section));
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", initApplicationPage);
} else {
initApplicationPage();
}
})();