请求指导:如何实现滚动时图片沿曲线轨迹的动画效果
实现方案
核心思路
- 监听
scroll事件,计算滚动进度(当前滚动距离 / 页面可滚动总距离) - 利用数学公式(贝塞尔曲线、三角函数)计算元素在不同进度下的坐标,替代直接操作SVG path的复杂逻辑
- 通过CSS
transform实现平滑移动,结合opacity控制元素显示/隐藏
示例代码
HTML结构
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>滚动轨迹动画</title> <style> body { margin: 0; height: 300vh; /* 足够长的滚动页面 */ position: relative; background: #f0f0f0; } .curve-img, .circle-img { position: fixed; width: 80px; height: 80px; border-radius: 8px; transition: opacity 0.3s ease; display: flex; align-items: center; justify-content: center; color: white; font-size: 14px; } .curve-img { background: #4285F4; opacity: 1; } .circle-img { background: #EA4335; opacity: 0; /* 初始隐藏 */ } </style> </head> <body> <div class="curve-img">曲线移动</div> <div class="circle-img">环形移动</div> <script> const curveImg = document.querySelector('.curve-img'); const circleImg = document.querySelector('.circle-img'); const bodyHeight = document.body.scrollHeight - window.innerHeight; // 曲线轨迹:二次贝塞尔曲线,可自定义起点、控制点、终点 function getCurvePosition(progress) { const startX = 100; const startY = 100; const controlX = 500; const controlY = 300; const endX = 900; const endY = 100; // 二次贝塞尔公式:B(t) = (1-t)²P0 + 2(1-t)tP1 + t²P2 const x = Math.pow(1 - progress, 2) * startX + 2 * (1 - progress) * progress * controlX + Math.pow(progress, 2) * endX; const y = Math.pow(1 - progress, 2) * startY + 2 * (1 - progress) * progress * controlY + Math.pow(progress, 2) * endY; return {x, y}; } // 环形轨迹:自定义圆心、半径,从底部开始顺时针转一圈 function getCirclePosition(progress) { const centerX = 600; const centerY = 400; const radius = 150; // 角度从π(底部)到3π(转一圈回到底部) const angle = Math.PI + progress * 2 * Math.PI; const x = centerX + radius * Math.cos(angle); const y = centerY + radius * Math.sin(angle); return {x, y}; } // 滚动监听逻辑,加入性能优化 let ticking = false; window.addEventListener('scroll', () => { if (!ticking) { requestAnimationFrame(() => { const scrollProgress = window.scrollY / bodyHeight; // 处理曲线移动元素 const curvePos = getCurvePosition(scrollProgress); curveImg.style.transform = `translate(${curvePos.x}px, ${curvePos.y}px)`; // 处理环形移动元素:滚动进度超过0.2时显示,0.2-0.8区间完成环形运动 if (scrollProgress > 0.2) { circleImg.style.opacity = 1; const circleProgress = Math.min((scrollProgress - 0.2) / 0.6, 1); const circlePos = getCirclePosition(circleProgress); circleImg.style.transform = `translate(${circlePos.x}px, ${circlePos.y}px)`; } else { circleImg.style.opacity = 0; } ticking = false; }); ticking = true; } }); // 初始化触发一次,避免初始位置错误 window.dispatchEvent(new Event('scroll')); </script> </body> </html>
关键说明
- 曲线轨迹调整:修改
getCurvePosition里的startX/Y、controlX/Y、endX/Y参数,就能自定义曲线的形状和走向 - 环形轨迹调整:修改
getCirclePosition里的centerX/Y(圆心)、radius(半径),或者调整angle的起始值,就能改变环形的位置和转动方向 - 动画区间控制:如果想让动画在特定滚动段触发,比如只在页面中间1/3部分运动,修改
scrollProgress的映射逻辑即可(示例中环形动画绑定在20%-80%的滚动区间) - 性能优化:用
requestAnimationFrame包裹滚动回调,避免频繁触发重绘,解决滚动卡顿问题
关于SVG Path的补充
如果之前尝试SVG的<animateMotion>失败,核心问题是SVG动画默认是时间驱动,而非滚动驱动。若一定要用SVG实现,需要把滚动进度绑定到动画的progress属性,但逻辑比JS计算坐标更复杂,上面的方案更直观、易调试。
内容的提问来源于stack exchange,提问作者Ariel
相关产品推荐
相关产品推荐

