前端

Canvas 递归绘制随机分形树

用 Canvas 递归分形画随机树,LCG 伪随机保证可复现,点击换一棵,并适配高清屏 DPR。

#canvas #recursion #generative-art

Canvas 递归绘制随机分形树

分形树是 Canvas 入门的经典练习:从树干出发,每画一节枝条就递归分叉,直到深度耗尽。加上伪随机种子,每次点击都能生成形态各异但风格统一的树。下面 demo 支持点击换树、窗口缩放高清重绘。

核心链路

1. 递归分形:depth 驱动一切

drawBranch(x, y, len, angle, depth, maxDepth) 是核心函数:

  1. 画当前枝段(线宽由 depth / maxDepth 的平方决定,主干粗、末梢细)
  2. 按深度决定分叉数量:主干区 2~3 叉,末梢 0~1 叉
  3. 子枝长度乘以 0.63~0.78 的随机缩放,逐层缩短
  4. depth <= 2 时在末梢稀疏开花

较粗的枝段还有概率从线段中途再分叉,让树形更自然。

2. 角度约束:树枝只朝上半平面

Canvas 的 Y 轴向下,所以 sin(angle) <= 0 表示朝上。clampAngleUpward 把角度限制在 [-π, 0],避免枝条插进地面:

function clampAngleUpward(angle) {
  let a = angle
  while (a <= -Math.PI) a += Math.PI * 2
  while (a > Math.PI) a -= Math.PI * 2
  if (a > 0) a = -a
  return a
}

3. LCG 伪随机:可复现的「随机」

用线性同余生成器(LCG)代替 Math.random(),同一 seed 下每次绘制结果一致;点击画布时重置 seed,得到新树:

function rand() {
  seed = (seed * 16807 + 0) % 2147483647
  return (seed - 1) / 2147483646
}

4. devicePixelRatio 高清适配

设置 canvas.width = cssWidth * dpr,再用 ctx.setTransform(dpr, 0, 0, dpr, 0, 0) 缩放坐标系,Retina 屏上线条不会发虚。resize 时按容器尺寸重算并整棵重绘。

在线体验

点击下方画布可重新生成一棵树。也可 全屏预览 打开独立页面。

正在加载随机树…

小结

随机分形树的套路很固定:递归 + 深度递减 + 随机参数。难点在于细节——线宽平滑过渡、中途分叉、角度约束、末梢开花,这些让「算法树」看起来像「自然树」。如果你要做自己的生成艺术,可以从固定 maxDepth 和单一分叉规则开始,再逐步加随机性和装饰。