列表元素滚动动画
基于Vue2以及指令的列表滚动显示动画
<template><div><h2>Scroll Animation Demo</h2><div class="list-container"><div class="spacer">向下滚动查看动画效果...</div><div v-for="item in 10" :key="item" v-slide-in class="list-item">Item {{ item }}</div><div class="spacer">向上滚动不会触发新的动画</div></div></div>
</template><script>
const OFFSET = 200;
const DURATION = 800;
const EASING = 'cubic-bezier(0.25, 0.46, 0.45, 0.94)';
const FILL = 'forwards';// 使用全局 WeakMap 和 IntersectionObserver
const animationMap = new WeakMap();
const animatedElements = new WeakSet(); // 记录已经动画过的元素
let lastScrollY = 0; // 记录上次滚动位置// 滚动事件处理函数
const handleScroll = () => {lastScrollY = window.scrollY;console.log('Scroll position updated:', lastScrollY);
};const intersectionObserver = new IntersectionObserver((entries) => {const currentScrollY = window.scrollY;const isScrollingDown = currentScrollY >= lastScrollY;console.log('Observer triggered:', { currentScrollY, lastScrollY, isScrollingDown,entriesCount: entries.length});entries.forEach((entry) => {console.log('Entry:', { isIntersecting: entry.isIntersecting,alreadyAnimated: animatedElements.has(entry.target)});if (entry.isIntersecting && !animatedElements.has(entry.target)) {const animation = animationMap.get(entry.target);if (animation) {console.log('Playing animation for element');animation.play();animatedElements.add(entry.target); // 标记为已动画}}});
}, {threshold: 0.1,rootMargin: '0px 0px -10px 0px' // 调整为更容易触发
});export default {name: 'TestContent11',directives: {slideIn: {inserted(el) {// 检查浏览器是否支持 Web Animations APIif (!el.animate) {console.warn('Web Animations API not supported');return;}// 设置初始样式,确保元素在动画前是隐藏的el.style.opacity = '0';// 创建动画const animation = el.animate([{transform: `translateY(${OFFSET}px)`,opacity: 0,},{transform: 'translateY(0)',opacity: 1,}], {duration: DURATION,easing: EASING,fill: FILL,});// 暂停动画animation.pause();console.log('Animation created and paused for element');// 开始观察元素intersectionObserver.observe(el);console.log('Element is being observed');// 存储动画引用animationMap.set(el, animation);},unbind(el) {// 停止观察元素intersectionObserver.unobserve(el);// 清理动画引用if (animationMap.has(el)) {const animation = animationMap.get(el);animation.cancel();animationMap.delete(el);}// 清理已动画标记animatedElements.delete(el);}}},data() {return {};},mounted() {// 初始化滚动位置lastScrollY = window.scrollY;console.log('Initial scroll position:', lastScrollY);// 监听滚动事件更新位置window.addEventListener('scroll', handleScroll, { passive: true });},beforeDestroy() {// 移除滚动事件监听window.removeEventListener('scroll', handleScroll);}
};
</script><style scoped>
.list-container {max-width: 600px;margin: 0 auto;padding-bottom: 500px; /* 添加足够的空间以便滚动 */
}.list-item {padding: 30px;margin: 20px 0;background-color: white;border-radius: 15px;box-shadow: 0 8px 25px rgba(0,0,0,0.1);border-left: 5px solid #667eea;transition: transform 0.3s ease, box-shadow 0.3s ease;font-size: 1.5em;font-weight: bold;text-align: center;color: #667eea;
}.list-item:hover {transform: translateY(-5px);box-shadow: 0 15px 35px rgba(0,0,0,0.15);
}.spacer {height: 200px;display: flex;align-items: center;justify-content: center;color: #667eea;font-size: 1.2em;opacity: 0.8;
}
</style>