\ 分形,那些神秘的圖形,它們無處不在,但未經訓練的眼睛卻看不見。今天我們將使用純 JavaScript 和 HTML5 Canvas API 繪製一個最著名的分形。讓我們開始編碼吧!
要定義分形樹,首先,我們當然必須知道分形的定義。
分形是通過重複數學方程式創建的無盡圖案,在任何尺度、任何縮放級別上,看起來大致相同。換句話說,一個幾何物體,其基本結構,無論粗糙或碎片化,都在不同尺度上重複自身。
所以如果我們分割一個分形,我們會看到整體的縮小版本。
Benoit Mandelbrot,1975年創造"分形"一詞的人,說:
\
\ 很清楚,對吧?
這裡有一些例子:

\ 
現在,什麼是分形樹?
想像一個分支,然後從中長出分支,然後每個分支又長出兩個分支,如此類推...這就是分形樹的樣子。
它的形式來源於謝爾賓斯基三角形(或謝爾賓斯基墊)。
如你所見,當改變分支之間的角度時,一個會變成另一個:

今天,我們最終會得到一個類似於該 GIF 最終形態的圖形。
首先,這是最終成品(你可以在過程中調整它):

現在讓我們一步步繪製它。
首先,我們用一個合理尺寸的畫布和一個包含所有 JS 代碼的腳本標籤初始化我們的 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。
我們通過 myCanvas 變量訪問畫布元素,並使用 ctx(上下文)變量創建 2D 渲染上下文,從而在 JS 中初始化我們的畫布元素。
<!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 方法的調用。然後是對我們剛剛完成的函數的調用。
現在在你的瀏覽器中運行代碼。你最終會看到一棵分形樹!

很棒,對吧?現在讓我們讓它變得更好。
我們將向我們的 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 是一個由藝術家為藝術家製作的開源


Bağlantıyı kopyalaX (Twitter)LinkedInFacebookEmail
Binance ADGM'den Tam Onay Kazandı