分形是由重複數學方程式創建的無盡圖案。我們將使用純 Vanilla JS 和 HTML5 Canvas API 繪製一個最著名的分形。分形是由重複數學方程式創建的無盡圖案。我們將使用純 Vanilla JS 和 HTML5 Canvas API 繪製一個最著名的分形。

使用 JavaScript 和 HTML5 編碼分形樹

2025/10/11 03:00

\ 分形,那些神秘的圖形,它們無處不在,但未經訓練的眼睛卻看不見。今天我們將使用純 JavaScript 和 HTML5 Canvas API 繪製一個最著名的分形。讓我們開始編碼吧!

你將學到什麼

  • 什麼是分形樹?
  • 使用純 JavaScript 編寫分形樹
  • 超越分形樹

什麼是分形樹?

要定義分形樹,首先,我們當然必須知道分形的定義。

分形是通過重複數學方程式創建的無盡圖案,在任何尺度、任何縮放級別上,看起來大致相同。換句話說,一個幾何物體,其基本結構,粗糙或碎片化,在不同尺度上重複自身。

所以如果我們分割一個分形,我們會看到整體的縮小版本。

1975年創造"分形"一詞的貝諾伊特·曼德布羅特說:

\

\ 很清楚,對吧?

這裡有一些例子:

Animated Von Koch Curve

\ Animated Sierpinski Carpet

現在,什麼是分形樹?

想像一個分支,然後從中長出分支,然後每個分支又長出兩個分支,如此類推...這就是分形樹的樣子。

它的形式來自謝爾賓斯基三角形(或謝爾賓斯基墊)。

如你所見,當改變分支之間的角度時,一個會變成另一個:

From Sierpinski Triangle to Fractal

今天,我們將得到一個類似於該 GIF 最終形式的圖形。

使用純 JavaScript 編寫分形樹

首先,這是最終成品(你可以在過程中調整它):

Final Fractal Tree

現在讓我們一步一步地繪製它。

首先,我們用合理尺寸的畫布和一個包含所有 JavaScript 代碼的 script 標籤初始化我們的 index.html 文件。

<!doctype html> <html lang="en">   <head>     <meta charset="UTF-8" />   </head>   <body>     <canvas id="my_canvas" width="1000" height="800"></canvas>     <script></script>   </body> </html> 

然後,我們開始編寫 JavaScript。

我們在 JavaScript 中初始化畫布元素,通過 myCanvas 變量訪問它,並使用 ctx(上下文)變量創建 2D 渲染上下文。

<!doctype html> <html lang="en">   <head>     <meta charset="UTF-8" />   </head>   <body>     <canvas id="my_canvas" width="1000" height="800"></canvas>     <script>       var myCanvas = document.getElementById("my_canvas");       var ctx = myCanvas.getContext("2d");     </script>   </body> </html> 

是的,getContext 方法添加了允許你繪圖的屬性和方法,在這種情況下是 2D 繪圖。

現在是時候思考了。我們如何定義繪製分形樹的算法呢?嗯... 🤔

讓我們看看,我們知道分支會變得越來越小。而且每個分支末端都會長出兩個分支,一個向左,一個向右。

換句話說,當一個分支足夠長時,在它上面附加兩個更小的分支。重複這個過程。

這聽起來像是我們應該在某處使用遞迴語句,不是嗎?

回到代碼,我們現在定義我們的函數 fractalTree,它應該至少接受四個參數:分支起始的 X 和 Y 坐標,分支的長度,以及它的角度。

在我們的函數內部,我們用 beginPath() 方法開始繪圖,然後用 save() 方法保存畫布的狀態。

<!doctype html> <html lang="en">   <head>     <meta charset="UTF-8" />   </head>   <body>     <canvas id="my_canvas" width="1000" height="800"></canvas>     <script>       var myCanvas = document.getElementById("my_canvas");       var ctx = myCanvas.getContext("2d");       function draw(startX, startY, len, angle) {           ctx.beginPath();           ctx.save();       }     </script>   </body> </html> 

beginPath 方法通常在你開始一個具有固定樣式的新線條或圖形時使用,比如整條線具有相同的顏色,或相同的寬度。save 方法通過將當前狀態推入堆疊來保存畫布的整個狀態。

現在我們將通過繪製一條線(分支),旋轉畫布,繪製下一個分支,依此類推來繪製我們的分形樹。它是這樣的(我將在代碼示例下解釋每個方法):

<!doctype html> <html lang="en">   <head>     <meta charset="UTF-8" />   </head>   <body>     <canvas id="my_canvas" width="1000" height="800"></canvas>     <script>       var myCanvas = document.getElementById("my_canvas");       var ctx = myCanvas.getContext("2d");       function draw(startX, startY, len, angle) {           ctx.beginPath();           ctx.save();            ctx.translate(startX, startY);           ctx.rotate(angle * Math.PI/180);           ctx.moveTo(0, 0);           ctx.lineTo(0, -len);           ctx.stroke();            if(len < 10) {               ctx.restore();               return;           }            draw(0, -len, len*0.8, -15);           draw(0, -len, len*0.8, +15);            ctx.restore();       }       draw(400, 600, 120, 0)     </script>   </body> </html> 

所以我們首先添加三個方法,translate、rotate 和 moveTo,它們"移動"畫布、它的原點和我們的"鉛筆",這樣我們就可以以我們想要的角度繪製分支。就像我們在繪製一個分支,然後居中這個分支(通過移動整個畫布),然後從我們前一個分支的末端繪製一個新分支。

if 語句之前的最後兩個方法是 lineTo 和 stroke;第一個添加一條直線到當前路徑,第二個則渲染它。你可以這樣想:lineTo 下達命令,stroke 執行它。

現在我們有一個 if 語句,告訴何時停止遞迴,何時停止繪圖。restore 方法,正如 MDN 文檔中所述,"通過彈出繪圖狀態堆疊中的頂部條目來恢復最近保存的畫布狀態"。

在 if 語句之後,我們有遞迴調用和另一個對 restore 方法的調用。然後是對我們剛剛完成的函數的調用。

現在在你的瀏覽器中運行代碼。你最終會看到一棵分形樹!

Fractal Tree First Iteration

很棒,對吧?現在讓我們讓它變得更好。

我們將向我們的 draw 函數添加一個新參數,branchWidth,使我們的分形樹更加逼真。

<!doctype html> <html lang="en">   <head>     <meta charset="UTF-8" />   </head>   <body>     <canvas id="my_canvas" width="1000" height="800"></canvas>     <script>       var myCanvas = document.getElementById("my_canvas");       var ctx = myCanvas.getContext("2d");       function draw(startX, startY, len, angle, branchWidth) {           ctx.lineWidth = branchWidth;            ctx.beginPath();           ctx.save();            ctx.translate(startX, startY);           ctx.rotate(angle * Math.PI/180);           ctx.moveTo(0, 0);           ctx.lineTo(0, -len);           ctx.stroke();            if(len < 10) {               ctx.restore();               return;           }            draw(0, -len, len*0.8, angle-15, branchWidth*0.8);           draw(0, -len, len*0.8, angle+15, branchWidth*0.8);            ctx.restore();       }       draw(400, 600, 120, 0, 10)     </script>   </body> </html> 

所以在每次迭代中,我們讓每個分支變得更細。我也改變了遞迴調用中的角度參數,使樹更"開放"。

現在,讓我們添加一些顏色!還有陰影,為什麼不呢。

<!doctype html> <html lang="en">   <head>     <meta charset="UTF-8" />   </head>   <body>     <canvas id="my_canvas" width="1000" height="800"></canvas>     <script>       var myCanvas = document.getElementById("my_canvas");       var ctx = myCanvas.getContext("2d");       function draw(startX, startY, len, angle, branchWidth) {           ctx.lineWidth = branchWidth;            ctx.beginPath();           ctx.save();            ctx.strokeStyle = "green";           ctx.fillStyle = "green";            ctx.translate(startX, startY);           ctx.rotate(angle * Math.PI/180);           ctx.moveTo(0, 0);           ctx.lineTo(0, -len);           ctx.stroke();            ctx.shadowBlur = 15;           ctx.shadowColor = "rgba(0,0,0,0.8)";            if(len < 10) {               ctx.restore();               return;           }            draw(0, -len, len*0.8, angle-15, branchWidth*0.8);           draw(0, -len, len*0.8, angle+15, branchWidth*0.8);            ctx.restore();       }       draw(400, 600, 120, 0, 10)     </script>   </body> </html> 

兩種顏色方法都是不言自明的(strokeStyle 和 fillStyle)。還有陰影相關的,shadowBlur 和 shadowColor。

就是這樣!保存文件並用瀏覽器打開它,查看最終成品。

現在我鼓勵你玩轉代碼!改變 shadowColor,fillStyle,製作更短或更長的分形樹,改變角度,或嘗試添加葉子,這應該很有挑戰性 😉

超越分形樹

正如我在這篇文章開頭所展示的,有不同的分形。用 Canvas API 製作所有這些可能不容易,但應該是可能的。我用 C 程式語言製作了其中一些,我也玩過 p5.js。

p5.js 是一個由藝術家為藝術家製作的開源 JavaScript 庫

免責聲明: 本網站轉載的文章均來源於公開平台,僅供參考。這些文章不代表 MEXC 的觀點或意見。所有版權歸原作者所有。如果您認為任何轉載文章侵犯了第三方權利,請聯絡 service@support.mexc.com 以便將其刪除。MEXC 不對轉載文章的及時性、準確性或完整性作出任何陳述或保證,並且不對基於此類內容所採取的任何行動或決定承擔責任。轉載材料僅供參考,不構成任何商業、金融、法律和/或稅務決策的建議、認可或依據。

您可能也會喜歡