马黑PHP整站系统

用canvas画布绘制一棵静态树

位置: 首页 > 代码集锦[ 发布时间: 2024.4.19  作者: 马黑  阅读: 107 ]

递归指的是一个函数在执行过程中调用自己、直至执行条件满足时终止函数的自执行。我们接触得最多的应该就是请求关键帧动画,我们把动画的执行机制做成一个函数,在函数内部使用 requestAnimationFrame() 反复调用该函数:

let raf = null; //操作动画的开关   let render = () => {     /* ... 这里是运行动画的代码 */     raf = requestAnimationFrame(render); //通过请求关键帧动画调用函数自身 };

requestAnimationFrame() 方法是JS封装好的以显示刷新率为运行频率的API,它在函数 render() 的内部调用了函数 render() ;自身,使得函数要执行的任务得以不间断地执行。上例没有在函数内部提供任务终结的直接出口,而是通过请求关键帧动画标识 raf 用做开关,以便可以在函数的外部对动画的运行、暂停进行有效控制。不过地道的递归函数则应在函数内部提供出口条件,换言之,函数自身应该具备终止执行任务的条件,为此,我们在设计递归函数时,除了可以像上例那样可以在外部开、关函数的运行外,还应有能力在函数内部设立开、关逻辑清晰的运行机制,让递归函数适时终止自执行。以下例子,函数需要一个整数型参数,函数的任务是1加到该参数,就是等差数列求和,数学家高斯小时候做的1加到100的那个问题:

let add = (num) => {     if(num <= 0) return 0;     console.log(`${num} + add(${num} - 1)`);//本行测试 :打印运算式子     return num + add(num - 1); }; console.log(add(100));

add(num) 函数首先设立一个条件,即代码第2行,如果参数 num ≤ 0 将返回 0,这个条件一旦满足,函数就会退出运行。第 4 行代码,整体意思是返回 num + add(num -1),这里信息量很庞大:return 是返回结果;而结果,首先是 num 自身,其次是它依次通过修改参数为 num - 1 来反复调用函数自身、直至参数 num ≤ 1 时把第二行的返回值也加上就退出累加工作。代码第 6 行,运行函数 add(num),传参是 100,并打印出最终累加结果。

需要特别注意,递归函数必须设计出口,以逻辑清晰的条件为临界,令任务执行完毕跳出任务的执行。递归递归,递是传递,将传递出去的指令一一落实,归就是回归,指令执行到满足条件了就要回头,不要无休止地干下去。

利用递归函数,我们可以在canvas画布上画一棵树,思路是这样子:

① 设计一个 draw 函数,需要 5 个参数:树干起始点XY坐标 sx 和 sy、树干单节长度 len、树干宽度 width、树干折向角度 deg;
② 接力绘制树干,每一次都将画布坐标系位移到(sx,sy)处、旋转画布坐标系 deg 个弧度。首次在外部调用函数时使用实数参数,内部的递归调用则依据情况按一定系数调整各个参数;
③ 以 len 参数即一节树干(后来变为树枝)的长度为出口参数,内部递归调用乘上一个小于 1 的参数,这样它不断变短——实际上从第二次开始它们变成了分岔的树枝——,当它短到一定的程度,退出递归调用,退出时还有一个重要的任务,绘制果实;
④ 函数内部要两次递归调用函数自身,实现树枝左右折向,同时也令绘制情景更为复杂化;
⑤ 我们需要树枝的折向角度不那么一致,以避免树的成品过于对称。

这样绘制出来的树不带叶子,适合在沙漠中生长,其果实可以加工成黑石浓缩饮料,价值连城。下面是完整代码:

<canvas id="canv" width="800" height="500" style="border: 1px solid gray"></canvas>   <script> let ctx = canv.getContext('2d'); let ww = canv.width, hh = canv.height;   let rad = () => Math.random() * 10 + 10; //获取10~20的弧度  
/* 绘制函数 以画树干为主体绘制任务,通过递归调用函数自身,变树干为树枝 当树干长度低于 15 改画随机颜色的果实并退出绘制任务 */
let draw = (sx,sy,len,width,deg) => {     ctx.strokeStyle = 'olivedrab';     ctx.lineWidth = width;     ctx.lineCap = 'square';     ctx.lineJoin = 'miter';     ctx.beginPath();     ctx.save();     ctx.translate(sx,sy);     ctx.rotate(deg * Math.PI / 180);     ctx.moveTo(0, 0);     ctx.lineTo(0, -len);     ctx.stroke();       if(len < 15) {         ctx.beginPath();         ctx.arc(0, -len, 6, 0, deg * Math.PI * 2, false);         ctx.fillStyle = `#${Math.random().toString(16).substr(-6)}`;         ctx.fill();         ctx.restore();         return;        }       //两次不同折角的递归调用函数自身     draw(0, -len, len * 0.8, width * 0.8, deg + rad());     draw(0, -len, len * 0.8, width * 0.8, deg - rad());         ctx.restore(); };   //从函数外部调用绘制函数 draw(ww / 2, hh, hh / 4.5, 10, 0);   </script>

可以将上述代码存为本地 .html 文档,或将代码拿到 pencil code,然后运行查看效果。

前一篇: 成吉思汗(歌词同步+小球碰撞演示)
下一篇: canvas画布:大白兔变大红兔

发表评论:

       

评论列表 [3条]

#3 | 小希 于 2024-4-19 20:46 发布: 沙漠中生长,其果实可以加工成黑石浓缩饮料~~看来可以多种一些,开辟黑石饮料原浆基地。

#2 | 飞飞 于 2024-4-19 20:42 发布: 递归函数这么复杂的绘制,代码并不冗长。设想得全面,连两边不要太对称都想到了。。。执行的精确,枝干由粗到细,最后自动画的彩色小果子也很诱人。。

#1 | 悄然 于 2024-4-19 20:35 发布: 同棵树的果子不可能这么五彩缤纷,所以这是一棵开花的树。。

Copyright © 2023 All Right Reserved 马黑PHP文章管理整站系统v1.8
联系我们: gxblk@163.com