news 2026/9/23 7:33:29

弹簧质点系统(Mass-Spring System):Canvas 模拟软胶果冻物理抖动

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
弹簧质点系统(Mass-Spring System):Canvas 模拟软胶果冻物理抖动

弹簧质点系统(Mass-Spring System):Canvas 模拟软胶果冻物理抖动

在现代高阶 UI 微交互、生动吉祥物萌宠动画以及先锋触控界面设计中,“果冻/软胶布丁般的柔体抖动质感(Soft-Body Jiggle Physics)”是一种能极大提升界面趣味性、亲和力与用户触摸满足感的顶级动效。

然而,许多前端在手写“果冻效果”时,往往只是简单地在 CSS 中修改scaleXscaleY(如scale(1.1, 0.9)简单往复变换)。
这种伪果冻动效在人眼看来极其单调和机械——因为整个形状是刚性均匀缩放的,没有任何局部波浪传递与体积形变

在计算物理与柔体动力学(Soft Body Simulation)中,弹簧-质点系统(Mass-Spring System)是以极简算力实现真实布料、软胶与果冻物理形变的经典皇冠模型。

本文将深入推导弹簧质点力学模型与韦尔莱数值积分(Verlet Integration),并在 HTML5 Canvas 中手写一个单屏 120fps、鼠标拖拽时产生真实弹性波浪传递的软胶果冻模拟器。

弹簧质点系统的三层网格拓扑力学结构

要让一个二维物体在受到外力拉扯时,既能弹性形变、又不会被扯碎或瞬间坍塌,质点之间必须建立三种互相交织的弹簧约束:

P(0,0) ──[结构弹簧]── P(0,1) ──[结构弹簧]── P(0,2) │ ╲ ╱ │ ╲ ╱ │ │ ╲ [剪切弹簧] ╱ │ ╲ [剪切弹簧] ╱ │ │ ╲ ╱ │ ╲ ╱ │ │ ╲ ╱ │ ╲ ╱ │ P(1,0) ──[结构弹簧]── P(1,1) ──[结构弹簧]── P(1,2) │ │ │ └─── [弯曲弹簧] ────┴─── [弯曲弹簧] ────┘ (跨越两个质点,防止过度折叠萎缩)
  1. 结构弹簧(Structural Springs):连接相邻的水平与垂直质点,维持物体的基础网格轮廓;
  2. 剪切弹簧(Shear Springs):连接对角线质点,防止正方形网格发生压扁平移形变;
  3. 弯曲弹簧(Bending Springs):跨越一个质点连接隔行隔列的节点,维持果冻内部的抗弯刚度与充盈“体积感”!
弹簧胡克定律与阻尼力方程

连接质点 $i$ 与质点 $j$ 的弹簧合力为:

$$\mathbf{F}_{ij} = -k \left( |\mathbf{p}_i - \mathbf{p}_j| - L_0 \right) \frac{\mathbf{p}_i - \mathbf{p}_j}{|\mathbf{p}_i - \mathbf{p}_j|} - c (\mathbf{v}_i - \mathbf{v}_j)$$

其中 $L_0$ 为弹簧静止原长,$k$ 为刚度系数,$c$ 为阻尼耗散系数。

韦尔莱数值积分(Verlet Integration):绝对稳定的物理步进

相比于容易在大幅度拉伸时发生能量爆炸崩溃的显式欧拉法,位置韦尔莱积分(Position Verlet)无需显式存储速度,具有极高的数值稳定性:

$$\mathbf{p}_{n+1} = 2\mathbf{p}n - \mathbf{p}{n-1} + \mathbf{a}_n \cdot \Delta t^2$$

引入速度衰减因子(空气阻力与内部摩擦):

$$\mathbf{p}_{n+1} = \mathbf{p}_n + (\mathbf{p}n - \mathbf{p}{n-1}) \cdot (1 - \text{friction}) + \mathbf{a}_n \cdot \Delta t^2$$

// softbody-mass-spring.ts export class PointMass { public x: number; public y: number; public oldX: number; public oldY: number; public pinned: boolean = false; // 是否被鼠标钉住抓取 constructor(x: number, y: number) { this.x = x; this.y = y; this.oldX = x; this.oldY = y; } public update(friction: number = 0.02, gravity: number = 0.15) { if (this.pinned) return; const vx = (this.x - this.oldX) * (1 - friction); const vy = (this.y - this.oldY) * (1 - friction); this.oldX = this.x; this.oldY = this.y; this.x += vx; this.y += vy + gravity; } } export class SpringLink { public p1: PointMass; public p2: PointMass; public restLength: number; public stiffness: number; constructor(p1: PointMass, p2: PointMass, stiffness: number = 0.5) { this.p1 = p1; this.p2 = p2; this.restLength = Math.hypot(p1.x - p2.x, p1.y - p2.y); this.stiffness = stiffness; } // 刚度距离约束求解 (Relaxation Pass) public solve() { const dx = this.p2.x - this.p1.x; const dy = this.p2.y - this.p1.y; const dist = Math.hypot(dx, dy) || 1; const diff = (dist - this.restLength) / dist; const offsetX = dx * diff * 0.5 * this.stiffness; const offsetY = dy * diff * 0.5 * this.stiffness; if (!this.p1.pinned) { this.p1.x += offsetX; this.p1.y += offsetY; } if (!this.p2.pinned) { this.p2.x -= offsetX; this.p2.y -= offsetY; } } }

Canvas 果冻网格平滑轮廓渲染

// jelly-canvas-renderer.ts export class JellyStage { private canvas: HTMLCanvasElement; private ctx: CanvasRenderingContext2D; private points: PointMass[] = []; private springs: SpringLink[] = []; private cols = 5; private rows = 5; private spacing = 32; constructor(canvas: HTMLCanvasElement) { this.canvas = canvas; this.ctx = canvas.getContext('2d')!; this.buildJellyGrid(200, 150); } private buildJellyGrid(startX: number, startY: number) { // 1. 初始化 5x5 质点网格 for (let r = 0; r < this.rows; r++) { for (let c = 0; c < this.cols; c++) { this.points.push(new PointMass(startX + c * this.spacing, startY + r * this.spacing)); } } // 2. 编织结构弹簧与剪切弹簧 for (let r = 0; r < this.rows; r++) { for (let c = 0; c < this.cols; c++) { const idx = r * this.cols + c; // 水平结构弹簧 if (c < this.cols - 1) { this.springs.push(new SpringLink(this.points[idx], this.points[idx + 1], 0.6)); } // 垂直结构弹簧 if (r < this.rows - 1) { this.springs.push(new SpringLink(this.points[idx], this.points[idx + this.cols], 0.6)); } // 对角剪切弹簧 if (c < this.cols - 1 && r < this.rows - 1) { this.springs.push(new SpringLink(this.points[idx], this.points[idx + this.cols + 1], 0.4)); this.springs.push(new SpringLink(this.points[idx + 1], this.points[idx + this.cols], 0.4)); } } } } public stepAndRender() { // 1. 物理步进 for (const p of this.points) { p.update(0.015, 0.05); // 微重力与低摩擦 } // 2. 多轮约束松弛迭代 (保障果冻刚度) for (let iter = 0; iter < 4; iter++) { for (const s of this.springs) { s.solve(); } } // 3. Canvas 渲染晶莹果冻外观 const w = this.canvas.width; const h = this.canvas.height; this.ctx.fillStyle = '#090d16'; this.ctx.fillRect(0, 0, w, h); // 绘制外圈平滑轮廓闭包 this.ctx.beginPath(); // 提取外边框质点 const perimeterIndices = [0, 1, 2, 3, 4, 9, 14, 19, 24, 23, 22, 21, 20, 15, 10, 5]; for (let i = 0; i < perimeterIndices.length; i++) { const p = this.points[perimeterIndices[i]]; if (i === 0) this.ctx.moveTo(p.x, p.y); else this.ctx.lineTo(p.x, p.y); } this.ctx.closePath(); // 填充水晶果冻渐变色 const grad = this.ctx.createLinearGradient(150, 100, 350, 300); grad.addColorStop(0, 'rgba(99, 102, 241, 0.85)'); grad.addColorStop(1, 'rgba(236, 72, 153, 0.85)'); this.ctx.fillStyle = grad; this.ctx.fill(); this.ctx.strokeStyle = '#ffffff'; this.ctx.lineWidth = 3; this.ctx.stroke(); } }

总结

真实的弹性形变与波浪传递是柔体动力学赋予数字交互最神奇的魔法。看清结构弹簧、剪切弹簧与弯曲弹簧在空间中的抗变形约束,用韦尔莱数值积分接管质点的物理步进,我们就能在纯前端 Canvas 画布上,以极轻的算力复现出如水晶软胶果冻般灵动弹颤的 AAA 级物理微交互体验。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/23 7:28:14

Java全栈开发实战:亲子互动平台架构与实现

1. 项目背景与核心价值作为一名在Java全栈开发领域深耕多年的技术人&#xff0c;我见过太多缺乏实战价值的毕业设计项目。这个亲子互动平台的设计初衷&#xff0c;是要解决当代家庭教育中三个核心痛点&#xff1a;亲子陪伴时间碎片化、互动形式单一化、成长记录分散化。根据中国…

作者头像 李华
网站建设 2026/9/23 7:27:13

从拖延到发布:我如何写出第一篇博客并坚持下去

我第一次真正把一篇博客发出去的时候&#xff0c;距离我注册好域名整整过去了十一个月。那十一个月里我换了三次博客主题、研究过十几套评论插件、甚至给文章分类都想好了七八个名字&#xff0c;但正文一个字都没写。最后把我从这种"准备永动机"里拽出来的&#xff0…

作者头像 李华
网站建设 2026/9/23 7:26:38

消息队列内存数据中心架构设计与优化实践

1. 内存数据中心的架构设计在消息队列系统中&#xff0c;MemoryDataCenter扮演着至关重要的角色。作为整个系统的内存中枢&#xff0c;它负责管理所有运行时数据&#xff0c;包括交换机、队列、绑定关系以及消息本身。这种全内存的设计理念源于对高性能的极致追求——相比磁盘I…

作者头像 李华
网站建设 2026/9/23 7:26:13

Claude Code 知识工作插件实战:用 slash commands 封装高效工作流

1. 从标题说起&#xff1a;knowledge-work-plugins 到底是个什么定位第一次看到knowledge-work-plugins这个仓库名&#xff0c;我的直觉是&#xff1a;这不是一个普通的小工具&#xff0c;而是一套面向“知识工作者”的插件集合。知识工作者这个词覆盖面很广——写代码的、写文…

作者头像 李华
网站建设 2026/9/23 7:24:33

C++ volatile与atomic关键字深度解析与应用实践

1. volatile 关键字深度解析1.1 volatile 的本质与编译器行为volatile 是 C 中最容易被误解的关键字之一。它的核心作用是告诉编译器&#xff1a;"这个变量可能会在你不知道的情况下被改变"。这种改变可能来自硬件设备、其他线程&#xff0c;甚至是信号处理程序。编译…

作者头像 李华
网站建设 2026/9/23 7:23:01

AI写作工具助力学术论文高效撰写

1. 学术写作的智能化转型去年帮同事老张改职称论文时&#xff0c;他盯着空白文档发呆的样子让我印象深刻。这位临床经验丰富的主治医师&#xff0c;面对学术写作竟像新手司机上了高速——明明满肚子病例素材&#xff0c;却不知如何组织成符合规范的论文。这种困境在工程、教育等…

作者头像 李华