使用 JavaScript 和 p5.js 创建书籍封面

Avatar of Engin Arslan
Engin Arslan

DigitalOcean 为您旅程的每个阶段提供云产品。 立即开始使用 200 美元的免费积分!

我最近出版了一本书和一个名为 Coding for Visual Learners 的互动课程。 它使用广受欢迎的 JavaScript 编程语言和 p5.js 编程库从头开始教授初学者编码。 由于 p5.js 是一个很棒且易于使用的绘图库,因此我想利用它来创建我的 书籍 和课程的封面。 本教程介绍如何使用 JavaScript 和 p5.js 创建此特定视觉效果。

Book Cover using p5.js (circling 1's and 0's around the title)

p5.js 是一个基于素描理念的绘图和创意编码库。 就像素描可以被认为是一种最小化的方法来快速绘制原型一样,p5.js 构建在编写最少代码以将您的视觉、交互或动画创意转换为屏幕的概念之上。 p5.js 是流行库 Processing 的 JavaScript 实现,后者基于 Java 编程语言。

p5.js 的简洁性使其成为一个非常容易学习的库。 但是不要让这种简单性误导你认为 p5.js 功能有限。 p5.js 拥有令人印象深刻的功能、历史和社区,如果你想使用代码创建艺术、设计、运动或互动作品,它将成为一项宝贵的学习投资。 p5.js 程序可以从几行代码到数千行代码不等。

您可以使用 p5.js 构建复杂的 数据可视化

查看 Engin Arslan (@enginarslan) 在 CodePen 上的笔 p5.js 数据可视化

或者,生成艺术

查看 Engin Arslan (@enginarslan) 在 CodePen 上的笔 p5.js 生成艺术

在本教程中,我将假设您具备一些 JavaScript 知识,例如熟悉 if-else 结构、数组等。 如果您想复习 JavaScript 知识或想从头开始学习这种强大的语言,我强烈建议您查看我的书籍和课程 Coding for Visual Learners。 它专为绝对初学者设计。

我还将假设您对计算机图形学有一定的了解,以便诸如坐标系或颜色模式之类的概念不需要过多解释。 但是,如果任何内容不清楚,请随时与我联系,我将尽力澄清。

您可以使用您的操作系统上的 p5.js 编辑器 或使用在线代码编辑器来跟随本教程。 这是一个 ,可以用作 p5.js 项目的模板。

让我们开始吧!

第一部分:p5.js 基础

setup 和 draw 函数

大多数 p5.js 脚本包含两个函数声明,称为 setupdraw。 您将在这两个函数内部编写所有与 p5.js 相关的代码,并且 p5.js 将以特定的方式为您执行这些函数。 了解 p5.js 如何执行这些函数非常重要,因为它们构成了任何 p5.js 程序的基础。

  1. setup 函数是您将在其中编写与程序初始化相关的代码的地方。 setup 函数内部编写的任何内容都会执行一次,并且在程序开始时执行。
  2. draw 函数是您将在其中编写大多数绘图相关功能的地方。 draw 函数在 setup 函数之后执行,并且会连续执行(默认情况下接近每秒 60 次,帧),这使您可以使用此库创建各种交互式和动画视觉效果。

鉴于 drawsetup 函数对于 p5.js 的工作原理至关重要,这是您几乎需要为编写的每个 p5.js 代码使用的样板代码。

查看 Engin Arslan (@enginarslan) 在 CodePen 上的笔 book-cover-step-01

如果您没有明确指示,p5.js 将使用默认大小创建一个画布(您将使用的网页内的绘图区域),这对于您的目的来说可能太小了。 这就是您调用 createCanvas 函数的原因,该函数使用给定的 x 和 y 尺寸显式创建画布。 您还在 draw 函数内部调用 background 函数以便能够为画布设置颜色。 这对于样板目的而言可能不是必需的,但它有助于查看创建的画布。

p5.js 中的颜色和形状

p5.js 中的颜色设置函数(如您在此处使用的 background 函数)使用一个、两个、三个或四个参数。 一个参数使用给定的 R、G 和 B(红色、绿色、蓝色)分量的值创建纯色。 使用单个值调用颜色设置函数与使用相同值作为三个单独的参数调用它相同。 下面的这两个声明将创建相同的结果

background(150);
background(150, 150, 150);

当提供时,第四个参数设置颜色的 alpha(透明度)。 当用作最后一个参数时,第二个参数也控制颜色的 alpha,因此也控制您在其之后绘制的形状的 alpha。 说到形状,让我们创建一个形状。 这是一个使用 rect 函数绘制的矩形。

查看 Engin Arslan (@enginarslan) 在 CodePen 上的笔 book-cover-step-02

rect 函数的前两个参数设置矩形的位置,后两个参数用于设置大小。 请注意,当与参数 00 一起使用时,矩形将绘制到屏幕的左上方。 0, 0 是画布的原点,位于左上角。 默认情况下,矩形形状在 p5.js 中从其左上角绘制。 如果您想更改此行为,您可以使用 p5.js 变量 CENTER 声明一个名为 rectMode 的函数以更改矩形绘制模式。 将此声明放在 setup 函数内部是有意义的,因为您只需要在程序的整个生命周期中执行一次。

查看 Engin Arslan (@enginarslan) 在 CodePen 上的笔 book-cover-step-03

如果您想将此矩形绘制在屏幕中央,您可以选择几种方法。 您可以为其提供值 700/2,以便它使用宽度和高度的一半。 但像这样硬编码值不是良好的编程实践。 您可以尝试在 rect 函数以及 setupdraw 函数之间共享变量。 但幸运的是,由于相对于画布的宽度和高度放置事物是一种常见操作,因此 p5.js 为您提供了一种快捷方式。 您可以使用 p5.js 变量 widthheight 来获取画布的当前宽度和高度。 将这些变量除以二以将其用于矩形的位置。

查看 Engin Arslan (@enginarslan) 在 CodePen 上的笔 book-cover-step-04

到目前为止,您已经了解了如何为背景设置颜色,但还没有看到如何为形状设置颜色。 有两个函数允许您这样做。 其中一个是 fill 函数,它为在此函数声明之后出现的形状设置填充颜色,然后是 stroke 函数,它为笔触设置颜色。

颜色函数的工作方式可能有点不直观,因为它们会影响声明之后出现的形状,而不是之前出现的形状。 为了了解它们是如何工作的,您可以将调用这些函数视为为连续的绘图操作设置活动颜色。

另一个相关的函数是 strokeWidth,它设置形状笔触的宽度。 您将使用所有这三个函数来创建一个具有白色边框的透明矩形。

查看 Engin Arslan (@enginarslan) 在 CodePen 上的笔 book-cover-step-05

您还将在屏幕中央创建文本。 为此,您将使用 text 函数。 text 函数接受 3 个输入,要绘制到屏幕上的文本以及文本放置的 x 和 y 位置。 您还可以使用 textFont 函数为文本设置所需的字体。

查看 Engin Arslan (@enginarslan) 在 CodePen 上的笔 book-cover-step-06

这里有几个地方需要修复。首先,注意文本没有居中对齐。正如你现在可能猜到的那样,有一个函数可以设置文本的对齐方式,称为textAlign函数。你将在setup函数中使用textAlign函数,因为你不会再更改此属性。你将向它提供两个参数:CENTER, CENTER。这将使文本绘制在水平和垂直方向上对齐。

第二件事需要注意的是,文本形状受到上面矩形的填充和描边值的影响,但你可能不想在绘制文本时使用这些值。让我们在绘制文本之前设置新的值以去除以前的值。

查看 Engin Arslan 在 CodePen 上创作的笔 book-cover-step-07@enginarslan)。

为了能够调整描边,你可以使描边颜色透明或将描边粗细设置为 0。这里还使用了一个noStroke函数,它完全去除了描边。在我看来,选择哪一个并不重要,它们在这个用例中都具有相同的效果。

你现在将调整矩形的形状,使其更好地包含此文本。此外,出于易读性的目的,你将使背景变为黑色。

查看 Engin Arslan 在 CodePen 上创作的笔 book-cover-step-08@enginarslan)。

目前,文本和矩形太小了。你已经知道如何放大矩形。为了能够更改文本的大小,你可以使用一个名为textSize的函数。但你将改为使用一个 p5.js 变换函数scale来处理缩放,因为它将允许你以更直接的方式控制矩形和文本的缩放。不幸的是,在使用 p5.js 时,变换并不直观,因此需要进一步解释。

p5.js 变换函数

p5.js 中的变换相对于原点进行。你可以使用一个scale函数,它会按给定量缩放其后的形状,但形状会从原点(屏幕的左上角)开始缩放。这很少是想要的。在处理形状时,通常希望相对于它们的中心点进行缩放。

注意,调用scale函数使所有内容都变大,但也使它们发生位移,因为缩放是相对于左上角进行的。

查看 Engin Arslan 在 CodePen 上创作的笔 book-cover-step-09@enginarslan)。

如果形状位于原点,那么这种缩放操作就可以满足我们的目的。幸运的是,有一种方法可以实现这一点。为此,你应该将scale函数与translate函数一起使用。以下是它的工作原理

查看 Engin Arslan 在 CodePen 上创作的笔 book-cover-step-10@enginarslan)。

你已将矩形和文本的 x 和 y 位置设置为 0。这将形状移动到原点。然后,你调用translate变换函数将形状移动到它们在width/2height/2处的初始位置。这是有效的,因为translate函数实际上并没有移动形状,而是移动了整个坐标系。现在坐标系的原点位于屏幕中央,你可以调用scale函数,它将从此期望点进行缩放。

如果这听起来有点复杂,请放心,你不是一个人。移动整个坐标系来变换一个形状不仅对于你想要实现的目标来说是过度杀伤,而且也可能不切实际,因为它意味着这些形状之后出现的任何其他内容都需要使用这个新变换的坐标系,这会给流程增加很多复杂性。让我们通过绘制一个新形状来看一个例子。

查看 Engin Arslan 在 CodePen 上创作的笔 book-cover-step-10-B@enginarslan)。

如你所见,你现在正在使用ellipse函数在120和0的x和y坐标处绘制一个圆圈。但问题是,即使提供的y值为0,你也可以清楚地看到圆圈不在屏幕顶部,而是在中间,恰好是y轴的新0值。这在大多数情况下都是非常不希望看到的,但幸运的是,p5.js中有一种方法可以解决它。输入pushpop函数!

push & pop

p5.js 的push函数允许你创建一个新状态,而pop函数将状态恢复到之前的条件。这允许你将完全不同的设置应用于各个对象,而不用担心这些设置会影响后续的形状,只要你在pushpop调用之间执行所有操作即可。以下是它的工作原理

查看 Engin Arslan 在 CodePen 上创作的笔 book-cover-step-11@enginarslan)。

完美!你现在正在执行的所有变换更改都保留在pushpop函数调用之间。需要注意的是,你应该始终一起调用pushpop函数。只使用其中一个而不用另一个是没有意义的。既然你已经修复了关于变换的问题,你可以去除圆形。在最终确定屏幕上的文本之前,你将添加的最后一件事是为其添加一点动画。让我们在动画的前 200 帧中稍微放大它,以增加视觉效果的活力。为此,你可以使用frameCount p5.js 变量。frameCount p5.js 变量跟踪在 p5.js 程序的生命周期内draw函数被调用的次数。

查看 Engin Arslan 在 CodePen 上创作的笔 book-cover-step-12@enginarslan)。

以下代码片段添加到程序中。如果frameCount小于 200,则将当前frameCount值的1/400添加到当前比例中。你使用数字 400 的原因是,当 frameCount 为 200 时,你希望将 0.5 添加到总比例中。但这并不是解决此问题的最优雅或最可扩展的方法。

if (frameCount < 200) {
  scale(1.5 + frameCount/400);
} else {
  scale(2);
}

你可以使用的一个 p5.js 函数来推导出这个增量缩放值是map函数。map函数将预期在给定最小值和最大值范围内的给定值映射到所需最小值和最大值范围内的数字。以下声明

map(frameCount, 0, 200, 0, 0.5);

将介于 0 和 200 之间的frameCount变量映射到介于 0 和 0.5 之间的值。让我们在代码中使用它

查看 Engin Arslan 在 CodePen 上创作的笔 book-cover-step-13@enginarslan)。

这好多了。代码也很可扩展。你只需更改scaleFactormaxLimit变量即可调整文本的大小和增长速度。在本演练的下一部分,你将构建最终视觉效果的背景效果,这可以说是此视觉效果中更令人兴奋的部分。但你到目前为止看到的这些部分应该足以让你熟悉 p5.js 的基础知识。

第二部分:创建环形形状

在本教程的第二部分,你将学习如何在背景中创建动画的环形形状。但首先,让我们将文本绘制代码移动到它自己的函数中,这样它就不会占用draw函数中的太多空间。

查看 Engin Arslan 在 CodePen 上创作的笔 book-cover-step-14@enginarslan)。

此更新没有引入任何功能更改,只是一个代码结构更新,使代码更易读。你现在不需要drawTitleText函数,因此可以将其注释掉以专注于背景形状。

你将使用一个 JavaScript 对象来表示屏幕上最终视觉效果的数字。使用对象来创建形状使更容易将形状视为具有特定属性和行为的独立实体。它还有助于将与形状相关的功能封装在形状本身下,这对代码组织很有帮助。

在这里,你正在创建一个名为Num的 JavaScript 构造函数来表示环中使用的数字。它接受几个参数;形状要显示的消息、x、y 位置、旋转和形状的颜色值。你以前没有见过rotate函数,但它与scaletranslate函数非常相似。顾名思义,它通过旋转形状来变换形状。

function Num(msg, x, y, rot, clr) {
    this.x = x;
    this.y = y;
    this.rot = rot;
    this.msg = msg;
    this.color = clr;

    this.render = function() {
        push();
        fill(this.color);
        translate(this.x, this.y);
        rotate(this.rot);
        text(this.msg, 0, 0);
        pop();
    }
}

构造函数有一个render方法,用于处理将形状绘制到屏幕上。你现在可以使用此构造函数初始化此形状对象的实例并将其绘制到屏幕上。

查看 Engin Arslan 在 CodePen 上创作的笔 book-cover-step-15@enginarslan)。

有几件事值得一提。

首先,注意Num函数非常通用。它可以以灵活的方式用于将任何文本呈现到屏幕上。它也很容易使用,因为你可以简单地通过定义所需的旋转值来控制旋转,而不是在外部使用pushpop函数。

其次,请注意在声明函数时如何避免使用某些单词。你可以将构造函数命名为Number而不是Num,但Number是JavaScript中已存在的函数,因此你应该尽量避免使用它。如果你使用Number,事情不一定会崩溃,但通常最好不要造成名称冲突。此外,对于参数,你没有使用rotatecolor作为参数名称,因为这些是内置p5.js函数的名称。如果你要将这些名称用于参数,则将无法在该函数的上下文中使用名称相同的函数。学习如何避免这些名称需要实践。

最后,请注意你如何向数字对象提供90的旋转值,但数字似乎没有旋转90度。这是因为p5.js在执行旋转时默认使用弧度而不是度数。可以通过在setup函数中使用DEGREES变量调用p5.js的angleMode函数,将其设置为使用度数作为默认旋转单位。

angleMode(DEGREES);

现在屏幕上只有一个数字,这很好,但目标是在环形结构中显示多个数字。让我们弄清楚如何对单个数字执行此操作,然后你可以扩展操作以对任意数量的数字进行放置。

查看Engin Arslan在CodePen上创建的笔book-cover-step-16@enginarslan)。

代码量正在增长!这是为创建环形布局添加的代码。

var radius = 100;
var amount = 15;

push();
translate(width / 2, height / 2);

for (var i = 0; i < amount; i++) {
    rotate(i);
    var num = new Num(1, 0 + radius, 0, 90, 255);
    num.render();
}

pop();

这里我们有一个for循环,它创建了给定数量的Num对象,这些对象与当前原点相距给定的radius距离。目前它只创建了15个对象。这是为了说明一个要点。请注意形状周围的间距不均匀。这是因为p5.js中的变换调用(如translaterotatescale)具有累积效应。例如,下面列出的这两个调用将导致x轴上总共平移20像素。

translate(10, 0);
translate(10, 0);

为了解决这个问题,你可以使用pushpop,以便变换效果保持局部化,并且不会累积。这应该可以解决形状间距不均匀的问题。你还应该使形状均匀分布在虚线圆上。为此,你可以将360(圆的总角度)除以你拥有的形状数量,以得出每个形状应旋转多少才能实现均匀分布。

查看Engin Arslan在CodePen上创建的笔book-cover-step-17@enginarslan)。

完美,在下一步中,你将把这个环形绘制操作封装成一个函数。你将使该函数接受radiusamount作为参数。此外,在函数内部,你将生成一个随机数数组,将其馈送到Num对象,以便你可以向对象传递0或1的随机值,而不是始终使用数字1。

为此,你将使用p5.js的random函数。这是一个代码片段,它生成一个介于0和2之间(不包括2)的随机浮点数,该浮点数使用JavaScript的parseInt函数转换为整数,并被推入数组中。

var randomNumbers = [];
for (var i = 0; i <= amount; i++) {
    randomNumbers.push(parseInt(random(2), 10));
}

现在,你可以从这个randomNumbers集合中传递一个项目到Num对象,而不是始终使用数字1。这是整个代码

查看Engin Arslan在CodePen上创建的笔book-cover-step-18@enginarslan)。

现在的问题是p5.js的random函数为每个draw函数调用生成一个随机值,但出于我们的目的,我们希望这些随机值在程序执行期间保持不变。为了能够做到这一点,你应该使用p5.js的randomSeed函数。randomSeed确保你为给定的种子值获得相同的“随机”值。你将使种子值可以从函数外部配置。

查看Engin Arslan在CodePen上创建的笔book-cover-step-19@enginarslan)。

数字不再闪烁了。由于你创建了绘制单个环的函数,因此此时使用循环绘制多个具有不同数量Num对象的环一点也不难。为了保持示例简单,我们使用一个数值常量来使环的密度彼此相似。

查看Engin Arslan在CodePen上创建的笔book-cover-step-19-B@enginarslan)。

你快完成了!事实上,我将在这一点上只显示最终代码,因为它不包含你没有学过的任何内容。

此版本中更新了几件事。

  • 更新了“Coding for Visual Learners Text”的背景,使其半透明。
  • 为环引入了旋转。
  • 为Num对象的颜色也引入了随机性。

查看Engin Arslan在CodePen上创建的笔Coding for Visual Learners – Cover@enginarslan)。

如果你不希望形状动画化,你可以在setup函数中调用名为noLoop的函数,以仅显示动画的第一帧。如果你想调整形状,可以从调整amountspacingradiusrotSpeed参数开始。

我使用静态图像作为我的书封,但使用动画形状为课程创建动画。公平地说,我还使用Photoshop稍微调整了一些元素的位置,但基本形状是用JavaScript和p5.js创建的,这让我作为一个程序员感到高兴。


希望你喜欢这个关于p5.js的简短介绍!如有任何疑问,请随时与我联系!你可以在enginarslan.com找到我的网站,我还在Twitter上@inspiratory