分形是通过重复数学方程式创建的无尽图案。我们将使用纯 JS 和 HTML5 Canvas API 绘制一个最著名的分形。分形是通过重复数学方程式创建的无尽图案。我们将使用纯 JS 和 HTML5 Canvas API 绘制一个最著名的分形。

使用JavaScript和HTML5编码分形树

2025/10/11 03:00

\ 分形,那些神秘的图形,它们无处不在,但未经训练的眼睛却看不见。今天我们将使用纯 JavaScript 和 HTML5 Canvas API 绘制一个最著名的分形。让我们开始编码吧!

你将学到什么

  • 什么是分形树?
  • 用纯 JavaScript 编写分形树
  • 超越分形树

什么是分形树?

要定义分形树,首先,我们当然必须知道分形的定义。

分形是通过重复数学方程创建的无尽图案,在任何尺度、任何缩放级别上,看起来大致相同。换句话说,一个几何对象,其基本结构,粗糙或碎片化,在不同尺度上重复自身。

所以如果我们分割一个分形,我们会看到整体的缩小版本。

创造"分形"一词的贝努瓦·曼德布罗特在1975年说:

\

\ 很清楚,对吧?

这里有一些例子:

动画冯·科赫曲线

\ 动画谢尔宾斯基地毯

现在,什么是分形树?

想象一个分支,然后从它延伸出分支,然后每个分支又延伸出两个分支,如此继续……这就是分形树的样子。

它的形式来源于谢尔宾斯基三角形(或谢尔宾斯基垫片)。

如你所见,当改变分支之间的角度时,一个变成了另一个:

从谢尔宾斯基三角形到分形

今天,我们将得到一个类似于该 GIF 最终形态的图形。

用纯 JavaScript 编写分形树

首先,这是最终成品(你可以在过程中调整它):

最终分形树

现在让我们一步步地绘制它。

首先,我们用一个合理尺寸的画布和一个包含所有 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 是一个由艺术家为艺术家制作的开源 JavaScript 库,基于 Processing 语言。你可以绘制或动画任何可以想象的东西。如果你对用代码创作艺术感兴趣,这是必不可少的。他们有一个很棒

免责声明: 本网站转载的文章均来源于公开平台,仅供参考。这些文章不代表 MEXC 的观点或意见。所有版权归原作者所有。如果您认为任何转载文章侵犯了第三方权利,请联系 [email protected] 以便将其删除。MEXC 不对转载文章的及时性、准确性或完整性作出任何陈述或保证,并且不对基于此类内容所采取的任何行动或决定承担责任。转载材料仅供参考,不构成任何商业、金融、法律和/或税务决策的建议、认可或依据。