/** * 滚动入场动画 composable * 元素进入视口时触发淡入上滑动画 * 自动尊重 prefers-reduced-motion * 使用 MutationObserver 确保异步渲染的子组件也能被观察到 */ export default function useScrollReveal() { onMounted(() => { // 尊重用户减少动效偏好 if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return const io = new IntersectionObserver( (entries) => { entries.forEach((entry) => { if (entry.isIntersecting) { entry.target.classList.add('is-visible') io.unobserve(entry.target) } }) }, { threshold: 0.1, rootMargin: '0px 0px -40px 0px' } ) // 观察当前已有的 .reveal 元素 const observed = new WeakSet() function observeAll() { document.querySelectorAll('.reveal:not(.is-visible)').forEach((el) => { if (!observed.has(el)) { observed.add(el) io.observe(el) } }) } observeAll() // 监听 DOM 变化,捕获异步渲染的子组件中的 .reveal 元素 const mo = new MutationObserver(observeAll) mo.observe(document.body, { childList: true, subtree: true }) onUnmounted(() => { io.disconnect() mo.disconnect() }) }) }