Files
KantSites/public/assets/motion.js
T
kantarci 495b26896e
CMS quality / php (8.2) (push) Canceled after 0s
CMS quality / php (8.4) (push) Canceled after 0s
CMS quality / browser (push) Canceled after 0s
CMS quality / mysql (push) Canceled after 0s
Fassung 1.7.0: verlässliche Installation, übersichtliche Verwaltung und bedienbare Bewegung
2026-09-14 10:49:54 +02:00

36 lines
2.4 KiB
JavaScript

/** Visible content is the baseline. Enhance only when it enters the viewport. */
export function initMotion(doc = document) {
const view = doc.defaultView;
const preference = view.matchMedia('(prefers-reduced-motion: reduce)');
const animations = new Set();
const seen = new WeakSet();
const start = {
fade: { opacity: .2 }, rise: { transform: 'translateY(28px)' }, sink: { transform: 'translateY(-28px)' },
left: { transform: 'translateX(-36px)' }, right: { transform: 'translateX(36px)' },
zoom: { transform: 'scale(.92)' }, flip: { transform: 'perspective(800px) rotateX(-16deg)' }, blur: { filter: 'blur(6px)' },
};
const nodes = [...doc.querySelectorAll('[class*="fx-entrance-"]')].filter(node => Object.keys(start).some(name => node.classList.contains(`fx-entrance-${name}`)));
const show = node => {
if (seen.has(node) || preference.matches) return;
seen.add(node);
const name = Object.keys(start).find(name => node.classList.contains(`fx-entrance-${name}`));
const delay = node.classList.contains('fx-delay-long') ? 600 : node.classList.contains('fx-delay-medium') ? 300 : node.classList.contains('fx-delay-short') ? 150 : 0;
// Fade itself is optional; direction, zoom and flip keep text at full contrast.
const style = view.getComputedStyle(node);
const finish = { opacity: style.opacity, transform: style.transform, filter: style.filter };
const first = { ...start[name] };
if (first.transform && finish.transform !== 'none') first.transform += ' ' + finish.transform;
const animation = node.animate([first, finish], { duration: 650, delay, easing: 'cubic-bezier(.2,.7,.2,1)', fill: 'none' });
animations.add(animation); animation.finished.then(() => animations.delete(animation), () => animations.delete(animation));
};
const observer = new view.IntersectionObserver(entries => {
for (const entry of entries) if (entry.isIntersecting) { show(entry.target); observer.unobserve(entry.target); }
}, { threshold: 0, rootMargin: '0px 0px -24px 0px' });
if (!preference.matches) nodes.forEach(node => observer.observe(node));
const reduce = () => { if (preference.matches) { observer.disconnect(); animations.forEach(animation => animation.cancel()); } };
preference.addEventListener('change', reduce);
return () => { observer.disconnect(); animations.forEach(animation => animation.cancel()); preference.removeEventListener('change', reduce); };
}
if (!document.querySelector('#editor')) initMotion();