基于滚动值实现三张横向卡片的动画效果
横向卡片滚动视差动画实现优化
我有三张横向排列的卡片,想要实现类似某站点Result Section的滚动视差动画效果,目前编写了一段JS代码,但运行效果不符合预期,以下是我的代码,求优化:
window.addEventListener('scroll', () => { const cardOne = document.getElementById("box_one") const cardTwo = document.getElementById("box_two") const cardThree = document.getElementById("box_three") const scrollYValue = window.scrollY; $(window).scroll(function () { if ($(window).scrollTop() > $('body').height() / 2) { const cardOne = document.getElementById("box_one") const cardTwo = document.getElementById("box_two") const cardThree = document.getElementById("box_three") const scrollYValue = window.scrollY; $(window).scroll(function() { var position = $(window).scrollTop(); var scroll = $(window).scrollTop(); // console.log(scroll, position); if (scroll >= position) { console.log("Up"); cardOne.style.transform = "translate3d(0px, " + -scrollYValue / 200 + "em, 0px)", cardTwo.style.transform = "translate3d(0px, " + -scrollYValue / 600 + "em, 0px)", cardThree.style.transform = `translateY(${scrollYValue / 30}px)` } else if (scroll <= position) { console.log("back"); cardOne.style.transform = "translate3d(0px, " + scrollYValue + "px, 0px)", cardTwo.style.transform = `translateY(${-scrollYValue / 90}px)` cardThree.style.transform = `translateY(${-scrollYValue / 30}px)` } position = scroll; }); } }); })
代码存在的问题
- 重复绑定scroll事件:外层已通过原生JS绑定scroll事件,内部又嵌套两次jQuery的scroll绑定,会导致事件多次触发,严重影响性能,甚至出现动画抖动。
- DOM元素重复获取:每次scroll都重新调用
getElementById,完全没必要,应该初始化时缓存元素。 - 滚动方向判断失效:
position和scroll初始值都是当前scrollTop,后续position = scroll会导致scroll >= position永远成立,"back"分支永远不会执行。 - 单位混用:动画同时使用
em和px,导致动画速度不协调,视觉效果混乱。 - 触发条件不合理:用
body高度的一半作为触发时机,应该判断卡片是否进入视口,避免在页面其他区域滚动时触发无用动画。
优化后的实现方案
核心思路
- 缓存DOM元素,减少重复查询
- 仅绑定一次scroll事件,避免重复触发
- 用
IntersectionObserver判断卡片区域是否进入视口,仅在可视范围内执行动画 - 正确记录上一次滚动位置,判断滚动方向
- 统一动画单位,调整视差参数,模拟目标效果
完整代码
HTML结构示例
<div class="cards-container"> <div class="card" id="box_one">卡片1</div> <div class="card" id="box_two">卡片2</div> <div class="card" id="box_three">卡片3</div> </div>
CSS基础样式
.cards-container { display: flex; gap: 2rem; justify-content: center; margin: 100vh 0; /* 让卡片处于页面中间,方便测试滚动 */ } .card { width: 300px; height: 400px; background: #fff; box-shadow: 0 4px 12px rgba(0,0,0,0.1); border-radius: 8px; display: flex; align-items: center; justify-content: center; font-size: 1.5rem; position: relative; }
JS代码
// 缓存DOM元素 const cardOne = document.getElementById("box_one"); const cardTwo = document.getElementById("box_two"); const cardThree = document.getElementById("box_three"); const cardsContainer = document.querySelector(".cards-container"); let lastScrollTop = 0; let isAnimating = false; // 监听卡片区域是否进入视口 const observer = new IntersectionObserver((entries) => { entries.forEach(entry => { isAnimating = entry.isIntersecting; }); }, { threshold: 0.1 }); observer.observe(cardsContainer); // 绑定一次scroll事件 window.addEventListener('scroll', () => { if (!isAnimating) return; const scrollTop = window.scrollY; // 判断滚动方向 const isScrollUp = scrollTop < lastScrollTop; // 计算视差偏移量(可根据需求调整系数) const offsetFactor = scrollTop * 0.02; if (isScrollUp) { // 向上滚动时的动画 cardOne.style.transform = `translateY(${offsetFactor * 1.5}px)`; cardTwo.style.transform = `translateY(${offsetFactor * 0.8}px)`; cardThree.style.transform = `translateY(${offsetFactor * 0.3}px)`; } else { // 向下滚动时的动画 cardOne.style.transform = `translateY(${-offsetFactor * 1.5}px)`; cardTwo.style.transform = `translateY(${-offsetFactor * 0.8}px)`; cardThree.style.transform = `translateY(${-offsetFactor * 0.3}px)`; } lastScrollTop = scrollTop; }, { passive: true });
说明
- 调整
offsetFactor后的系数可以控制每张卡片的滚动速度差,系数越大,视差效果越明显 IntersectionObserver确保只有卡片进入视口时才执行动画,提升页面性能- 使用
passive: true优化滚动事件的性能,避免浏览器卡顿
内容的提问来源于stack exchange,提问作者sajeevan
相关产品推荐
相关产品推荐

