// directives/intersectionTransition.ts import { Directive } from 'vue' interface TransitionOptions { type?: string duration?: number | string easing?: string delay?: number | string opacity?: number | string transform?: string threshold?: number } interface IntersectionElement extends HTMLElement { _observer?: IntersectionObserver _options?: TransitionOptions } function setupTransition(el: IntersectionElement, binding: any) { // Handle different binding value types let options: TransitionOptions = {} if (typeof binding.value === 'string') { // Simple string usage: v-show-on-intersect="'fade-up'" options = { type: binding.value } } else if (typeof binding.value === 'object') { // Object usage: v-show-on-intersect="{ type: 'fade-up', duration: '2s', transform: 'translateX(-100px)' }" options = { ...binding.value } } // Set defaults const { type = 'fade-up', duration = '1s', easing = 'cubic-bezier(0.4, 0, 0.2, 1)', delay = '0s', opacity = '0', transform, threshold = 0.1 } = options el._options = options // Get initial transform (custom or preset) const initialTransform = transform || getInitialTransform(type) // Initially hide the element el.style.opacity = String(opacity) el.style.transform = initialTransform el.style.transition = `all ${duration} ${easing} ${delay}` const observer = new IntersectionObserver((entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { // Show element el.style.opacity = '1' el.style.transform = 'none' } else { // Hide element el.style.opacity = String(opacity) el.style.transform = initialTransform } }) }, { threshold: threshold as number }) el._observer = observer observer.observe(el) } export const intersectionTransition: Directive = { mounted(el: IntersectionElement, binding) { setupTransition(el, binding) }, updated(el: IntersectionElement, binding) { // Handle dynamic updates to the directive value if (JSON.stringify(binding.value) !== JSON.stringify(binding.oldValue)) { // Disconnect old observer and setup with new options if (el._observer) { el._observer.disconnect() } setupTransition(el, binding) } }, unmounted(el: IntersectionElement) { if (el._observer) { el._observer.disconnect() } } } function getInitialTransform(transitionType: string): string { switch (transitionType) { case 'fade-up': return 'translateY(5em)' case 'fade-down': return 'translateY(-5em)' case 'slide-left': return 'translateX(5em)' case 'slide-right': return 'translateX(-5em)' case 'scale': return 'scale(0.8)' case 'scale-up': return 'scale(1.2)' case 'rotate': return 'rotate(180deg)' case 'rotate-left': return 'translateY(100px) rotate(12deg) scale(0.9)' case 'rotate-right': return 'translateY(100px) rotate(-12deg) scale(0.9)' case 'flip-x': return 'rotateX(90deg)' case 'flip-y': return 'rotateY(90deg)' default: return '' } }