基于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 API
if (!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>