说在前面
大家平时做图片展示,很多都是卡片平铺、瀑布流、轮播图。 这次我们换个思路:把图片“挂”在一根绳子上,加上随风摆动的动态效果,支持拖拽拉扯回弹。
在线体验
codePen
码上掘金
关键代码
1、场景分层
Canvas 画绳子,DOM 放照片
canvas#ropeCanvas:只负责画绳子。#photos:绝对定位的图片元素层。.controls:风力滑块控制区。
<canvas id="ropeCanvas"></canvas>
<div id="photos"></div>
<div class="controls">...</div>
这么拆的好处是:绳子可以高频重绘,图片继续保留 DOM 的 3D transform 和 pointer 交互能力,性能和开发体验都更稳。
2、绳子曲线
线性插值 + 抛物线下垂
绳子不是死直线,而是通过参数 t(0~1)取点:
function ropeAnchorPoint(t) {
const lineX = lerp(ropeStart.x, ropeEnd.x, t);
const lineY = lerp(ropeStart.y, ropeEnd.y, t);
const arc = 4 * t * (1 - t);
return {
x: lineX + ropeSway * arc + dragX * (0.36 + 0.64 * arc),
y: lineY + ropeSag * arc + dragY * (0.36 + 0.64 * arc),
};
}
arc = 4t(1-t) 是关键,它在中点最大、两端最小,天然适合模拟“中间下垂、两端固定”的绳子形态。
3、照片摆动
弹簧阻尼模型做“钟摆感”
每张图都有自己的 angle(角度)和 velocity(角速度),每帧按受力更新:
const acc =
-p.stiffness * Math.sin(p.angle - p.restAngle) -
p.damping * p.velocity +
scaledWind;
p.velocity += acc * dt;
p.angle += p.velocity * dt;
这里本质是“回复力 + 阻尼 + 风力扰动”。
不同图片还带随机 phase 和 mass,所以摇摆不会完全同步,看起来就更像真实挂件。
4、拖拽联动
限制位移 + 弹性回归
拖拽不是直接把图片瞬移,而是把拖拽位移转成“绳子的外力输入”:
const len = Math.hypot(dx, dy);
const dragLimit = 110;
if (len > dragLimit) {
const ratio = dragLimit / len;
dx *= ratio;
dy *= ratio;
}
然后再通过速度与阻尼平滑回弹:
dragVX += (dragTargetX - dragX) * dragK * dt;
dragVX *= Math.exp(-dragDamping * dt);
dragX += dragVX * dt;
5、视觉细节
绳子高光 + 穿绳遮挡 + 透视倾斜
这个效果需要注意一些细节:
- 绳子画两遍:粗深色主线 + 细浅色高光线。
- 每张图上方加
.rope-pass,并按切线角度旋转,制造“绳子穿过卡片孔位”的假象。 - 图片 transform 叠加
rotateZ + rotateY + rotateX,速度越大越有轻微俯仰感。
const tangent = ropeTangent(p.t);
const tangentAngle = Math.atan2(tangent.y, tangent.x);
p.passEl.style.transform = `translate(0, -50%) rotate(${tangentAngle}rad)`;
源码地址
gitee
github
🌟 觉得有帮助的可以点个 star~
🖊 有什么问题或错误可以指出,欢迎 pr~
📬 有什么想要实现的功能或想法可以联系我~
公众号
关注公众号『 前端也能这么有趣 』,获取更多有趣内容。
发送 加群 还可以加入群聊,一起来学习(摸鱼)吧~
说在后面
🎉 这里是 JYeontu,现在是一名前端工程师,有空会刷刷算法题,平时喜欢打羽毛球 🏸 ,平时也喜欢写些东西,既为自己记录 📋,也希望可以对大家有那么一丢丢的帮助,写的不好望多多谅解 🙇,写错的地方望指出,定会认真改进 😊,偶尔也会在自己的公众号『前端也能这么有趣』发一些比较有趣的文章,有兴趣的也可以关注下。在此谢谢大家的支持,我们下文再见 🙌。