跟着本教程,一边学习 p5.js 中变量和创建运动的基础知识,一边制作一个动态风景。
跟随本教程,你将学习:
- 在p5.js草图中声明、初始化和更新变量
- 在p5.js的图形函数中使用变量、运算符和 random(),在画布上创建运动效果
- 在p5.js项目中同时添加线性运动和随机运动
学习本教程之前,你应该已经学过:
在开始之前,你应该能够:
- 登录 p5.js网页编辑器并保存一个新项目
- 更改画布的尺寸和背景颜色
- 添加并自定义形状和文字
- 使用
mouseX和mouseY添加简单的交互效果 - 为代码添加注释
- 阅读并解决 错误信息
第1步:选择从哪里开始
登录p5.js网页编辑器然后在以下选项中选择一项:
- 如果你已经完成了之前的 入门指南:
- 如果你想从头开始:
点击 p5.js Web Editor 中的 Play,并勾选“Auto-refresh”旁边的复选框。这样当你继续为项目添加代码时,画布会持续更新。勾选后,每次修改草图都不需要再按一次 Play 按钮。
如果你复制了这个模板,你的代码应如下所示:
代码行 为了让这段文字始终可见,请确保这段代码位于 别忘了: 你的程序最后一行应该是 text(`${mouseX}, ${mouseY}`, 20, 20);会把鼠标指针的 x 与 y 坐标以坐标对 x, y 的形式显示出来。第一个数字是变量mouseX的值,表示鼠标指针在画布上移动时的 x 坐标;第二个数字是变量mouseY的值,表示鼠标指针的 y 坐标。draw() 的最后几行。如果文字和背景混在一起,你可能需要修改 fill() 的值来更改文字颜色。当你不再需要这些坐标时,在 fill() 和 text() 函数前加上 //。这会把这些代码行变成注释,从而让程序跳过它们!}(右花括号),用于结束 draw() 函数代码块。
变量
变量用于存储可在草图中使用的值。 当你要在草图中添加会随时间变化的元素时,变量非常有帮助。 它们可用于计算、文本信息、函数参数等等!
mouseX和mouseY是 p5.js 库内置的变量。它们会在鼠标指针拖动经过画布时,存储其 x 和 y 坐标。在开始教程中,你创建了一个交互式风景,其中把mouseX和mouseY用作瓢虫或其他表情符号的 x、y 坐标。这样一来,表情符号就能跟随鼠标在画布上移动,让你的作品具有交互性!
在上面的模板中,你使用mouseX和mouseY配合text()函数在画布上打印鼠标的 x、y 坐标。变量可以通过text()函数和字符串插值(string interpolation)(示例 2)与文本一起显示在画布上。
字符串插值(string interpolation)
在开始中你学到,字符串是始终用引号("")包裹的数据类型。要把变量和字符串结合使用,可以借助模板字面量(template literals)! 模板字面量使用反引号 (`) 而不是引号 ("") 作为开始和结束。你可以在反引号之间输入任意字符来生成字符串,就像这个示例。你还可以通过 $ 占位符把变量放进字符串中,即在花括号内写入变量名,就像这个示例。
想了解更多,请查看string interpolation(示例2),template literals,或 p5.js 关于string的参考页面! 存储了数字的变量可以在需要数字参数的地方使用。如果把存储了string的变量用在本应传入数字的位置,控制台会显示类似 “...was expecting Number for the first parameter, received string instead.(...第一个参数应为数字,却接收到了字符串。)” 的错误信息。请查看调试实用指南中的“错误信息”部分,了解一些常见错误及其修复方法!
第2步:创建背景风景
- 为背景设置颜色。
- 添加并为风景中的形状上色(例如太阳或月亮、山脉、建筑、房屋、树木等)。
- 添加注释,说明每一部分代码的作用。
有关如何在画布上使用颜色和形状的更多信息,请参见 Get Started 中的第 4–6 步。
你的代码可以如下所示:
上述代码使用 circle() 和 triangle() 在风景中创建对象,并使用 fill() 和 stroke() 为形状及其轮廓上色。
background()用于更改画布背景颜色。draw()会不断重复执行其中的代码。这意味着如果多个形状位置接近,后绘制的形状会覆盖先绘制的形状。- 弯月是通过重叠两个
circle()形状创建的。- 这个示例 使用两个重叠的圆来创建弯月效果。
- 山脉是通过重叠两个
triangle()形状创建的(见下图)。- 这个示例 展示了如何使用重叠的三角形创建更有细节的山脉。
- 弯月是通过重叠两个
triangle()需要提供画布上 3 个点的位置才能显示。每个点都包含一个 x 坐标和一个 y 坐标。前两个数字是第一个点的坐标 (x₁, y₁),接下来的两个数字是第二个点的坐标 (x₂, y₂),最后两个数字是第三个点的坐标 (x₃, y₃)。

一个标注图,显示三角形三个顶点的坐标:(x1, y1)、(x2, y2) 和 (x3, y3)。函数语法为:triangle(x1, y1, x2, y2, x3, y3);
你的风景作品可能与上面的示例代码非常不同。你可以在作品中使用以下任意形状(点击链接了解更多):
rect() | triangle() | ellipse() | circle() | line() | square() | quad() | point() | arc()
你也可以参考以下资源,进一步了解如何在项目中使用形状和颜色:
fill() | stroke() | background() | draw()
请务必避免 调试指南 中提到的常见错误,以及 The Coding Train 的这段视频中提到的问题。
第3步:使用自定义变量绘制形状
使用
ellipse()方法在画布天空中绘制一朵白云。在草地代码的正下方添加以下代码:
// 云朵 fill(255); ellipse(50, 50, 80, 40);
创建一个名为
cloudOneX的自定义变量,并将数字 50 存储在其中——该变量将在整个程序中用于存储白云的 x 坐标值。在
setup()之前添加以下代码:// 云朵 x 坐标的自定义变量 let cloudOneX = 50;
将
ellipse(50, 50, 80, 40);中的 x 坐标替换为cloudOneX变量。该行代码应修改为:
ellipse(cloudOneX, 50, 80, 40);
你的代码可以如下所示:
ellipse() 需要 4 个数字才能在画布上显示。前两个数字 (x, y) 是椭圆中心点的坐标。后两个数字分别表示椭圆的宽度和高度(单位为像素)。

一个椭圆示意图,标注了宽度、高度,以及中心点 (x, y)。函数语法为:ellipse(x, y, width, height);
在上述代码中,云朵使用 ellipse() 绘制,其 x 坐标为 cloudOneX(存储值为 50),y 坐标为 50,宽度为 80 像素,高度为 40 像素。
自定义变量
自定义变量用于存储可以在之后发生变化的值,例如 numbers 或 strings。由于自定义变量存储的是可变的值,我们可以利用它们来改变画布上形状的 x 坐标、y 坐标或大小。当形状的 x 或 y 坐标发生变化时,它看起来就像在移动。在本步骤中:
- 你使用
ellipse(),将数字 50 作为 x 坐标,在画布上放置了一朵白云; - 你在
setup();之前*声明(declare)*了一个名为cloudOneX的自定义变量;- 当你想在程序中创建自定义变量时,必须为变量命名,并使用关键字
let来声明它。 - 变量名可以是任意名称;但最好使用能够帮助你记住变量用途的名称。
- 当你想在程序中创建自定义变量时,必须为变量命名,并使用关键字
- 你通过将数字 50 赋值给
cloudOneX来*初始化(initialize)*该变量;- 使用赋值运算符(
=)可以将一个值存储到变量中; - 当一个值第一次被存入自定义变量时,这个过程称为初始化变量。
- 使用赋值运算符(

一个示意图,标注了 JavaScript 中声明和初始化变量的语法结构:“let” 被标注为“声明自定义变量的关键字”,“variable name” 被标注为“名称”,“=” 被标注为“赋值运算符”,用于“将值赋给变量名”,“value” 被标注为“任意数据类型,例如数字或字符串”。
最后,你可以将变量名 cloudOneX 作为 ellipse() 中 x 坐标的参数。由于变量 cloudOneX 中存储的是数字 50,因此在任何需要数字参数的函数中都可以使用它。在这里,你通过将原本的数字 50 替换为变量名 cloudOneX,把它作为白云的 x 坐标: cloudOneX: ellipse(cloudOneX, 50, 80, 40);.
变量作用域
变量的作用域(scope)描述了变量在程序中可以被使用的位置。通常,将自定义变量声明在 setup() 和 draw() 之外是很有用的,因为这样变量就具有全局作用域(global scope)。具有全局作用域的变量可以在程序的任何位置使用。全局变量通常声明在代码的最前面几行,这样可以帮助程序员清楚地了解哪些值会发生变化,使代码更易维护,并减少后续的混乱。
内置变量,例如 mouseX、mouseY、width 和 height,不需要声明,因为它们已经内置于 p5.js 库中。它们具有全局作用域,因此可以在代码中的任何位置使用。
在其他函数内部声明的变量(例如在 draw() 或 setup() 内部)具有局部作用域(local scope)。这意味着它们只能在声明它们的代码块或函数内部使用。在 setup() 中声明的变量不能在 draw() 或其他函数中使用;同样,在 draw() 中声明的变量也不能在 setup() 中使用。你可以查看这个示例来了解全局变量和局部变量的区别。
要进一步了解如何声明、初始化和使用自定义变量,可以参考以下 p5.js 页面:let、numbers和strings。
使用变量实现动画
当画布上某个形状的 x 或 y 坐标发生变化时,它看起来就像在移动。我们可以使用变量来代替画布上任意对象的 x 或 y 坐标。在示例中:
- 修改
cloudOneX中存储的值(即更改初始化时赋给它的数值),观察画布上白云的变化;- 如果
cloudOneX的值增大,白云会向右移动; - 如果
cloudOneX的值减小,白云会向左移动。
- 如果
在下一步中,我们将改变 cloudOneX 的值,使云朵在画布上水平移动。
第4步:添加水平运动,
在白云代码的下一行添加以下代码:
// 将 x 坐标设置为帧计数 // 在左边缘重置 cloudOneX = frameCount % width
你的代码可以如下所示:
在上一步中,你将 cloudOneX 设置为 frameCount % width。请记住,draw() 会在循环中不断重复执行:
frameCount是一个内置变量,用于记录draw()被执行的次数。只要程序持续运行,这个数值就会不断增加。- 你可以查看这个示例,观察
frameCount中存储的数值变化。
- 你可以查看这个示例,观察
width是一个内置变量,用于存储在createCanvas()中定义的画布宽度。在本示例中,width为 400,height也为 400。%是取余运算符(也称为模运算符)。它将左侧的数字(frameCount)除以右侧的数字(400),并返回余数。请参考下表,了解程序运行过程中frameCount和frameCount % width的变化情况(当width为 400 时):
|
|
|---|---|
0 | 0 |
100 | 100 |
300 | 300 |
400 | 0 |
500 | 100 |
700 | 300 |
800 | 0 |
- 对于
cloudOneX = frameCount % width:- 当
frameCount小于 400 时,frameCount % width会返回frameCount当前的值。- 例如:当
frameCount为 40 时,frameCount % width会返回 40,并将其存储到cloudOneX中。这会将白云移动到 x 坐标为 40 的位置。
- 例如:当
- 随着
frameCount的增加,新的数值会不断存入cloudOneX,白云看起来会向右移动(x 坐标变大)。 - 当
frameCount等于width时,frameCount % width会返回 0。这会将云朵的位置重置为 x 坐标 0——即画布的最左侧。 - 当
frameCount大于width时,frameCount % width会再次返回frameCount的余数值。- 例如:当
frameCount为 440 时,frameCount % width会返回 40,并将其存储到cloudOneX中。
- 例如:当
- 每当
frameCount达到width的整数倍时,frameCount % width都会等于 0,看起来就像云朵重新回到了画布最左侧(x 坐标为 0)。
- 当
当程序运行时:
cloudOneX被声明并初始化为 50;setup()执行,创建一个宽度为width400 像素的画布;- 第一次执行
draw()时:- 背景和所有风景形状被绘制在画布上;
- 云朵以 x 坐标
cloudOneX(存储值为 50)绘制; cloudOneX更新为frameCount % width的余数;
- 第二次执行
draw()时:- 背景和风景再次绘制,覆盖之前的形状;
- 使用更新后的
cloudOneX值重新绘制云朵,从而产生云朵从 x=0 移动到 x=400 后再重置的视觉效果; - 这个过程会持续进行,直到
draw()被停止。
你可以查看这个示例,它展示了 frameCount 和 frameCount % width 的数值变化,以及形状在画布上移动的过程。如需了解更多关于取余运算符的信息,请访问 MDN 上的 Remainder 参考页面。
动画与 draw()
draw() 函数会反复执行代码,在制作一系列静态画面动画时,它的工作方式类似于翻页动画书(flipbook)。
每当 draw() 读取背景和风景形状的代码时,它都会覆盖上一次 draw() 运行时绘制在画布上的内容。当画布上的内容在每次运行时发生变化,就会产生类似翻到动画书下一页的效果。
- 这个示例 展示了圆形在画布上水平移动时,其 x 坐标的变化。
- 这个示例 展示了圆形在画布上垂直移动时,其 y 坐标的变化。
- 这个示例 展示了程序运行过程中圆形大小的变化。
- 以下示例展示了每次
draw()运行时,如何在新位置绘制新的形状:
水平运动 | 垂直运动 | 随机运动- 在这些示例中,移除了
background(),以消除draw()所带来的“翻页动画”效果。由于背景不再覆盖之前的内容,我们可以看到每一次绘制的新形状。
- 在这些示例中,移除了
如需更多信息,请访问 p5.js 关于 draw() 的参考页面。
frameRate()、frameCount 与 console.log()
draw() 的执行次数会存储在变量 frameCount 中,而每秒执行的次数称为帧率(frame rate)。默认情况下,帧率由你的计算机决定,大多数电脑约为 60。这意味着 draw() 中的代码大约每秒运行 60 次。
我们可以使用 frameRate() 函数来设置或显示 draw() 的帧率。还可以使用 console.log() 在控制台中输出帧率和 frameCount 的值,以便查看它们的变化。
更多信息请访问以下 p5.js 参考页面:
frameRate() | frameCount | console.log()
第 5 步:添加更多移动的云朵并调整帧率
- 通过将帧率设置为 15 来让动画变慢:
- 在
background()下一行添加:frameRate(15); //set frame rate to 15
- 在
- 重复 第 4 步,为你的风景添加更多云朵,并使用不同的 x、y 值。
你可以在第一朵云的下方添加以下代码:
ellipse(cloudOneX - 40, 100, 60, 20); ellipse(cloudOneX + 20, 150, 40, 10);
你的代码可以如下所示:
在上一步中,你新增了:
- 第二朵云:x 坐标为
cloudOneX - 40,y 坐标为100- 这会在第一朵云的左侧 40 像素、下方 50 像素的位置绘制第二朵云。
- 第三朵云:x 坐标为
cloudOneX + 20,y 坐标为150- 这会在第一朵云的右侧 20 像素、下方 100 像素的位置绘制第三朵云。
这里,我们将 cloudOneX 作为一个参考点(reference point)——在将多个会一起移动的形状放置到画布上时,用作定位的基准点。
- 这个示例 展示了如何使用参考点 (x, y) 来一次性改变多个形状的位置。
第6步:添加垂直运动
在风景中添加一棵树(或其他物体),使其在垂直方向(向上或向下)移动。
在画布上绘制一棵树。
在绘制云朵的代码与更新变量的代码之间添加以下内容:
// 生长的树 // 树干 fill("rgb(118,80,72)"); rect(40, 270, 15, 50); // 树叶 fill("green"); triangle(25, 270, 45, 240, 70, 270);
将绘制树叶的三角形顶部顶点的值(240)减去
frameCount % 290:修改上方三角形中 y2 的值,使其减去
frameCount % 290:// 树叶 fill("green"); triangle(25, 270, 45, 240 - frameCount % 290, 70, 270);
添加更多以相同方式生长的树。
在第一棵树的下方添加以下代码:
// 树干 fill("rgb(118,80,72)"); rect(340, 330, 15, 50); // 树叶 fill("green"); triangle(325, 330, 345, 240 - frameCount % 290, 370, 330);
你的代码可以如下所示:
在本步骤中,你:
- 添加了一棵由矩形(树干)和三角形(树叶)组成的树;
- 修改了三角形顶部顶点的 y 坐标(
y2),使其减去frameCount % 290(可参考triangle()的示意图回顾y2的位置):- 因为你希望树叶“生长”,三角形的顶部顶点需要在每次
draw()运行时向上移动。正如这个示例所示,在画布上向上移动意味着 y 坐标(y2)减小。 - 为了实现这种运动,你将
y2修改为240 - frameCount % 290。这会根据frameCount除以 290 的余数,逐步减小y2的值。 - 正如在第 5 步中所看到的,每当
draw()运行一次,frameCount % 290的值就会增加 1,因此y2也随之变化:- 在
draw()首次运行之前,y2为 240; - 第一次运行时,
frameCount % 290的值为 1,y2变为 239; - 第二次运行时,值为 2,
y2变为 238; - 第三次运行时,值为 3,
y2变为 237; - 当
frameCount变为 290 的倍数时,frameCount % 290的值为 0,y2会恢复为原始值 240; - 这个过程会持续进行,直到
draw()被停止。
- 在
- 因为你希望树叶“生长”,三角形的顶部顶点需要在每次
更多信息请访问以下 p5.js 参考页面:
frameRate() | frameCount | triangle()
第7步: 加上随机运动
添加会在天空中随机位置出现的流星。
声明两个变量
lineXone和lineYone,并将他们初始化为0在setup()前面添加以下代码:// 用于流星的自定义变量 let lineXone = 0; let lineYone = 0;
在
frameRate(15)下方添加以下内容来画一条表示流星的线:// 流星 stroke("yellow"); line(lineXone, lineYone, lineXone + 30, lineYone - 30);在将
cloudOneX赋值为frameCount % width的那行代码后,添加以下文本,将lineXone和lineYone设置为随机值:// 将流星设置为随机位置 lineXone = random(0, width); lineYone = random(0, height/2);删除
draw()函数底部用于显示鼠标指针 x 和 y 坐标的代码行,以查看你的最终项目效果。与你的朋友分享!
你的代码应该看起来像这样:
在上一步中,你创建了两个新变量:lineXone 和 lineYone,用于在天空中绘制一条代表流星的线。这条线使用 line() 函数绘制(见下图)。

一条线段的示意图,端点为 (x1, y1) 和 (x2, y2)。函数语法为:line(x1, y1, x2, y2);
line() 需要 4 个数字才能在画布上显示。前两个数字是第一个点的坐标 (x1, y1)——其中 x1 是 x 坐标,y1 是 y 坐标。后两个数字是第二个点的坐标 (x2, y2),其中 x2 是 x 坐标,y2 是 y 坐标。
如需了解更多信息,请访问 p5.js 关于 line() 的参考页面。
在本项目中,你绘制了一条从点 (lineXone, lineYone) 开始的线,其中 x1 是存储在 lineXone 中的值,y1 是存储在 lineYone 中的值。该线段结束于点 (lineXone + 30, lineYone - 30),其中 x2 比 x1 向右 30 像素,y2 比 y1 向上 30 像素。
通过使用 x1、y1 来计算 x2、y2 的值,线段的两个端点可以以相同方式移动。这样,当 lineXone 和 lineYone 变化时,整条线可以在画布上移动,而形状保持不变。
random() 用于生成指定最小值和最大值之间的随机数。你使用它重新赋值 lineXone 和 lineYone,使它们变为随机数,从而将流星放置在天空中的随机位置。
使用 random() 的示例:
如需了解随机数生成的更多信息,请访问 p5.js 关于 random() 的参考页面。
当 draw() 运行时:
- 设置背景;
- 将流星和其他风景元素一起绘制到画布上,并更新
cloudOneX; - 将
lineXone重新赋值为0到width之间的随机数(确保它始终出现在画布范围内); - 将
lineYone重新赋值为0到height/ 2之间的随机数;height/ 2表示流星只会出现在画布的上半部分。
当 draw() 再次运行时,同样的过程会重复执行,流星会出现在画布上半部分的另一个随机位置。这个过程会持续进行,直到 draw() 被停止。
错误
p5.js 会通过控制台告知程序员哪些代码行它无法理解,这些信息称为错误信息。程序员会利用这些信息来定位并修复代码中的“bug”。想了解更多,请阅读调试实用指南或观看这个视频!