123 lines
4.2 KiB
JavaScript
123 lines
4.2 KiB
JavaScript
import { prefersReducedMotion } from "../shared/media.js";
|
|
|
|
export function initHeaderScroll() {
|
|
const header = document.querySelector(".header");
|
|
if (!header) return;
|
|
|
|
function updateHeaderScrollPadding() {
|
|
const inner = header.querySelector(".header__inner");
|
|
let h;
|
|
if (header.classList.contains("header--scrolled") && inner) {
|
|
const r = inner.getBoundingClientRect();
|
|
h = Math.ceil(r.bottom + 20);
|
|
} else {
|
|
h = Math.ceil(header.getBoundingClientRect().height);
|
|
}
|
|
document.documentElement.style.setProperty("--header-offset", `${Math.max(h, 52)}px`);
|
|
}
|
|
|
|
function getScrollPaddingTopPx() {
|
|
const raw = getComputedStyle(document.documentElement).scrollPaddingTop;
|
|
const px = Number.parseFloat(raw);
|
|
return Number.isFinite(px) ? px : 0;
|
|
}
|
|
|
|
/** Вертикальный скролл к якорю с учётом `scroll-padding-top` на `html` (запас под шапку). */
|
|
function scrollWindowToElementTop(el, behavior) {
|
|
updateHeaderScrollPadding();
|
|
const pad = getScrollPaddingTopPx();
|
|
const top = el.getBoundingClientRect().top + window.scrollY - pad;
|
|
window.scrollTo({ top: Math.max(0, top), behavior });
|
|
}
|
|
|
|
/** Smooth-скролл к якорю + одна подстройка после окончания (layout / смена режима шапки). */
|
|
function scrollToInPageAnchor(el) {
|
|
const reduced = prefersReducedMotion();
|
|
if (reduced) {
|
|
scrollWindowToElementTop(el, "auto");
|
|
return;
|
|
}
|
|
|
|
let settled = false;
|
|
const settle = () => {
|
|
if (settled) return;
|
|
updateHeaderScrollPadding();
|
|
const pad = getScrollPaddingTopPx();
|
|
const y = Math.max(0, el.getBoundingClientRect().top + window.scrollY - pad);
|
|
if (Math.abs(window.scrollY - y) <= 2) {
|
|
settled = true;
|
|
return;
|
|
}
|
|
settled = true;
|
|
window.scrollTo({ top: y, behavior: "auto" });
|
|
};
|
|
|
|
scrollWindowToElementTop(el, "smooth");
|
|
window.addEventListener("scrollend", settle, { passive: true, once: true });
|
|
window.setTimeout(settle, 550);
|
|
}
|
|
|
|
function onScroll() {
|
|
const y = window.scrollY;
|
|
const scrollDown = y > onScroll.lastY + 1;
|
|
const headerHeight = Math.max(52, Math.ceil(header.getBoundingClientRect().height || 0));
|
|
const floatStartY = Math.ceil(headerHeight * 1.35);
|
|
|
|
if (onScroll.isFloating) {
|
|
if (y <= Math.floor(headerHeight * 0.45)) onScroll.isFloating = false;
|
|
} else if (scrollDown && y > floatStartY) {
|
|
onScroll.isFloating = true;
|
|
}
|
|
|
|
const hadScrolled = header.classList.contains("header--scrolled");
|
|
header.classList.toggle("header--scrolled", onScroll.isFloating);
|
|
if (hadScrolled !== header.classList.contains("header--scrolled")) updateHeaderScrollPadding();
|
|
onScroll.lastY = y;
|
|
}
|
|
onScroll.lastY = window.scrollY;
|
|
onScroll.isFloating = false;
|
|
|
|
document.querySelectorAll('a[href^="#"]').forEach((link) => {
|
|
link.addEventListener("click", (e) => {
|
|
const id = link.getAttribute("href");
|
|
if (!id || id === "#") return;
|
|
const el = document.querySelector(id);
|
|
if (!el) return;
|
|
e.preventDefault();
|
|
scrollToInPageAnchor(el);
|
|
history.pushState(null, "", id);
|
|
});
|
|
});
|
|
|
|
window.addEventListener("scroll", onScroll, { passive: true });
|
|
onScroll();
|
|
|
|
if (typeof ResizeObserver !== "undefined") {
|
|
const roHeader = new ResizeObserver(() => updateHeaderScrollPadding());
|
|
roHeader.observe(header);
|
|
const headerInner = header.querySelector(".header__inner");
|
|
if (headerInner) roHeader.observe(headerInner);
|
|
}
|
|
window.addEventListener("resize", updateHeaderScrollPadding, { passive: true });
|
|
window.addEventListener(
|
|
"load",
|
|
() => {
|
|
updateHeaderScrollPadding();
|
|
const raw = location.hash;
|
|
if (!raw || raw.length < 2) return;
|
|
let id;
|
|
try {
|
|
id = decodeURIComponent(raw.slice(1));
|
|
} catch {
|
|
return;
|
|
}
|
|
if (!id) return;
|
|
const el = document.getElementById(id);
|
|
if (!el) return;
|
|
requestAnimationFrame(() => scrollWindowToElementTop(el, "auto"));
|
|
},
|
|
{ once: true },
|
|
);
|
|
updateHeaderScrollPadding();
|
|
}
|