Files
sanaz-goodarzi/components/page-motion.js
T
2026-09-15 20:37:49 +03:30

61 lines
2.6 KiB
JavaScript

'use client';
import { useEffect } from 'react';
import { usePathname } from 'next/navigation';
const targets = '.reference-service-card, .reference-about-intro, .reference-about-values, .reference-faq, .reference-contact > div, .section-heading, .service-row, .service-detail, .about-band-grid, .contact-cta, .about-intro, .values-grid > div, .faq-section, .contact-info > div, .offices-grid > article, .contact-form, .portfolio-grid > article, .portfolio-detail';
export default function PageMotion() {
const pathname = usePathname();
useEffect(() => {
const main = document.getElementById('main');
if (!main || !('IntersectionObserver' in window)) return;
const preference = window.matchMedia('(prefers-reduced-motion: reduce)');
const seen = new WeakSet();
const prepared = new Set();
const observer = new IntersectionObserver(entries => {
for (const entry of entries) {
if (!entry.isIntersecting) continue;
observer.unobserve(entry.target);
entry.target.dataset.scrollReveal = 'visible';
}
}, { threshold: 0, rootMargin: '0px 0px 48px 0px' });
const prepare = element => {
if (seen.has(element)) return;
seen.add(element);
// Never hide already-visible content, restored scroll positions, or filtered cards.
if (preference.matches || element.getBoundingClientRect().top < window.innerHeight) return;
element.dataset.scrollReveal = 'pending';
prepared.add(element);
observer.observe(element);
};
main.querySelectorAll(targets).forEach(prepare);
// Inspect added subtrees only; do not rescan the page on every Swiper update.
const mutations = new MutationObserver(records => {
for (const record of records) {
for (const node of record.addedNodes) {
if (!(node instanceof Element)) continue;
if (node.matches(targets)) prepare(node);
node.querySelectorAll(targets).forEach(prepare);
}
}
for (const element of prepared) {
if (!element.isConnected) {
observer.unobserve(element);
prepared.delete(element);
}
}
});
mutations.observe(main, { childList: true, subtree: true });
const revealAll = () => {
observer.disconnect();
prepared.forEach(element => element.removeAttribute('data-scroll-reveal'));
prepared.clear();
};
const onPreferenceChange = () => { if (preference.matches) revealAll(); };
preference.addEventListener('change', onPreferenceChange);
return () => {
mutations.disconnect();
revealAll();
preference.removeEventListener('change', onPreferenceChange);
};
}, [pathname]);
return null;
}