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

2024-01-16 06:32

本文主要是介绍如何使用 JavaScript 和 Canvas 创建星形图案,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

c2fcb7f4bb409d3a8d7f6be5657b2cc8.png

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

翻译 | 杨小二

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

74e923a6cb96224d6e5f76761f803ebd.png

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

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

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

drawStar 方法

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

dd8223a46d4ce8643c6ea0f3f7d8289c.png

在这种情况下,我们可以使用下面的代码调用一次 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 之前,让我们再次浏览一下星形模式并考虑制作它需要什么。

下面是模式的前两行。

bdb303663635f347a0d3c1ba45dfa1b5.png

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

  • 将星星隔开

  • 连续的星星数

  • 行数

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

  • 点数

  • 描边颜色

  • 填充颜色

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

  • 边界宽度

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

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

5135c6d53e9eee81464f05148fdbff15.png

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

  • 第一颗星的位置

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

  • 最小和最大星点数

由于这是一种模式,因此应该定期重复变化的星形属性。例如,你会注意到点数从 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 canvasdrawJS.setBackground('#215875');// get star propertieslet props = getPatternProps();// loop through rowsfor (var y = 0; y < props.count.y; ++y) {// loop through stars in a rowfor (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 * x
props.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。然后我们再次开始增加。但是我们如何确定每颗星星上的点数呢?为了找到这个,我们首先需要知道每个恒星在序列中的位置。

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

寻找明星指数

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

399394745dc39f75080573b0c938f385.png

我们可以在循环外声明一个计数器变量并每次递增它,但让我们用数学方法找到索引,以便我们可以更多地探索这些 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 会重置为零。以下是两行中这种重复增加的情况:

fd9ef8699caed7910de7c4b8cfabe5e3.png

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

let numPoints = props.numPoints.min + morePoints

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

de6d2ca9cbdbae7299b5e25ebe7cdc9b.png

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

使星星变小

为了使星星变小,让我们放置一个从 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;

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

e37eacc07408c3226f6b97a49570cb3d.png

查找颜色、线宽和旋转

现在我们想要一个重复的颜色模式。请记住,我们有四种笔触颜色和四种填充颜色,正如我们在 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 canvasdrawJS.setBackground('#215875');// get star propertieslet props = getPatternProps();// loop through rowsfor (var y = 0; y < props.count.y; ++y) {// loop through columnsfor (var x = 0; x < props.count.x; ++x) {// determine star's center positionlet cx = props.start.x + props.space.x * x;let cy = props.start.y + props.space.y * y;// determine number of pointslet 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 sizelet multiplier = (props.count.y - y) / props.count.ylet outerRadius = 35, innerRadius = 20;outerRadius *= multiplier;innerRadius *= multiplier;// determine star colorlet color_idx = star_idx % props.fills.length;let fill = props.fills[color_idx];let stroke = props.strokes[color_idx];// determine linewidth and rotationconst lineWidth = 4, rotate = 0;// call the methoddrawJS.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 绘制星星。

谢谢阅读!

学习更多技能

请点击下方公众号

81d6047466b005fc59c404d632339813.gif

4310a1c7dd5be3071336c4319cd29446.png

a62592183fea7ea53446e993355333e0.png

这篇关于如何使用 JavaScript 和 Canvas 创建星形图案的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/611607

相关文章

python使用watchdog实现文件资源监控

《python使用watchdog实现文件资源监控》watchdog支持跨平台文件资源监控,可以检测指定文件夹下文件及文件夹变动,下面我们来看看Python如何使用watchdog实现文件资源监控吧... python文件监控库watchdogs简介随着Python在各种应用领域中的广泛使用,其生态环境也

Python中构建终端应用界面利器Blessed模块的使用

《Python中构建终端应用界面利器Blessed模块的使用》Blessed库作为一个轻量级且功能强大的解决方案,开始在开发者中赢得口碑,今天,我们就一起来探索一下它是如何让终端UI开发变得轻松而高... 目录一、安装与配置:简单、快速、无障碍二、基本功能:从彩色文本到动态交互1. 显示基本内容2. 创建链

SpringCloud集成AlloyDB的示例代码

《SpringCloud集成AlloyDB的示例代码》AlloyDB是GoogleCloud提供的一种高度可扩展、强性能的关系型数据库服务,它兼容PostgreSQL,并提供了更快的查询性能... 目录1.AlloyDBjavascript是什么?AlloyDB 的工作原理2.搭建测试环境3.代码工程1.

Java调用Python代码的几种方法小结

《Java调用Python代码的几种方法小结》Python语言有丰富的系统管理、数据处理、统计类软件包,因此从java应用中调用Python代码的需求很常见、实用,本文介绍几种方法从java调用Pyt... 目录引言Java core使用ProcessBuilder使用Java脚本引擎总结引言python

SpringBoot操作spark处理hdfs文件的操作方法

《SpringBoot操作spark处理hdfs文件的操作方法》本文介绍了如何使用SpringBoot操作Spark处理HDFS文件,包括导入依赖、配置Spark信息、编写Controller和Ser... 目录SpringBoot操作spark处理hdfs文件1、导入依赖2、配置spark信息3、cont

springboot整合 xxl-job及使用步骤

《springboot整合xxl-job及使用步骤》XXL-JOB是一个分布式任务调度平台,用于解决分布式系统中的任务调度和管理问题,文章详细介绍了XXL-JOB的架构,包括调度中心、执行器和Web... 目录一、xxl-job是什么二、使用步骤1. 下载并运行管理端代码2. 访问管理页面,确认是否启动成功

Java中的密码加密方式

《Java中的密码加密方式》文章介绍了Java中使用MD5算法对密码进行加密的方法,以及如何通过加盐和多重加密来提高密码的安全性,MD5是一种不可逆的哈希算法,适合用于存储密码,因为其输出的摘要长度固... 目录Java的密码加密方式密码加密一般的应用方式是总结Java的密码加密方式密码加密【这里采用的

Java中ArrayList的8种浅拷贝方式示例代码

《Java中ArrayList的8种浅拷贝方式示例代码》:本文主要介绍Java中ArrayList的8种浅拷贝方式的相关资料,讲解了Java中ArrayList的浅拷贝概念,并详细分享了八种实现浅... 目录引言什么是浅拷贝?ArrayList 浅拷贝的重要性方法一:使用构造函数方法二:使用 addAll(

使用Nginx来共享文件的详细教程

《使用Nginx来共享文件的详细教程》有时我们想共享电脑上的某些文件,一个比较方便的做法是,开一个HTTP服务,指向文件所在的目录,这次我们用nginx来实现这个需求,本文将通过代码示例一步步教你使用... 在本教程中,我们将向您展示如何使用开源 Web 服务器 Nginx 设置文件共享服务器步骤 0 —

解决mybatis-plus-boot-starter与mybatis-spring-boot-starter的错误问题

《解决mybatis-plus-boot-starter与mybatis-spring-boot-starter的错误问题》本文主要讲述了在使用MyBatis和MyBatis-Plus时遇到的绑定异常... 目录myBATis-plus-boot-starpythonter与mybatis-spring-b