如何使用 JavaScript 和 Canvas 创建星形图案

共 8575字,需浏览 18分钟

 ·

2021-10-02 17:09


英文 | https://javascript.plainenglish.io/how-to-create-a-star-pattern-using-javascript-and-canvas-322f8af61ce1

翻译 | 杨小二


在本教程中,我们将使用 JavaScript 和 HTML5 Canvas API 创建下图横幅的星形图案。

因为即使创建一颗星也需要多行代码,所以我们将使用 drawJS——我构建的一个处理星形渲染的迷你库。

在此过程中,我们将使用对象字面量、嵌套的 for 循环和余数运算符来创建这种模式——以及一个 CSS 媒体查询来使横幅在窄屏幕宽度下调整大小。

使用 drawJS 的星形图案演示地址:https://codepen.io/nevkatz/pen/jOmexaK

drawStar 方法

图案是通过为图案中的每个星星调用 drawJS 库中的 drawStar 方法来创建的。假设我们只想创建图案中的第一颗星,如下所示。

在这种情况下,我们可以使用下面的代码调用一次 drawStar。

drawJS.drawStar({    cx:50,    cy:50,    numPoints:5,    stroke:'#622569',    fill:'#b8a9c9',    lineWidth:4,    innerRadius: 20,    outerRadius:35   });

现在,由于我们想要创建多个,因此,我们最终将编写一些更复杂的 JavaScript。但首先,让我们编写 HTML 和 CSS 并思考模式的特征。

打好基础

让我们从一些标记和样式开始。

HTML 和 CSS

HTML中的<canvas> 元素。

<canvas id="myCanvas" width="1300" height="600"></canvas>

我们的第一个 CSS 将使其成为块元素并将其居中。

canvas {  display: block;  margin: 10px auto;}

为了使画布具有响应性,我们需要添加一个媒体查询,它将在屏幕宽度低于 1200 像素时将宽度设置为 100%。

我们还可以使用height: auto 来帮助我们的画布在调整大小时保持其纵横比。

@media screen and (max-width: 1200px) {  canvas {    width: 100%;    height: auto;   }}

规划模式

在我们开始编写最初的 JavaScript 之前,让我们再次浏览一下星形模式并考虑制作它需要什么。

下面是模式的前两行。

要以这种方式排列星星,我们需要编写设置几个值的逻辑:

  • 将星星隔开

  • 连续的星星数

  • 行数

我们的程序还需要设置每颗星星的独特属性,包括以下内容:

  • 点数

  • 描边颜色

  • 填充颜色

星星还有一些共同的特性:

  • 边界宽度

  • 内半径,或恒星中心到每个角的距离

  • 外半径,或从恒星中心到每个外点的距离

最后,我们需要设置以下内容:

  • 第一颗星的位置

  • 笔触和填充颜色的调色板(每种四种颜色)

  • 最小和最大星点数

由于这是一种模式,因此应该定期重复变化的星形属性。例如,你会注意到点数从 5 开始,然后从一颗星到下一颗增加 1,直到达到 12 点。一旦发生这种情况,下一位明星将获得 5 分。

这些是我们需要转换为代码的模式属性。

设置模式属性

现在我们已经概述了模式的要求,让我们卷起袖子,用两个函数编写它的逻辑:

  • getPatternProps,它建立星形图案的属性。

  • init,它将遍历每个星星的位置,设置每个星星的属性,并调用 drawJS.drawStar 方法。

getPatternProps 函数将返回一个包含我们模式属性的对象。

function getPatternProps() {     let patternProps = {        start: {            x: 50,            y: 50,        },        numPoints: {            min: 5,            max: 12        },        space: {            x: 100,            y: 100        },        count: {            x: 12,            y: 6        },        fills: ['#b8a9c9', '#98d1d6', '#fed8b1', '#cff7d5'],        strokes: ['#622569', '#4C858A', '#f06d06', '#008000']    };  return patternProps;}

让我们来看看发生了什么:

  • start 将第一颗星的 x 和 y 坐标设置为 (50,50)。

start: { x:50, y:50 }
  • numPoints 将使星星可以拥有的最少点数设为 5,并将最大点数设置为 12。

numPoints: { min:5, max:12 }
  • space 将使星星在垂直和水平方向上相距 100 像素。

space: {x: 100, y: 100 }
  • count 告诉我们在一行 (x) 和六行 (y) 中有 12 颗星。

count: { x: 12, y: 6}
  • fills 将确定作为重复序列出现的四种填充颜色。

fills: ['#b8a9c9', '#98d1d6', '#fed8b1', '#cff7d5']
  • 笔画以相同的方式工作,使用数组来设置星形边框颜色的重复序列。

strokes: ['#622569', '#4C858A', '#f06d06', '#00ff00']

启动我们的 init 函数

我们的 init 函数将执行以下操作:

  • 设置图案的背景颜色

  • 检索模式属性

  • 遍历每个星位置

  • 设置每颗星的属性

  • 为每个星星调用 drawJS.drawStar

让我们启动我们的 init 函数并调用 setBackground,一个用于设置画布背景颜色的 drawJS 方法。

function init() {   drawJS.setBackground('#215875');}

此 setBackground方法使用 Canvas API 的 rect 和 fill 方法,并且是你将在库中看到的第一个方法。

现在,我们有了背景颜色,让我们通过调用 getPatternProps 来获取星形图案的属性。

function init() {   drawJS.setBackground('#215875');   let props = getPatternProps();}

设置循环

在我们的 init 函数中,让我们创建一个填充星星的嵌套循环。我们将逐步建立这个。

让我们首先遍历行。我们可以使用 props.count.y 值访问行数。

let props = getPatternProps();for (var y = 0; y < props.count.y; ++y) {}

现在让我们看看 props.count.x 中每行的星星数。

for (var y = 0; y < props.count.y; ++y) {   for (var x = 0; x < props.count.x; ++x) {   }}

我们现在拥有的这些 x 和 y 索引将派上用场。

这是我们的 init 函数的开始:

function init() {    // set background on canvas    drawJS.setBackground('#215875');
// get star properties let props = getPatternProps();
// loop through rows for (var y = 0; y < props.count.y; ++y) {
// loop through stars in a row for (var x = 0; x < props.count.x; ++x) {
} }}

好的,这是 init 函数的一个很好的开始。到目前为止,我们已经设置了我们的背景颜色,定义了我们的图案属性,并设置了我们的循环。

确定每个星星的位置

在这个嵌套循环中,让我们想象一下它正在绘制的当前星星。要确定这颗星星的位置,它必须使用 props 中的 start 和 space 属性。

我们的目标是计算恒星的中心坐标并将它们存储在两个变量中:cx 和 cy。

让我们从星星的左上角位置开始:props.start.x 和 props.start.y。那是在横幅的左上角,第一颗星星的中心所在。

接下来,让我们找出每颗星星相对于第一颗星星的位置应该在哪里。这取决于它在序列中的位置(x 和 y)以及星间距(props.space.x 和 props.space.y)。

要找出离第一个位置有多远,让我们将当前的 x 或 y 索引乘以相应的空间值。

props.space.x * xprops.space.y * y

下面的表达式将 props.start.x 添加到 props.space.x 和 x 的乘积,给我们当前恒星的水平位置。

let cx = props.start.x + props.space.x * x;

这个表达式以同样的方式为我们提供了垂直位置。

let cy = props.start.y + props.space.y * y;

我们现在可以获得每颗恒星的中心。

找出每颗星星的点数

在模式中,点数增加,直到达到 12。之后,我们回到 5。然后我们再次开始增加。但是我们如何确定每颗星星上的点数呢?为了找到这个,我们首先需要知道每个恒星在序列中的位置。

如果我们把整个模式看作一个数组,这个位置可以看作是星星的索引。

寻找明星指数

下面你可以看到每个星星的索引在模式的前两行中应该是什么。

我们可以在循环外声明一个计数器变量并每次递增它,但让我们用数学方法找到索引,以便我们可以更多地探索这些 x 和 y 值。

让我们首先找到当前星星所在行上方的行数,即 y。现在让我们将 y ,先前行的数量,乘以 props.count.x,每行的星星数。

y * props.count.x

如果我们当前的星星在第三排呢?

  • 我们知道 props.count.x 总是 12。

  • 在我们的第三行中,y 是 2。

  • 所以上面几行的星星数是 12 • 2 = 24。

然后我们应该添加 x,它是当前行中先前星的数量。然后我们可以用这个方程定义 star_idx。

let star_idx = x + props.count.x + y;

寻找点的范围

当我们从一颗星到下一颗时,我们希望点数从 5 增加到 12,然后从 5 重新开始并重复。

要知道何时从 5 点重新开始,我们必须知道我们可以拥有多少个不同的点数。所以让我们找出最小点数和最大点数之间的范围,包括 5 和 12。

在代码中,我们将称其为 diff。

let diff = props.numPoints.max - props.numPoints.min + 1;

由于 star.numPoints.max 是 12 并且 star.numPoints.min 是 5,因此在这种情况下 diff 最终为 8。

使用余数运算符

现在让我们找出每颗星星的点数。每颗星至少有五分,但有些可能有更多分。

为了计算出还有多少,我们将使用 num、星级索引和我们刚刚计算的差异。我们使用余数运算符来查找将 num 除以 diff 所得的余数。

let morePoints = num % diff;

morePoints 可以得到的最高值是 7。例如,15 除以8,我们将 15 除以 8 得到的余数是 7。如果我们增加到 16,那么 16 除以8 会重置为零。以下是两行中这种重复增加的情况:

为了获得当前星星上的点数,我们取 morePoints 并添加最小点数——props.numPoints.min——即 5。

let numPoints = props.numPoints.min + morePoints

以下是两行中点数的外观:

万岁!已找到每颗星上的点数。

使星星变小

为了使星星变小,让我们放置一个从 1 开始但在每行之后逐渐变小的乘数。

let multiplier = (props.count.y - y) / props.count.y;

因为我们的行索引 y 从零开始,props.count.y — y 在我们的第一行中是 6 — 0 或 6。

结果,props.count.y — 1 / props.count.y = 6/6 = 1。

但在第二行,y 是 1。

所以 props.count.y — y 是 6 — 1 或 5。

因此,如果我们四舍五入到最接近的百分位,props.count.y — 1 / props.count.y = 5/6 = 0.83。

我们现在可以使用乘数来调整星星的外半径和内半径,我们将分别初始化为 35 和 20。

let outerRadius = 35, innerRadius = 20;

现在让我们用乘数改变每一个。

outerRadius *= multiplier;innerRadius *= multiplier;

完整的乘法器逻辑如下所示:

查找颜色、线宽和旋转

现在我们想要一个重复的颜色模式。请记住,我们有四种笔触颜色和四种填充颜色,正如我们在 stars 对象中的数组中所指定的:

fills: ['#b8a9c9', '#98d1d6', '#fed8b1', '#cff7d5'],strokes: ['#622569', '#4C858A', '#f06d06', '#008000']

为了确定填充和笔画,我们将再次使用余数运算符,但这一次是为了确定笔画和填充数组中应该有什么索引,我们将假设它们具有相同的长度。我们将此值称为 color_idx。

let color_idx = num % props.fills.length

我们的 props.fills 数组的长度为 4,它成为我们的除数——所以我们可能的余数值在 0 到 3 之间。

为了获得填充和描边,我们使用 color_idx 索引到每个数组。

let fill = props.fills[color_idx];let stroke = props.strokes[color_idx];

一旦一颗星星用数组中的最后一种颜色渲染,程序就会为下一颗星星使用第一种颜色。

最后,让我们设置 lineWidth 和rotate,这对每个星星都是一样的:

const lineWidth = 4, rotate = 0;

至此,我们需要的所有属性都已经设置好了!

回顾

让我们回顾一下我们现在拥有的九个属性。

  • cx 和 cy 是恒星中心点的坐标。

  • externalRadius 和innerRadius 决定了恒星的大小。

  • 填充和描边是星星的颜色。

  • 每颗星的线宽和旋转保持不变。

  • LnumPoints 确定每颗星上的点。

现在让我们将所有这些打包成一个对象,并将其直接传递给 drawStar 方法。

drawJS.drawStar({     cx,     cy,     outerRadius,     innerRadius,     numPoints,     lineWidth,     stroke,     fill,     rotate});

由于每个键的名称都与其在该对象中的值相同,因此我们可以对键和值使用一个术语——例如,cx 代替 cx:cx,rotate 代替rotate:rotate。

这是完成的 init 函数的样子。

function init() {    // set background on canvas    drawJS.setBackground('#215875');
// get star properties let props = getPatternProps();
// loop through rows for (var y = 0; y < props.count.y; ++y) {
// loop through columns for (var x = 0; x < props.count.x; ++x) {
// determine star's center position let cx = props.start.x + props.space.x * x; let cy = props.start.y + props.space.y * y;
// determine number of points let star_idx = x + props.count.x * y; let diff = props.numPoints.max - props.numPoints.min + 1; let morePoints = star_idx % diff; let numPoints = props.numPoints.min + morePoints;
// determine star size let multiplier = (props.count.y - y) / props.count.y let outerRadius = 35, innerRadius = 20; outerRadius *= multiplier; innerRadius *= multiplier;
// determine star color let color_idx = star_idx % props.fills.length; let fill = props.fills[color_idx]; let stroke = props.strokes[color_idx];
// determine linewidth and rotation const lineWidth = 4, rotate = 0;
// call the method drawJS.drawStar({ cx, cy, outerRadius, innerRadius, numPoints, lineWidth, stroke, fill, rotate }); } // end inner loop } // end outer loop}

就是这样!这就是代码。下面是我们的演示,因此,你可以看到所有内容如何组合在一起。

查看演示地址:https://codepen.io/nevkatz/pen/jOmexaK

总结

恭喜你!到这里已完成本教程,你已经成功地使用了许多 JavaScript 概念以及相当多的数学来创建这个星形图案。如果你想了解这方面的更多信息,以下是我建议的一些后续步骤:

  • 修改 getPatternProps 以便你可以想出不同的模式。

  • 尝试为一颗星星制作动画——然后尝试将多颗星星排列成一个图案。

  • 尝试使用 Math.random() 和一些模式修改来创建夜空。

  • 希望你能从我的这篇文章中学会用 JavaScript 和 Canvas 绘制星星。

谢谢阅读!


学习更多技能

请点击下方公众号

浏览 19
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报