v4
This commit is contained in:
+40
-39
@@ -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();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user