资讯动态

用 p5.js 的 sin() 函数模拟简谐运动:Nature of Code 3.5 完整实战指南

发布时间:2026/10/14 17:07:22 来源:尧图企业网站定制
教程文档示例工程【免费下载链接】website-archiveArchive of the Coding Train website (first version)项目地址https://gitcode.com/gh_mirrors/we/website-archive点击查看免费下载本文基于 Coding Train 网站归档仓库中《Nature of Code》课程 3.5 节「Simple Harmonic Motion简谐运动」系统讲解如何用 p5.js 的sin()函数让物体产生来回摆动、呼吸缩放等周期运动。你将掌握正弦波的视觉直觉、幅值amplitude与周期period的数学含义并通过 6 个可直接运行的示例 sketch学会用map()把三角函数输出映射到画布坐标最终能独立做出呼吸圆、上下弹跳小球乃至双轴 Lissajous 运动。为什么正弦函数的图像是一条波浪简谐运动Simple Harmonic Motion描述的是物体围绕平衡位置做往复运动的现象例如钟摆、弹簧振子。而这一切的数学根源就是三角函数中的sin()。回顾三角函数的定义在单位圆上角度θ对应的点的纵坐标就是sin(θ)。当我们让角度从 0 匀速增加到2π即TWO_PI一个完整圆周sin(θ)的输出会在-1 到 1之间平滑地往返变化画出来就是一条经典的波浪线。这正是视频开头「Why does a graph of sine look like a wave?」要回答的问题——正弦波本质上就是匀速旋转的圆在纵轴上的投影。在 p5.js 中sin()接收的参数单位是弧度radians一个完整周期对应TWO_PI约 6.28318。如果你需要回顾弧度制与极坐标下的 sin/cos/tan 关系可以阅读同课程的 3.4 Polar Coordinates 讲义关于角度与向量旋转的内容可参考 3.3 Angles and Vectors 讲义。第一个实战让圆「呼吸」起来本节视频的第一个代码示例variations 中的Basic实现了一个随着正弦值缩放半径的呼吸圆。核心思路用一个随时间递增的angle变量驱动sin(angle)再用map()将-1~1的正弦值映射为0~200的半径圆就会像呼吸一样放大缩小。// learning/nature-of-code/3.5-simple-harmonic-motion/basic/sketch.js let angle 0; function setup() { createCanvas(400, 400); } function draw() { background(0); translate(200, 200); fill(252, 238, 33); let r map(sin(angle), -1, 1, 0, 200); circle(0, 0, r * 2); angle 0.02; }逐行拆解这段代码angle 0.02每帧让角度增加一个固定小量相当于「匀速旋转」这是简谐运动的驱动器sin(angle)输出在-1与1之间振荡map(value, start1, stop1, start2, stop2)p5.js 的线性映射函数这里把-1~1映射为0~200即半径的最小值 0、最大值 200circle(0, 0, r * 2)p5.js 的circle(x, y, diameter)第三个参数是直径所以半径r要乘以 2。如果对map()的完整参数语义还不熟悉可以查看仓库中的 The map() Function 讲义其中详细介绍了映射公式与多参数用法。所有 6 个变体示例的 HTML 页面均通过 CDN 引入 p5.js 1.4.1结构完全一致以 main/index.html 为例script srchttps://cdn.jsdelivr.net/npm/p51.4.1/lib/p5.min.js/script script srcsketch.js/script幅值与周期控制呼吸的深度与快慢视频在 4:24 处专门讲解了幅值Amplitude与周期Period它们是简谐运动的两个核心参数参数含义在代码中的体现幅值 Amplitude振荡偏离平衡位置的最大距离map()的目标区间范围如0~200中的 200周期 Period完成一次完整往返所需的时间/帧数angle每次增加的量增量越小周期越长频率 Frequency单位时间内完成的周期数f 1 / Period周期的倒数在 Basic 示例中angle每帧增加0.02那么经过TWO_PI / 0.02 ≈ 314帧才走完一个完整周期。改变增量就能直接改变呼吸的快慢——增量越大周期越短频率越高。让周期约为 1 秒频率与增量换算视频在 7:12 提出一个有趣的问题能不能让呼吸周期约等于 1 秒对应变体Approximate Time。假设运行在 60 FPS一帧约为 1/60 秒要让周期接近 1 秒就应让角度在 60 帧内走完TWO_PI于是增量取TWO_PI / 60// learning/nature-of-code/3.5-simple-harmonic-motion/approximate-time/sketch.js let angle 0; function setup() { createCanvas(400, 400); } function draw() { background(0); translate(200, 200); fill(252, 238, 33); let r map(sin(angle), -1, 1, 0, 200); circle(0, 0, r * 2); let increment TWO_PI / 60; angle increment; console.log(frameRate()); }代码末尾用console.log(frameRate())输出当前帧率用于验证 60 FPS 假设是否成立。需要注意的是这只是近似方案它依赖帧率恒定而实际浏览器帧率会波动。当帧率不是 60 时「每秒走完一个周期」的目标就不再精确。精确计时基于 millis() 的帧率无关周期为了彻底摆脱帧率影响变体Exact Time Exercise改用millis()自 sketch 启动以来的毫秒数直接驱动角度用取模运算把当前时间切进一个指定周期内// learning/nature-of-code/3.5-simple-harmonic-motion/exact-time-exercise/sketch.js let angle 0; let angleV 0; let saveTime 0; let period 1000; // 周期1000 毫秒 1 秒 function setup() { createCanvas(windowWidth, windowHeight); } function draw() { background(0); translate(width / 2, height / 3); fill(252, 238, 33); angle (TWO_PI * (millis() % period)) / period; let r map(sin(angle), -1, 1, 0, height / 3); circle(0, 0, r * 2); fill(255); textFont(Courier); textSize(64); textAlign(CENTER, CENTER); let seconds floor(millis() / 1000); let sec floor(seconds % 60); let min floor(seconds / 60); text(${nf(min, 2)}:${nf(sec, 2)}, 0, height / 2); }这段代码的两个关键点angle (TWO_PI * (millis() % period)) / period让角度在period这里是 1000 毫秒内从 0 线性走到TWO_PI周期被硬编码为恰好 1 秒与帧率完全无关。修改period即可得到任意精确周期画布右下角用nf(min, 2):nf(sec, 2)显示运行计时器方便肉眼验证圆的呼吸周期确实等于 1 秒。这是「用帧率近似」与「用真实时间精确」两种做法的对比也是从模拟走向真实时间驱动的关键一步。玩转角速度加速的呼吸Main变体Main引入角速度angular velocity概念不再直接给angle加固定增量而是维护一个angleV让角速度本身也随时间增长于是呼吸会越变越快呈现出加速度的视觉效果// learning/nature-of-code/3.5-simple-harmonic-motion/main/sketch.js let angle 0; let angleV 0; function setup() { createCanvas(400, 400); } function draw() { background(0); translate(200, 200); fill(252, 238, 33); let r map(sin(angle), -1, 1, 0, 200); circle(0, 0, r * 2); // let increment TWO_PI / 60; angle angleV; angleV 0.0001; // console.log(frameRate()); }对照基本版这里的驱动方式变为两层递进angle angleV角度每帧增加的量由角速度决定angleV 0.0001角速度每帧再增加一个微小量即角加速度。注释中还保留了上一节let increment TWO_PI / 60的写法方便与 Basic / Approximate Time 两个版本对照。视频在 8:13 讲解频率时正是借助这种结构说明当角速度递增时单位时间内的振荡次数频率也在持续增加。把 sin 映射到 y 坐标上下弹跳的小球Yoyo前面几个版本都是把sin()映射为圆的半径缩放运动。视频在 10:00 处的Yoyo变体则把sin()映射到y 坐标让小球像悠悠球一样沿竖直方向来回运动// learning/nature-of-code/3.5-simple-harmonic-motion/yoyo/sketch.js let angle 0; let angleV 0.05; function setup() { createCanvas(600, 400); } function draw() { background(0); translate(300, 200); fill(252, 238, 33); stroke(252, 238, 33); let y map(sin(angle), -1, 1, -200, 200); strokeWeight(4); line(0, 0, 0, y); circle(0, y, 32); angle angleV; }要点说明map(sin(angle), -1, 1, -200, 200)把正弦值映射为-200~200的纵向偏移translate(300, 200)之后 y 就在画布中部上下 200 像素范围内往返line(0, 0, 0, y)绘制一条从平衡位置指向小球的竖线直观展示位移量angleV 0.05角速度比基本版大运动节奏更快这里的sin()输出的正负号直接决定小球位于平衡位置上方负值对应画布上方还是下方。从这个版本可以清楚看到简谐运动的位移是时间的正弦函数map()则是把数学上的[-1, 1]区间翻译成屏幕上任意像素范围的桥梁。二维拓展双轴简谐运动与 Lissajous 曲线2-axes Exercise最后一个变体2-axes Exercise把简谐运动推广到两个轴x 由cos(angle1)驱动y 由sin(angle2)驱动且两个角度以不同速度递增。当 x、y 两个方向上的频率之比为整数时轨迹会形成被称为Lissajous 曲线的闭合图案// learning/nature-of-code/3.5-simple-harmonic-motion/2-axes-exercise/sketch.js let angle1 0; let angle2 0; let angleV1 0.07; let angleV2 0.03; function setup() { createCanvas(windowWidth, windowHeight); background(0); } function draw() { background(0, 5); // 半透明背景产生拖影 translate(width / 2, height / 2); fill(252, 238, 33, 255); stroke(252, 238, 33, 200); let ampx (0.9 * width) / 2; let ampy (0.9 * height) / 2; let x map(cos(angle1), -1, 1, -ampx, ampx); let y map(sin(angle2), -1, 1, -ampy, ampy); strokeWeight(4); line(0, 0, x, y); circle(x, y, 32); angle1 angleV1; angle2 angleV2; }该示例的实现细节background(0, 5)用带透明度的背景刷新让每一帧轨迹残留形成流光拖影x、y 的幅值ampx、ampy分别取自画布宽高的 90%让轨迹铺满屏幕两轴角速度0.07与0.03之比为7:3这正是形成漂亮 Lissajous 闭合图案的关键想要深入探索这种二维合成运动可以参考仓库中的 Coding Challenge #116 Lissajous Curve Table它以表格形式同时绘制多组不同频率比的 Lissajous 图案。运行方式与后续延伸本节的 6 个变体示例都存放在仓库目录 learning/nature-of-code/3.5-simple-harmonic-motion 下每个子目录basic、approximate-time、exact-time-exercise、main、yoyo、2-axes-exercise都包含一个index.html与一个sketch.js。直接在浏览器中打开index.html即可运行无需本地安装任何依赖p5.js 1.4.1 由 CDN 提供。如果你想继续深入这个主题仓库中的后续课程是天然的延伸路径3.6 Graphing Sine Wave 讲义把正弦值逐点绘制成真正的波浪线并尝试改变周期与相位3.7 Additive Waves 讲义封装 Wave 类并叠加多条正弦波实现更复杂的波形合成课程讲义主页见 _learning/nature-of-code/index.md。此外视频结尾还向社区征集创意Ideas! What will you create?原讲义中记录了社区提交的多个衍生作品如 sin/cos 可视化、旋转小球、彩虹生成器、悠悠球特效等你也可以在掌握上述 6 个基础示例后把简谐运动组合进粒子系统、摆锤模拟或自己的动画项目中。小结简谐运动的四步套路纵观全部 6 个变体用 p5.js 实现简谐运动的流程可以抽象为固定套路维护角度声明angle以及可选的角速度angleV每帧递增计算正弦值sin(angle)得到-1~1的振荡值需要横向运动时改用cos(angle)映射到画布用map()把-1~1映射为像素坐标或尺寸区间即设定幅值驱动渲染把映射结果用于circle()、line()或translate()并配合背景刷新呈现动画。若需要精确的物理周期则用millis() % period替代帧计数。掌握了这一套模式你就拥有了在 p5.js 中制造一切周期性运动的通用工具箱。赞分享教程文档示例工程【免费下载链接】website-archiveArchive of the Coding Train website (first version)项目地址https://gitcode.com/gh_mirrors/we/website-archive点击查看免费下载相关推荐The Nature of Code p5.js 示例项目教程The Nature of Code p5.js 示例项目教程 1. 项目介绍 The Nature of Code p5.js 是一个开源项目旨在将《The示例工程文档/教程前端Nature of Code 5.18用 p5.js 与 matter.js 搭建 2D 物理世界Coding Train 仓库实战Nature of Code 5.18用 p5.js 与 matter.js 搭建 2D 物理世界Coding Train 仓库实战 本篇文章以 Codi教程文档示例工程p5.js 函数参数与实参用参数化函数实现代码复用Code! Programming with p5.js 第 5.2 章实战p5.js 函数参数与实参用参数化函数实现代码复用Code! Programming with p5.js 第 5.2 章实战 本指南围绕 Coding教程文档示例工程创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑