import type { Directive, DirectiveBinding } from 'vue' /** * v-reveal — animate an element into view on first scroll-intersection. * *
// default: fade + rise *
// slide from a direction *
* // stagger * * Adds `.bgl-reveal` immediately (hidden, pre-transformed) and toggles * `.bgl-reveal-in` once the element enters the viewport. Honors * `prefers-reduced-motion` (shows instantly, no transform). All visual tuning * lives in CSS (motion.css) via data-attributes, so it's themeable and cheap. */ type Dir = 'up' | 'down' | 'left' | 'right' | 'none' interface RevealOptions { /** Direction the element travels in from. Default 'up'. */ y?: Dir /** Stagger / entrance delay in ms. */ delay?: number /** Animate only once (default) or re-run when scrolled away and back. */ once?: boolean /** 0–1 visibility threshold before triggering. Default 0.12. */ threshold?: number } const prefersReducedMotion = () => typeof window !== 'undefined' && window.matchMedia?.('(prefers-reduced-motion: reduce)').matches function parse(value: RevealOptions | Dir | undefined): Required { const base: Required = { y: 'up', delay: 0, once: true, threshold: 0.12 } if (!value) { return base } if (typeof value === 'string') { return { ...base, y: value } } return { ...base, ...value } } const observers = new WeakMap() const reveal: Directive = { mounted(el, binding: DirectiveBinding) { const opts = parse(binding.value) // Reduced motion: reveal instantly, skip all transforms/observers. if (prefersReducedMotion()) { return } el.classList.add('bgl-reveal') if (opts.y !== 'none') { el.dataset.revealDir = opts.y } if (opts.delay) { el.style.setProperty('--bgl-reveal-delay', `${opts.delay}ms`) } const observer = new IntersectionObserver((entries) => { for (const entry of entries) { if (entry.isIntersecting) { el.classList.add('bgl-reveal-in') if (opts.once) { observer.unobserve(el) } } else if (!opts.once) { el.classList.remove('bgl-reveal-in') } } }, { threshold: opts.threshold, rootMargin: '0px 0px -8% 0px' }) observer.observe(el) observers.set(el, observer) }, unmounted(el) { observers.get(el)?.disconnect() observers.delete(el) }, getSSRProps() { return {} }, } export default reveal export type { RevealOptions }