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

相关文章

Pandas使用SQLite3实战

《Pandas使用SQLite3实战》本文主要介绍了Pandas使用SQLite3实战,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学... 目录1 环境准备2 从 SQLite3VlfrWQzgt 读取数据到 DataFrame基础用法:读

JSON Web Token在登陆中的使用过程

《JSONWebToken在登陆中的使用过程》:本文主要介绍JSONWebToken在登陆中的使用过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录JWT 介绍微服务架构中的 JWT 使用结合微服务网关的 JWT 验证1. 用户登录,生成 JWT2. 自定义过滤

Java中StopWatch的使用示例详解

《Java中StopWatch的使用示例详解》stopWatch是org.springframework.util包下的一个工具类,使用它可直观的输出代码执行耗时,以及执行时间百分比,这篇文章主要介绍... 目录stopWatch 是org.springframework.util 包下的一个工具类,使用它

Java进行文件格式校验的方案详解

《Java进行文件格式校验的方案详解》这篇文章主要为大家详细介绍了Java中进行文件格式校验的相关方案,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、背景异常现象原因排查用户的无心之过二、解决方案Magandroidic Number判断主流检测库对比Tika的使用区分zip

Java实现时间与字符串互相转换详解

《Java实现时间与字符串互相转换详解》这篇文章主要为大家详细介绍了Java中实现时间与字符串互相转换的相关方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、日期格式化为字符串(一)使用预定义格式(二)自定义格式二、字符串解析为日期(一)解析ISO格式字符串(二)解析自定义

Java使用Curator进行ZooKeeper操作的详细教程

《Java使用Curator进行ZooKeeper操作的详细教程》ApacheCurator是一个基于ZooKeeper的Java客户端库,它极大地简化了使用ZooKeeper的开发工作,在分布式系统... 目录1、简述2、核心功能2.1 CuratorFramework2.2 Recipes3、示例实践3

Springboot处理跨域的实现方式(附Demo)

《Springboot处理跨域的实现方式(附Demo)》:本文主要介绍Springboot处理跨域的实现方式(附Demo),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不... 目录Springboot处理跨域的方式1. 基本知识2. @CrossOrigin3. 全局跨域设置4.

springboot security使用jwt认证方式

《springbootsecurity使用jwt认证方式》:本文主要介绍springbootsecurity使用jwt认证方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地... 目录前言代码示例依赖定义mapper定义用户信息的实体beansecurity相关的类提供登录接口测试提供一

go中空接口的具体使用

《go中空接口的具体使用》空接口是一种特殊的接口类型,它不包含任何方法,本文主要介绍了go中空接口的具体使用,具有一定的参考价值,感兴趣的可以了解一下... 目录接口-空接口1. 什么是空接口?2. 如何使用空接口?第一,第二,第三,3. 空接口几个要注意的坑坑1:坑2:坑3:接口-空接口1. 什么是空接

Spring Boot 3.4.3 基于 Spring WebFlux 实现 SSE 功能(代码示例)

《SpringBoot3.4.3基于SpringWebFlux实现SSE功能(代码示例)》SpringBoot3.4.3结合SpringWebFlux实现SSE功能,为实时数据推送提供... 目录1. SSE 简介1.1 什么是 SSE?1.2 SSE 的优点1.3 适用场景2. Spring WebFlu