This commit is contained in:
2026-05-18 09:43:29 +12:00
parent b950229003
commit 6ff970015f
189 changed files with 18603 additions and 2727 deletions
+40 -39
View File
@@ -6,13 +6,19 @@ type RevealOptions = {
const defaultOptions: Required<RevealOptions> = {
delay: 0,
distance: 24,
distance: 16,
threshold: 0.18
};
export function reveal(node: HTMLElement, options: RevealOptions = {}) {
const settings = { ...defaultOptions, ...options };
const media = window.matchMedia('(prefers-reduced-motion: reduce)');
const mobileMedia = window.matchMedia('(max-width: 768px)');
const isMobile = mobileMedia.matches;
const effectiveDelay = isMobile ? Math.min(settings.delay, 16) : settings.delay;
const effectiveDistance = isMobile ? Math.min(settings.distance, 14) : settings.distance;
const effectiveThreshold = isMobile ? Math.min(settings.threshold, 0.08) : settings.threshold;
const effectiveRootMargin = isMobile ? '0px 0px 18% 0px' : '0px 0px -8% 0px';
if (media.matches) {
node.classList.add('reveal-visible');
@@ -21,57 +27,52 @@ export function reveal(node: HTMLElement, options: RevealOptions = {}) {
};
}
node.style.setProperty('--reveal-delay', `${settings.delay}ms`);
node.style.setProperty('--reveal-distance', `${settings.distance}px`);
node.style.setProperty('--reveal-delay', `${effectiveDelay}ms`);
node.style.setProperty('--reveal-distance', `${effectiveDistance}px`);
node.classList.add('reveal-ready');
// If the element is already visible at all in the initial viewport,
// reveal it immediately so the first section below the hero doesn't
// appear blank on page load.
const initialCheck = () => {
let observer: IntersectionObserver | null = null;
let cancelled = false;
// Defer the layout-reading initial check + observer setup to the next
// frame. With 20+ `use:reveal` instances mounting in a row, calling
// getBoundingClientRect() synchronously after a class mutation forces
// a layout recalc per element — a textbook layout-thrash pattern and
// the largest contributor to forced-reflow warnings on this page.
requestAnimationFrame(() => {
if (cancelled) return;
const rect = node.getBoundingClientRect();
const viewportHeight = window.innerHeight || document.documentElement.clientHeight;
if (rect.top < viewportHeight && rect.bottom > 0) {
const initialViewportReach = isMobile ? viewportHeight * 1.18 : viewportHeight;
if (rect.top < initialViewportReach && rect.bottom > 0) {
node.classList.add('reveal-visible');
return true;
return;
}
return false;
};
if (initialCheck()) {
return {
destroy() {}
};
}
const observer = new IntersectionObserver(
(entries) => {
for (const entry of entries) {
if (entry.isIntersecting) {
node.classList.add('reveal-visible');
continue;
}
const viewportHeight = window.innerHeight || document.documentElement.clientHeight;
const rect = entry.boundingClientRect;
const fullyOutOfView = rect.bottom <= 0 || rect.top >= viewportHeight;
if (fullyOutOfView) {
node.classList.remove('reveal-visible');
observer = new IntersectionObserver(
(entries) => {
for (const entry of entries) {
if (entry.isIntersecting) {
node.classList.add('reveal-visible');
observer?.unobserve(node);
break;
}
}
},
{
threshold: effectiveThreshold,
rootMargin: effectiveRootMargin
}
},
{
threshold: settings.threshold,
rootMargin: '0px 0px -8% 0px'
}
);
);
observer.observe(node);
observer.observe(node);
});
return {
destroy() {
observer.disconnect();
cancelled = true;
observer?.disconnect();
}
};
}