如何使用 JavaScript 和 Canvas 創(chuàng)建星形圖案

英文 | https://javascript.plainenglish.io/how-to-create-a-star-pattern-using-javascript-and-canvas-322f8af61ce1
翻譯 | 楊小二
在本教程中,我們將使用 JavaScript 和 HTML5 Canvas API 創(chuàng)建下圖橫幅的星形圖案。

因?yàn)榧词箘?chuàng)建一顆星也需要多行代碼,所以我們將使用 drawJS——我構(gòu)建的一個(gè)處理星形渲染的迷你庫(kù)。
在此過(guò)程中,我們將使用對(duì)象字面量、嵌套的 for 循環(huán)和余數(shù)運(yùn)算符來(lái)創(chuàng)建這種模式——以及一個(gè) CSS 媒體查詢(xún)來(lái)使橫幅在窄屏幕寬度下調(diào)整大小。
使用 drawJS 的星形圖案演示地址:https://codepen.io/nevkatz/pen/jOmexaK
drawStar 方法
圖案是通過(guò)為圖案中的每個(gè)星星調(diào)用 drawJS 庫(kù)中的 drawStar 方法來(lái)創(chuàng)建的。假設(shè)我們只想創(chuàng)建圖案中的第一顆星,如下所示。

在這種情況下,我們可以使用下面的代碼調(diào)用一次 drawStar。
drawJS.drawStar({cx:50,cy:50,numPoints:5,stroke:'#622569',fill:'#b8a9c9',lineWidth:4,innerRadius: 20,outerRadius:35});
現(xiàn)在,由于我們想要?jiǎng)?chuàng)建多個(gè),因此,我們最終將編寫(xiě)一些更復(fù)雜的 JavaScript。但首先,讓我們編寫(xiě) HTML 和 CSS 并思考模式的特征。
打好基礎(chǔ)
讓我們從一些標(biāo)記和樣式開(kāi)始。
HTML 和 CSS
HTML中的<canvas> 元素。
<canvas id="myCanvas" width="1300" height="600"></canvas>我們的第一個(gè) CSS 將使其成為塊元素并將其居中。
canvas {display: block;margin: 10px auto;}
為了使畫(huà)布具有響應(yīng)性,我們需要添加一個(gè)媒體查詢(xún),它將在屏幕寬度低于 1200 像素時(shí)將寬度設(shè)置為 100%。
我們還可以使用height: auto 來(lái)幫助我們的畫(huà)布在調(diào)整大小時(shí)保持其縱橫比。
@media screen and (max-width: 1200px) {canvas {width: 100%;height: auto;}}
規(guī)劃模式
在我們開(kāi)始編寫(xiě)最初的 JavaScript 之前,讓我們?cè)俅螢g覽一下星形模式并考慮制作它需要什么。
下面是模式的前兩行。

要以這種方式排列星星,我們需要編寫(xiě)設(shè)置幾個(gè)值的邏輯:
將星星隔開(kāi)
連續(xù)的星星數(shù)
行數(shù)
我們的程序還需要設(shè)置每顆星星的獨(dú)特屬性,包括以下內(nèi)容:
點(diǎn)數(shù)
描邊顏色
填充顏色
星星還有一些共同的特性:
邊界寬度
內(nèi)半徑,或恒星中心到每個(gè)角的距離
外半徑,或從恒星中心到每個(gè)外點(diǎn)的距離

最后,我們需要設(shè)置以下內(nèi)容:
第一顆星的位置
筆觸和填充顏色的調(diào)色板(每種四種顏色)
最小和最大星點(diǎn)數(shù)
由于這是一種模式,因此應(yīng)該定期重復(fù)變化的星形屬性。例如,你會(huì)注意到點(diǎn)數(shù)從 5 開(kāi)始,然后從一顆星到下一顆增加 1,直到達(dá)到 12 點(diǎn)。一旦發(fā)生這種情況,下一位明星將獲得 5 分。
這些是我們需要轉(zhuǎn)換為代碼的模式屬性。
設(shè)置模式屬性
現(xiàn)在我們已經(jīng)概述了模式的要求,讓我們卷起袖子,用兩個(gè)函數(shù)編寫(xiě)它的邏輯:
getPatternProps,它建立星形圖案的屬性。
init,它將遍歷每個(gè)星星的位置,設(shè)置每個(gè)星星的屬性,并調(diào)用 drawJS.drawStar 方法。
getPatternProps 函數(shù)將返回一個(gè)包含我們模式屬性的對(duì)象。
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;}
讓我們來(lái)看看發(fā)生了什么:
start 將第一顆星的 x 和 y 坐標(biāo)設(shè)置為 (50,50)。
start: { x:50, y:50 }numPoints 將使星星可以擁有的最少點(diǎn)數(shù)設(shè)為 5,并將最大點(diǎn)數(shù)設(shè)置為 12。
numPoints: { min:5, max:12 }space 將使星星在垂直和水平方向上相距 100 像素。
space: {x: 100, y: 100 }count 告訴我們?cè)谝恍?(x) 和六行 (y) 中有 12 顆星。
count: { x: 12, y: 6}fills 將確定作為重復(fù)序列出現(xiàn)的四種填充顏色。
fills: ['#b8a9c9', '#98d1d6', '#fed8b1', '#cff7d5']筆畫(huà)以相同的方式工作,使用數(shù)組來(lái)設(shè)置星形邊框顏色的重復(fù)序列。
strokes: ['#622569', '#4C858A', '#f06d06', '#00ff00']啟動(dòng)我們的 init 函數(shù)
我們的 init 函數(shù)將執(zhí)行以下操作:
設(shè)置圖案的背景顏色
檢索模式屬性
遍歷每個(gè)星位置
設(shè)置每顆星的屬性
為每個(gè)星星調(diào)用 drawJS.drawStar
讓我們啟動(dòng)我們的 init 函數(shù)并調(diào)用 setBackground,一個(gè)用于設(shè)置畫(huà)布背景顏色的 drawJS 方法。
function init() {drawJS.setBackground('#215875');}
此 setBackground方法使用 Canvas API 的 rect 和 fill 方法,并且是你將在庫(kù)中看到的第一個(gè)方法。
現(xiàn)在,我們有了背景顏色,讓我們通過(guò)調(diào)用 getPatternProps 來(lái)獲取星形圖案的屬性。
function init() {drawJS.setBackground('#215875');let props = getPatternProps();}
設(shè)置循環(huán)
在我們的 init 函數(shù)中,讓我們創(chuàng)建一個(gè)填充星星的嵌套循環(huán)。我們將逐步建立這個(gè)。
讓我們首先遍歷行。我們可以使用 props.count.y 值訪問(wèn)行數(shù)。
let props = getPatternProps();for (var y = 0; y < props.count.y; ++y) {}
現(xiàn)在讓我們看看 props.count.x 中每行的星星數(shù)。
for (var y = 0; y < props.count.y; ++y) {for (var x = 0; x < props.count.x; ++x) {}}
我們現(xiàn)在擁有的這些 x 和 y 索引將派上用場(chǎng)。
這是我們的 init 函數(shù)的開(kāi)始:
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 函數(shù)的一個(gè)很好的開(kāi)始。到目前為止,我們已經(jīng)設(shè)置了我們的背景顏色,定義了我們的圖案屬性,并設(shè)置了我們的循環(huán)。
確定每個(gè)星星的位置
在這個(gè)嵌套循環(huán)中,讓我們想象一下它正在繪制的當(dāng)前星星。要確定這顆星星的位置,它必須使用 props 中的 start 和 space 屬性。
我們的目標(biāo)是計(jì)算恒星的中心坐標(biāo)并將它們存儲(chǔ)在兩個(gè)變量中:cx 和 cy。
讓我們從星星的左上角位置開(kāi)始:props.start.x 和 props.start.y。那是在橫幅的左上角,第一顆星星的中心所在。
接下來(lái),讓我們找出每顆星星相對(duì)于第一顆星星的位置應(yīng)該在哪里。這取決于它在序列中的位置(x 和 y)以及星間距(props.space.x 和 props.space.y)。
要找出離第一個(gè)位置有多遠(yuǎn),讓我們將當(dāng)前的 x 或 y 索引乘以相應(yīng)的空間值。
props.space.x * xprops.space.y * y
下面的表達(dá)式將 props.start.x 添加到 props.space.x 和 x 的乘積,給我們當(dāng)前恒星的水平位置。
let cx = props.start.x + props.space.x * x;這個(gè)表達(dá)式以同樣的方式為我們提供了垂直位置。
let cy = props.start.y + props.space.y * y;我們現(xiàn)在可以獲得每顆恒星的中心。
找出每顆星星的點(diǎn)數(shù)
在模式中,點(diǎn)數(shù)增加,直到達(dá)到 12。之后,我們回到 5。然后我們?cè)俅伍_(kāi)始增加。但是我們?nèi)绾未_定每顆星星上的點(diǎn)數(shù)呢?為了找到這個(gè),我們首先需要知道每個(gè)恒星在序列中的位置。
如果我們把整個(gè)模式看作一個(gè)數(shù)組,這個(gè)位置可以看作是星星的索引。
尋找明星指數(shù)
下面你可以看到每個(gè)星星的索引在模式的前兩行中應(yīng)該是什么。

我們可以在循環(huán)外聲明一個(gè)計(jì)數(shù)器變量并每次遞增它,但讓我們用數(shù)學(xué)方法找到索引,以便我們可以更多地探索這些 x 和 y 值。
讓我們首先找到當(dāng)前星星所在行上方的行數(shù),即 y。現(xiàn)在讓我們將 y ,先前行的數(shù)量,乘以 props.count.x,每行的星星數(shù)。
y * props.count.x如果我們當(dāng)前的星星在第三排呢?
我們知道 props.count.x 總是 12。
在我們的第三行中,y 是 2。
所以上面幾行的星星數(shù)是 12 ? 2 = 24。
然后我們應(yīng)該添加 x,它是當(dāng)前行中先前星的數(shù)量。然后我們可以用這個(gè)方程定義 star_idx。
let star_idx = x + props.count.x + y;尋找點(diǎn)的范圍
當(dāng)我們從一顆星到下一顆時(shí),我們希望點(diǎn)數(shù)從 5 增加到 12,然后從 5 重新開(kāi)始并重復(fù)。
要知道何時(shí)從 5 點(diǎn)重新開(kāi)始,我們必須知道我們可以擁有多少個(gè)不同的點(diǎn)數(shù)。所以讓我們找出最小點(diǎn)數(shù)和最大點(diǎn)數(shù)之間的范圍,包括 5 和 12。
在代碼中,我們將稱(chēng)其為 diff。
let diff = props.numPoints.max - props.numPoints.min + 1;由于 star.numPoints.max 是 12 并且 star.numPoints.min 是 5,因此在這種情況下 diff 最終為 8。
使用余數(shù)運(yùn)算符
現(xiàn)在讓我們找出每顆星星的點(diǎn)數(shù)。每顆星至少有五分,但有些可能有更多分。
為了計(jì)算出還有多少,我們將使用 num、星級(jí)索引和我們剛剛計(jì)算的差異。我們使用余數(shù)運(yùn)算符來(lái)查找將 num 除以 diff 所得的余數(shù)。
let morePoints = num % diff;morePoints 可以得到的最高值是 7。例如,15 除以8,我們將 15 除以 8 得到的余數(shù)是 7。如果我們?cè)黾拥?16,那么 16 除以8 會(huì)重置為零。以下是兩行中這種重復(fù)增加的情況:

為了獲得當(dāng)前星星上的點(diǎn)數(shù),我們?nèi)?morePoints 并添加最小點(diǎn)數(shù)——props.numPoints.min——即 5。
let numPoints = props.numPoints.min + morePoints以下是兩行中點(diǎn)數(shù)的外觀:

萬(wàn)歲!已找到每顆星上的點(diǎn)數(shù)。
使星星變小
為了使星星變小,讓我們放置一個(gè)從 1 開(kāi)始但在每行之后逐漸變小的乘數(shù)。
let multiplier = (props.count.y - y) / props.count.y;因?yàn)槲覀兊男兴饕?y 從零開(kāi)始,props.count.y — y 在我們的第一行中是 6 — 0 或 6。
結(jié)果,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。
我們現(xiàn)在可以使用乘數(shù)來(lái)調(diào)整星星的外半徑和內(nèi)半徑,我們將分別初始化為 35 和 20。
let outerRadius = 35, innerRadius = 20;現(xiàn)在讓我們用乘數(shù)改變每一個(gè)。
outerRadius *= multiplier;innerRadius *= multiplier;
完整的乘法器邏輯如下所示:

查找顏色、線寬和旋轉(zhuǎn)
現(xiàn)在我們想要一個(gè)重復(fù)的顏色模式。請(qǐng)記住,我們有四種筆觸顏色和四種填充顏色,正如我們?cè)?stars 對(duì)象中的數(shù)組中所指定的:
fills: ['#b8a9c9', '#98d1d6', '#fed8b1', '#cff7d5'],strokes: ['#622569', '#4C858A', '#f06d06', '#008000']
為了確定填充和筆畫(huà),我們將再次使用余數(shù)運(yùn)算符,但這一次是為了確定筆畫(huà)和填充數(shù)組中應(yīng)該有什么索引,我們將假設(shè)它們具有相同的長(zhǎng)度。我們將此值稱(chēng)為 color_idx。
let color_idx = num % props.fills.length我們的 props.fills 數(shù)組的長(zhǎng)度為 4,它成為我們的除數(shù)——所以我們可能的余數(shù)值在 0 到 3 之間。
為了獲得填充和描邊,我們使用 color_idx 索引到每個(gè)數(shù)組。
let fill = props.fills[color_idx];let stroke = props.strokes[color_idx];
一旦一顆星星用數(shù)組中的最后一種顏色渲染,程序就會(huì)為下一顆星星使用第一種顏色。
最后,讓我們?cè)O(shè)置 lineWidth 和rotate,這對(duì)每個(gè)星星都是一樣的:
const lineWidth = 4, rotate = 0;至此,我們需要的所有屬性都已經(jīng)設(shè)置好了!
回顧
讓我們回顧一下我們現(xiàn)在擁有的九個(gè)屬性。
cx 和 cy 是恒星中心點(diǎn)的坐標(biāo)。
externalRadius 和innerRadius 決定了恒星的大小。
填充和描邊是星星的顏色。
每顆星的線寬和旋轉(zhuǎn)保持不變。
LnumPoints 確定每顆星上的點(diǎn)。
現(xiàn)在讓我們將所有這些打包成一個(gè)對(duì)象,并將其直接傳遞給 drawStar 方法。
drawJS.drawStar({cx,cy,outerRadius,innerRadius,numPoints,lineWidth,stroke,fill,rotate});
由于每個(gè)鍵的名稱(chēng)都與其在該對(duì)象中的值相同,因此我們可以對(duì)鍵和值使用一個(gè)術(shù)語(yǔ)——例如,cx 代替 cx:cx,rotate 代替rotate:rotate。
這是完成的 init 函數(shù)的樣子。
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}
就是這樣!這就是代碼。下面是我們的演示,因此,你可以看到所有內(nèi)容如何組合在一起。
查看演示地址:https://codepen.io/nevkatz/pen/jOmexaK
總結(jié)
恭喜你!到這里已完成本教程,你已經(jīng)成功地使用了許多 JavaScript 概念以及相當(dāng)多的數(shù)學(xué)來(lái)創(chuàng)建這個(gè)星形圖案。如果你想了解這方面的更多信息,以下是我建議的一些后續(xù)步驟:
修改 getPatternProps 以便你可以想出不同的模式。
嘗試為一顆星星制作動(dòng)畫(huà)——然后嘗試將多顆星星排列成一個(gè)圖案。
嘗試使用 Math.random() 和一些模式修改來(lái)創(chuàng)建夜空。
希望你能從我的這篇文章中學(xué)會(huì)用 JavaScript 和 Canvas 繪制星星。
謝謝閱讀!
學(xué)習(xí)更多技能
請(qǐng)點(diǎn)擊下方公眾號(hào)
![]()

