59 lines
1.9 KiB
JavaScript
59 lines
1.9 KiB
JavaScript
export function initReveal() {
|
|
const items = document.querySelectorAll('[data-reveal]');
|
|
if (!('IntersectionObserver' in window)) {
|
|
for (const item of items) item.classList.add('is-visible');
|
|
return;
|
|
}
|
|
|
|
const groups = new Map();
|
|
const observer = new IntersectionObserver(
|
|
(entries) => {
|
|
// things that come into view together get a small stagger
|
|
let index = 0;
|
|
for (const entry of entries) {
|
|
if (!entry.isIntersecting) continue;
|
|
for (const item of groups.get(entry.target)) {
|
|
item.style.setProperty('--delay', `${Math.min(index++ * 90, 450)}ms`);
|
|
item.classList.add('is-visible');
|
|
}
|
|
observer.unobserve(entry.target);
|
|
}
|
|
},
|
|
{ rootMargin: '0px 0px -6% 0px' },
|
|
);
|
|
|
|
for (const item of items) {
|
|
// Chrome applies the element's own clip-path, so a fully clipped heading never
|
|
// counts as intersecting. Watch the parent for those instead.
|
|
const target = item.dataset.reveal === 'mask' ? item.parentElement : item;
|
|
if (!groups.has(target)) groups.set(target, []);
|
|
groups.get(target).push(item);
|
|
observer.observe(target);
|
|
}
|
|
}
|
|
|
|
// Fallback for browsers without scroll-driven animations
|
|
export function initTimeline() {
|
|
const timeline = document.querySelector('[data-timeline]');
|
|
if (!timeline || CSS.supports('animation-timeline: view()')) return;
|
|
if (matchMedia('(prefers-reduced-motion: reduce)').matches) return;
|
|
|
|
let ticking = false;
|
|
const update = () => {
|
|
const rect = timeline.getBoundingClientRect();
|
|
const progress = (innerHeight * 0.75 - rect.top) / rect.height;
|
|
timeline.style.setProperty('--progress', Math.min(1, Math.max(0, progress)).toFixed(3));
|
|
ticking = false;
|
|
};
|
|
|
|
addEventListener(
|
|
'scroll',
|
|
() => {
|
|
if (!ticking) requestAnimationFrame(update);
|
|
ticking = true;
|
|
},
|
|
{ passive: true },
|
|
);
|
|
update();
|
|
}
|