\ フラクタル、どこにでもある謎めいた図形ですが、訓練されていない目には見えません。今日は、バニラJSとHTML5 Canvas APIだけを使って、最もよく知られているフラクタルの一つを描いてみましょう。コーディングを始めましょう!
フラクタルツリーを定義するには、まず当然ながらフラクタルの定義を知る必要があります。
フラクタルは、数学的方程式を繰り返すことで作られる終わりのないパターンで、どのスケールでも、どのズームレベルでも、大体同じように見えます。言い換えれば、その基本構造が、粗いまたは断片的なものが、異なるスケールで自分自身を繰り返す幾何学的オブジェクトです。
つまり、フラクタルを分割すると、全体の縮小コピーが見えるのです。
1975年にフラクタルという用語を作ったブノワ・マンデルブロは言いました:
\
\ かなり明確ですよね?
いくつか例を挙げます:

\ 
さて、フラクタルツリーとは何でしょうか?
枝を想像してください。そこから枝が出て、さらに各枝から二つの枝が出て、というように続きます...それがフラクタルツリーの姿です。
その形はシェルピンスキーの三角形(またはシェルピンスキーのガスケット)から来ています。
ご覧のように、枝の間の角度を変えると、一方が他方になります:

今日は、このGIFの最終形態に似た図形を作成します。
まず、最終的な成果物はこちらです(途中で調整できます):

では、ステップバイステップで描いていきましょう。
まず、適切なサイズのキャンバスとすべてのJSコードが入る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を書き始めます。
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で描画できるプロパティとメソッドを追加します。
さて、考える時間です。フラクタルツリーを描くアルゴリズムをどのように定義できるでしょうか?うーん... 🤔
枝がどんどん小さくなっていくことはわかっています。そして各枝の先には、左右に2つの枝が出ています。
つまり、枝が十分に長い場合、それに2つの小さな枝を取り付けます。リピート。
どこかで再帰的なステートメントを使うべきだと思いませんか?
コードに戻りましょう。ここで、少なくとも4つの引数を取る関数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の3つのメソッドを追加します。これらはキャンバス、その原点、そして「鉛筆」を「移動」させて、希望の角度で枝を描けるようにします。これは、枝を描き、この枝を中心に配置し(キャンバス全体を移動させることで)、前の枝の終わりから新しい枝を描くようなものです。
if文の前の最後の2つのメソッドはlineToとstrokeです。最初のメソッドは現在のパスに直線を追加し、2番目のメソッドはそれをレンダリングします。次のように考えることができます:lineToが命令を出し、strokeがそれを実行します。
次に、再帰をいつ停止するか、描画をいつ停止するかを指示するif文があります。restoreメソッドは、MDN Docsに記載されているように、「描画状態スタックの一番上のエントリをポップすることで、最も最近保存されたキャンバスの状態を復元します」。
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を変更したり、フラクタルツリーを短くしたり長くしたり、角度


