161 lines
5.3 KiB
JavaScript
161 lines
5.3 KiB
JavaScript
(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();
|
|
}
|
|
})();
|