如何使用JS结合pixi.js实现自定义图片的布料/果冻拉伸交互效果
基于PixiJS实现图片拉伸(果冻/布料)交互效果方案
核心实现思路
不需要引入额外物理引擎,基于网格变形+简单弹簧物理模型即可实现目标效果:
- 首先把目标图片拆解为顶点网格,不直接操作整张图片,而是将图片分割为N行N列的三角形网格,每个顶点存储初始坐标、当前坐标、运动速度三个属性
- 给所有顶点绑定弹簧物理规则:顶点距离初始位置越远,受到的回弹拉力越大,同时加入阻尼系数避免无限回弹
- 监听鼠标交互事件:按下时检测距离鼠标最近的网格顶点,绑定为跟随顶点,鼠标移动时带动该顶点偏移,松开后顶点自动回弹到初始位置
关键实现步骤
1. 初始化网格纹理
使用PixiJS内置的SimplePlane类快速创建可变形网格:
// 已提前创建PIXI.Application实例app const texture = PIXI.Texture.from('目标图片资源路径'); // 定义网格行列数,数值越高变形越细腻,对应性能消耗越高 const plane = new PIXI.SimplePlane(texture, 20, 20); app.stage.addChild(plane); // 存储所有顶点的初始坐标,用于回弹计算 const originalPositions = plane.geometry.attributes.position.array.slice(); // 初始化顶点速度数组,用于物理计算 const velocities = new Array(originalPositions.length).fill(0);
2. 绑定鼠标交互事件
let draggingVertexIndex = -1; const vertexSnapThreshold = 15; // 鼠标吸附顶点的检测距离阈值 plane.interactive = true; // 鼠标按下时寻找最近的绑定顶点 plane.on('pointerdown', (e) => { const mouseLocalPos = e.data.getLocalPosition(plane); const positions = plane.geometry.attributes.position.array; let minDistance = Infinity; for (let i = 0; i < positions.length; i += 2) { const dx = positions[i] - mouseLocalPos.x; const dy = positions[i + 1] - mouseLocalPos.y; const distance = Math.hypot(dx, dy); if (distance < minDistance && distance < vertexSnapThreshold) { minDistance = distance; draggingVertexIndex = i; } } }); // 鼠标移动时更新拖拽顶点位置 app.stage.on('pointermove', (e) => { if (draggingVertexIndex === -1) return; const mouseLocalPos = e.data.getLocalPosition(plane); const positions = plane.geometry.attributes.position.array; positions[draggingVertexIndex] = mouseLocalPos.x; positions[draggingVertexIndex + 1] = mouseLocalPos.y; plane.geometry.attributes.position.update(); }); // 鼠标松开时解除拖拽绑定 app.stage.on('pointerup pointerupoutside', () => { draggingVertexIndex = -1; });
3. 加入弹簧物理回弹效果
在PixiJS的帧更新ticker中添加物理计算逻辑:
// 物理参数,可根据需求调整质感 const stiffness = 0.05; // 弹簧刚度,数值越大回弹速度越快 const damping = 0.92; // 阻尼系数,数值越小回弹衰减越快 app.ticker.add(() => { // 拖拽状态下不触发回弹计算 if (draggingVertexIndex !== -1) return; const positions = plane.geometry.attributes.position.array; for (let i = 0; i < positions.length; i += 2) { // 计算当前顶点和初始位置的偏移量 const offsetX = originalPositions[i] - positions[i]; const offsetY = originalPositions[i + 1] - positions[i + 1]; // 计算弹簧拉力对应的加速度 const accelerationX = offsetX * stiffness; const accelerationY = offsetY * stiffness; // 更新顶点速度 velocities[i] += accelerationX; velocities[i + 1] += accelerationY; velocities[i] *= damping; velocities[i + 1] *= damping; // 更新顶点坐标 positions[i] += velocities[i]; positions[i + 1] += velocities[i + 1]; } // 提交顶点更新到渲染器 plane.geometry.attributes.position.update(); });
效果优化建议
- 若要实现更自然的布料效果,可以给相邻顶点添加联动约束:单个顶点偏移时,带动周边一定范围内的顶点做等比例小幅度偏移,避免单个顶点拉扯过于突兀
- 网格行列数按需调整:图标类小尺寸图片用1010网格即可,大尺寸背景图建议控制在3030以内,避免性能损耗过高
- 通过调整物理参数切换效果:刚度调低、阻尼调低是软质布料效果,刚度调高、阻尼调到0.9左右是Q弹果冻效果
内容的提问来源于stack exchange,提问作者sirotkin37
相关产品推荐
相关产品推荐

