用 JavaScript 画一棵树?
产品说要让前端用 JavaScript 画一棵树出来,但是这难道不能直接让 UI 给一张图片吗?
图片
后来一问才知道,产品要的是一颗随机树,也就是树的茂盛程度、长度、枝干粗细都是随机的,那这确实没办法叫 UI 给图,毕竟 UI 不可能给我 10000 张树的图片吧?
Canvas 画一颗随机树
接下来使用 Canvas 去画这棵随机树。
基础页面
我们需要在页面上写一个 canvas 标签,并设置好宽高,同时需要获取它的 Dom 节点、绘制上下文,以便后续的绘制。
图片
坐标调整
默认的 Canvas 坐标系是这样的。
图片
但是我们现在需要从中间去向上去画一棵树,所以坐标得调整成这样:
- X 轴从最上面移动到最下面。
- Y 轴的方向由往下调整成往上,并且从最左边移动到画布中间。
图片
这些操作可以使用 Canvas 的方法:
- ctx.translate: 坐标系移动。
- ctx.scale: 坐标系缩放。
图片
绘制一棵树的要素
绘制一棵树的要素是什么呢?其实就是树枝和果实,但是其实树枝才是第一要素,那么树枝又有哪些要素呢?无非就这几个点:
- 起始点
- 树枝长度、树枝粗细
- 生长角度
- 终点
开始绘制
所以我们可以写一个 drawBranch 来进行绘制,并且初始调用肯定是绘制树干,树干的参数如下:
- 起始点:(0, 0)
- 树枝长度、树枝粗细:这些可以自己自定义
- 生长角度:90度
- 终点:需要算
图片
这个终点应该怎么算呢?其实很简单,根据树枝长度、生长角度就可以算出来了,这是初高中的知识
图片
于是我们可以使用 Canvas 的绘制方法,去绘制线段,其实树枝就是一个一个的线段:
图片
到现在我绘制出了一个树干出来:
图片
但是我们是想让这棵树开枝散叶,所以需要继续递归继续去绘制更多的树枝出来。
递归绘制
其实往哪开枝散叶呢?无非就是往左或者往右。
图片
所以需要递归画左边和右边的树枝,并且子树枝肯定要比父树枝更短、比父树枝更细,比如我们可以定义一个比例:
- 子树枝是父树枝长度的 0.8。
- 子树枝是父树枝粗细的 0.75。
而子树枝的生长角度,其实可以随机,我们可以在 0° - 30° 之间随机选一个角度,于是增加了递归调用的代码:
图片
但是这个时候会发现,报错了,爆栈了,因为我们只递归开始,但却没有在某个时刻递归停止。
图片
我们可以自己定义一个停止规则(规则可以自己定义,这会决定你这棵树的茂盛程度):
- 粗细小于 2 时马上停止
- (粗细小于 10 时 + 随机数)决定是否停止
现在可以看到我们已经大致绘制出一棵树了。
图片
不过还少了树的果实。
绘制果实
绘制果实很简单,只需要在绘制树枝结束的时候,去把果实绘制出来就行,其实果实就是一个个的白色实心圆:
图片
至此这棵树完整绘制完毕。
图片
绘制部分的代码如下: