如何使用 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

相关文章

Java中流式并行操作parallelStream的原理和使用方法

《Java中流式并行操作parallelStream的原理和使用方法》本文详细介绍了Java中的并行流(parallelStream)的原理、正确使用方法以及在实际业务中的应用案例,并指出在使用并行流... 目录Java中流式并行操作parallelStream0. 问题的产生1. 什么是parallelS

Linux join命令的使用及说明

《Linuxjoin命令的使用及说明》`join`命令用于在Linux中按字段将两个文件进行连接,类似于SQL的JOIN,它需要两个文件按用于匹配的字段排序,并且第一个文件的换行符必须是LF,`jo... 目录一. 基本语法二. 数据准备三. 指定文件的连接key四.-a输出指定文件的所有行五.-o指定输出

Java中Redisson 的原理深度解析

《Java中Redisson的原理深度解析》Redisson是一个高性能的Redis客户端,它通过将Redis数据结构映射为Java对象和分布式对象,实现了在Java应用中方便地使用Redis,本文... 目录前言一、核心设计理念二、核心架构与通信层1. 基于 Netty 的异步非阻塞通信2. 编解码器三、

Linux jq命令的使用解读

《Linuxjq命令的使用解读》jq是一个强大的命令行工具,用于处理JSON数据,它可以用来查看、过滤、修改、格式化JSON数据,通过使用各种选项和过滤器,可以实现复杂的JSON处理任务... 目录一. 简介二. 选项2.1.2.2-c2.3-r2.4-R三. 字段提取3.1 普通字段3.2 数组字段四.

Linux kill正在执行的后台任务 kill进程组使用详解

《Linuxkill正在执行的后台任务kill进程组使用详解》文章介绍了两个脚本的功能和区别,以及执行这些脚本时遇到的进程管理问题,通过查看进程树、使用`kill`命令和`lsof`命令,分析了子... 目录零. 用到的命令一. 待执行的脚本二. 执行含子进程的脚本,并kill2.1 进程查看2.2 遇到的

SpringBoot基于注解实现数据库字段回填的完整方案

《SpringBoot基于注解实现数据库字段回填的完整方案》这篇文章主要为大家详细介绍了SpringBoot如何基于注解实现数据库字段回填的相关方法,文中的示例代码讲解详细,感兴趣的小伙伴可以了解... 目录数据库表pom.XMLRelationFieldRelationFieldMapping基础的一些代

一篇文章彻底搞懂macOS如何决定java环境

《一篇文章彻底搞懂macOS如何决定java环境》MacOS作为一个功能强大的操作系统,为开发者提供了丰富的开发工具和框架,下面:本文主要介绍macOS如何决定java环境的相关资料,文中通过代码... 目录方法一:使用 which命令方法二:使用 Java_home工具(Apple 官方推荐)那问题来了,

Java HashMap的底层实现原理深度解析

《JavaHashMap的底层实现原理深度解析》HashMap基于数组+链表+红黑树结构,通过哈希算法和扩容机制优化性能,负载因子与树化阈值平衡效率,是Java开发必备的高效数据结构,本文给大家介绍... 目录一、概述:HashMap的宏观结构二、核心数据结构解析1. 数组(桶数组)2. 链表节点(Node

Java AOP面向切面编程的概念和实现方式

《JavaAOP面向切面编程的概念和实现方式》AOP是面向切面编程,通过动态代理将横切关注点(如日志、事务)与核心业务逻辑分离,提升代码复用性和可维护性,本文给大家介绍JavaAOP面向切面编程的概... 目录一、AOP 是什么?二、AOP 的核心概念与实现方式核心概念实现方式三、Spring AOP 的关

详解SpringBoot+Ehcache使用示例

《详解SpringBoot+Ehcache使用示例》本文介绍了SpringBoot中配置Ehcache、自定义get/set方式,并实际使用缓存的过程,文中通过示例代码介绍的非常详细,对大家的学习或者... 目录摘要概念内存与磁盘持久化存储:配置灵活性:编码示例引入依赖:配置ehcache.XML文件:配置