feat(motion): add scroll reveals and micro-interactions
This commit is contained in:
75
src/components/MotionController.tsx
Normal file
75
src/components/MotionController.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
'use client';
|
||||
|
||||
import { useEffect } from 'react';
|
||||
|
||||
const REVEAL_SELECTOR = '[data-reveal]';
|
||||
|
||||
export const MotionController = () => {
|
||||
useEffect(() => {
|
||||
const elements = Array.from(
|
||||
document.querySelectorAll<HTMLElement>(REVEAL_SELECTOR),
|
||||
);
|
||||
|
||||
if (!elements.length) return;
|
||||
|
||||
const reduceMotion = window.matchMedia(
|
||||
'(prefers-reduced-motion: reduce)',
|
||||
).matches;
|
||||
|
||||
document.querySelectorAll<HTMLElement>('[data-reveal-group]').forEach((group) => {
|
||||
group.querySelectorAll<HTMLElement>(REVEAL_SELECTOR).forEach((element, index) => {
|
||||
element.style.setProperty(
|
||||
'--reveal-delay',
|
||||
`${Math.min(index, 5) * 50}ms`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
if (reduceMotion) {
|
||||
elements.forEach((element) => {
|
||||
element.dataset.visible = 'true';
|
||||
});
|
||||
document.documentElement.dataset.motionReady = 'true';
|
||||
return () => {
|
||||
delete document.documentElement.dataset.motionReady;
|
||||
};
|
||||
}
|
||||
|
||||
const initialRevealBoundary = window.innerHeight * 0.92;
|
||||
elements.forEach((element) => {
|
||||
const bounds = element.getBoundingClientRect();
|
||||
if (bounds.top <= initialRevealBoundary && bounds.bottom >= 0) {
|
||||
element.dataset.visible = 'true';
|
||||
}
|
||||
});
|
||||
|
||||
document.documentElement.dataset.motionReady = 'true';
|
||||
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
entries.forEach((entry) => {
|
||||
if (!entry.isIntersecting) return;
|
||||
|
||||
const element = entry.target as HTMLElement;
|
||||
element.dataset.visible = 'true';
|
||||
observer.unobserve(element);
|
||||
});
|
||||
},
|
||||
{
|
||||
rootMargin: '0px 0px -8% 0px',
|
||||
threshold: 0.12,
|
||||
},
|
||||
);
|
||||
|
||||
elements.forEach((element) => {
|
||||
if (element.dataset.visible !== 'true') observer.observe(element);
|
||||
});
|
||||
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
delete document.documentElement.dataset.motionReady;
|
||||
};
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
};
|
||||
Reference in New Issue
Block a user