当前位置:  开发笔记 > 前端 > 正文

HTML5Canvas递归画树

上图就是用html5随机生成的大树:)但是你应该没想到40+行代码就可以搞定了吧~接下来就跟大家说说这棵大树是如何实现的。同样必须要有html容器。新建Index.html,代码如下:<html><head><metahttp-equiv="Con...">

 

QQ20120923 4QQ20120923 5

上图就是用html5随机生成的大树 : ) 但是你应该没想到40+行代码就可以搞定了吧~接下来就跟大家说说这棵大树是如何实现的。

同样必须要有html容器。新建Index.html,代码如下:


  1. 		 
  2.  
  3.  
  4.  
  5.  
  6.  
  7.  
  8.  
  9.  

接下来咱们开始tree.js:


  1. 		var canvas = document.createElement("canvas");  
  2. var ctx = canvas.getContext("2d");  
  3. canvas.width = 640;  
  4. canvas.height = 480;  
  5. document.body.appendChild(canvas); 

代码很好理解,创建一个canvas画布,然后选择为2d画布,设置长宽,最后将这个画布添加到body标签下。

这个脚本最重要的函数在下面,大树就是递归调用这个函数实现的,调用一次画一条线段:


  1. 		var drawTree = function (ctx, startX, startY, length, angle, depth, branchWidth){  
  2. var rand = Math.random,  
  3. newLength, newAngle, newDepth, maxBranch = 3,  
  4. endX, endY, maxAngle = 2 * Math.PI / 4,  
  5. subBraches;  
  6. ctx.beginPath();  
  7. ctx.moveTo(startX, startY);  
  8. endX = startX + length * Math.cos(angle);  
  9. endY = startY + length * Math.sin(angle);  
  10. ctx.lineCap = &#39;round&#39;;  
  11. ctx.lineWidth = branchWidth;  
  12. ctx.lineTo(endX, endY);  
  13. if (depth <= 2){  
  14. ctx.strokeStyle = &#39;rgb(0,&#39; + (((rand() * 64) + 128) >> 0) + &#39;,0)&#39;;  
  15. } else {  
  16. ctx.strokeStyle = &#39;rgb(&#39; + (((rand() * 64) + 64) >> 0) + &#39;,50,25)&#39;;  
  17. }  
  18. ctx.stroke();  
  19. newDepth = depth - 1;  
  20. if (!newDepth)  
  21. return;  
  22. subBranches = (rand() * (maxBranch - 1)) + 1;  
  23. branchWidth *= 0.7;  
  24. for (var i = 0; i < subBranches; i++){  
  25. newAngle = angle + rand() * maxAngle - maxAngle * 0.5;  
  26. newLength = length * (0.7 + rand() * 0.3);  
  27. drawTree(ctx, endX, endY, newLength, newAngle, newDepth, branchWidth);  
  28. }  

接下来一点点解释:

首先,解释下各个变量的含义。ctx就是前面我们的2d画布;startX是线段开始的横坐标,同理startY是纵坐标;length是线段长度;angle是角度;depth是深度,叶子深度为1,树干为12(可自己设定);branchWidth就线段的粗细。有了这些信息,其实就描述了一个线段,通过这些信息我们才能画一个线段。

接下来又很可耻地一大段定义:


  1. 		var rand = Math.random,  
  2. newLength, newAngle, newDepth, maxBranch = 3,  
  3. endX, endY, maxAngle = 2 * Math.PI / 4,  
  4. subBraches; 

rand其实就是随机一个0~1之间的实数,顾名思义,接下来这些new的就是下一节线段的各种参数。maxBranch就是最多有3个分叉,最大的角度 PI/2 即为,下一级调整角度在90%范围内。subBranches就是分叉的个数。

好了,重要可以画了:


  1. 		ctx.beginPath();  
  2. ctx.moveTo(startX, startY);  
  3. endX = startX + length * Math.cos(angle);  
  4. endY = startY + length * Math.sin(angle);  
  5. ctx.lineCap = &#39;round&#39;;  
  6. ctx.lineWidth = branchWidth;  
  7. ctx.lineTo(endX, endY); 

beginPath()表示告诉浏览器“我要开始画了!”,把之前的记录放弃了,这点有点像ps。moveTo()把光标移动到(startX, startY),再计算终点坐标,endX,endY,有点像高中学的参数方程。然后告诉浏览器,lineCap要round,线段的两头要是圆形的。有多粗呢?等于branchWidth。线段一直画到(endX, endY)。


  1. 		if (depth <= 2){  
  2. ctx.strokeStyle = &#39;rgb(0,&#39; + (((rand() * 64) + 128) >> 0) + &#39;,0)&#39;;  
  3. } else {  
  4. ctx.strokeStyle = &#39;rgb(&#39; + (((rand() * 64) + 64) >> 0) + &#39;,50,25)&#39;;  

如果是已经画到了最后两级,即为叶子,那么就rgb就为(0, 128~192, 0)(rgb代表颜色,分别为红绿蓝,red green blue)。还没的话,就在(64~128, 50 ,25)中取。大家可能发现了,rgb必须为整数,但是rand()只能rand实数。大家其实也注意到了有个” >>  0″,js当中表示位运算,整体向右移动n位,0就是移动0位。其实它的作用和Math.floor()一样,但是速度更快。

动手画!


  1. 		ctx.stroke(); 

这个线段就画好了,是时候准备下它的分叉的时候了。


  1. 		newDepth = depth - 1;  
  2. if (!newDepth)  
  3. return; 

如果这个线段是最后一级,就没有分叉了,也是一个递归的终止条件。


  1. 		subBranches = (rand() * (maxBranch - 1)) + 1;  
  2. branchWidth *= 0.7;  
  3. for (var i = 0; i < subBranches; i++){  
  4. newAngle = angle + rand() * maxAngle - maxAngle * 0.5;  
  5. newLength = length * (0.7 + rand() * 0.3);  
  6. drawTree(ctx, endX, endY, newLength, newAngle, newDepth, branchWidth);  

分叉数是1~3中的一个数。然后有多少个分叉,就画几条线段,newAngle为原角度调整90度之内,新长度为原长度的0.7~1.0之间。

最后画出主干,这棵树就可以开始画了。


  1. 		drawTree(ctx, 320, 470, 60, -Math.PI / 2, 12, 12); 

大家可能注意到角度为负,不符合传统观念。但你要知道,画布的纵坐标和传统的坐标轴正好是相反的。

剩下可以发挥的东西还很多,比如大家可以调整各种参数,使树的颜色、大小变化,或者用这种方法去做些其他的事~

打完收工~附上文件:tree.zip


推荐阅读
  • 本文介绍了Java的集合及其实现类,包括数据结构、抽象类和具体实现类的关系,详细介绍了List接口及其实现类ArrayList的基本操作和特点。文章通过提供相关参考文档和链接,帮助读者更好地理解和使用Java的集合类。 ... [详细]
  • 本文介绍了使用CentOS7.0 U盘刻录工具进行安装的详细步骤,包括使用USBWriter工具刻录ISO文件到USB驱动器、格式化USB磁盘、设置启动顺序等。通过本文的指导,用户可以轻松地使用U盘安装CentOS7.0操作系统。 ... [详细]
  • 处理docker容器时间和宿主机时间不一致问题的方法
    本文介绍了处理docker容器时间和宿主机时间不一致问题的方法,包括复制主机的localtime到容器、处理报错情况以及重启容器的步骤。通过这些方法,可以解决docker容器时间和宿主机时间不一致的问题。 ... [详细]
  • Tomcat/Jetty为何选择扩展线程池而不是使用JDK原生线程池?
    本文探讨了Tomcat和Jetty选择扩展线程池而不是使用JDK原生线程池的原因。通过比较IO密集型任务和CPU密集型任务的特点,解释了为何Tomcat和Jetty需要扩展线程池来提高并发度和任务处理速度。同时,介绍了JDK原生线程池的工作流程。 ... [详细]
  • 本文介绍了电流源并联合并的方法,以及谐振电路的原理。谐振电路具有很强的选频能力,通过将电感和电容连接在一起,电流和电压会产生震荡。谐振频率的大小取决于电感和电容的大小,而电路中的电阻会逐渐降低震荡的幅度。电阻和电容组成的电路中,当电容放完电后,电阻两端的电压为0,电流不再流过电容。然而,电感是一种特殊的器件,当有电流流过时,线圈会产生感应磁场,阻止电流的流动,从而使电流不会减小。 ... [详细]
  • 标题: ... [详细]
  • 单点登录原理及实现方案详解
    本文详细介绍了单点登录的原理及实现方案,其中包括共享Session的方式,以及基于Redis的Session共享方案。同时,还分享了作者在应用环境中所遇到的问题和经验,希望对读者有所帮助。 ... [详细]
  • 本文介绍了在Docker容器技术中限制容器对CPU的使用的方法,包括使用-c参数设置容器的内存限额,以及通过设置工作线程数量来充分利用CPU资源。同时,还介绍了容器权重分配的情况,以及如何通过top命令查看容器在CPU资源紧张情况下的使用情况。 ... [详细]
  • 集合的遍历方式及其局限性
    本文介绍了Java中集合的遍历方式,重点介绍了for-each语句的用法和优势。同时指出了for-each语句无法引用数组或集合的索引的局限性。通过示例代码展示了for-each语句的使用方法,并提供了改写为for语句版本的方法。 ... [详细]
  • Python SQLAlchemy库的使用方法详解
    本文详细介绍了Python中使用SQLAlchemy库的方法。首先对SQLAlchemy进行了简介,包括其定义、适用的数据库类型等。然后讨论了SQLAlchemy提供的两种主要使用模式,即SQL表达式语言和ORM。针对不同的需求,给出了选择哪种模式的建议。最后,介绍了连接数据库的方法,包括创建SQLAlchemy引擎和执行SQL语句的接口。 ... [详细]
  • position属性absolute与relative的区别和用法详解
    本文详细解读了CSS中的position属性absolute和relative的区别和用法。通过解释绝对定位和相对定位的含义,以及配合TOP、RIGHT、BOTTOM、LEFT进行定位的方式,说明了它们的特性和能够实现的效果。同时指出了在网页居中时使用Absolute可能会出错的原因,即以浏览器左上角为原始点进行定位,不会随着分辨率的变化而变化位置。最后总结了一些使用这两个属性的技巧。 ... [详细]
  • 开发笔记:Docker 上安装启动 MySQL
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了Docker上安装启动MySQL相关的知识,希望对你有一定的参考价值。 ... [详细]
  • Oracle优化新常态的五大禁止及其性能隐患
    本文介绍了Oracle优化新常态中的五大禁止措施,包括禁止外键、禁止视图、禁止触发器、禁止存储过程和禁止JOB,并分析了这些禁止措施可能带来的性能隐患。文章还讨论了这些禁止措施在C/S架构和B/S架构中的不同应用情况,并提出了解决方案。 ... [详细]
  • Spring常用注解(绝对经典),全靠这份Java知识点PDF大全
    本文介绍了Spring常用注解和注入bean的注解,包括@Bean、@Autowired、@Inject等,同时提供了一个Java知识点PDF大全的资源链接。其中详细介绍了ColorFactoryBean的使用,以及@Autowired和@Inject的区别和用法。此外,还提到了@Required属性的配置和使用。 ... [详细]
  • 本文介绍了Java的公式汇总及相关知识,包括定义变量的语法格式、类型转换公式、三元表达式、定义新的实例的格式、引用类型的方法以及数组静态初始化等内容。希望对读者有一定的参考价值。 ... [详细]
author-avatar
书友75271582
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有