JavaScript SVG分叉树

举报
福州司马懿 发表于 2021/11/19 01:13:35 2021/11/19
【摘要】 使用 SVG 做一个树的动画 <!-- <!DOCTYPE> 声明不是 HTML 标签;它是指示 web 浏览器关于页面使用哪个 HTML 版本进行编写的指令。 --> <...

使用 SVG 做一个树的动画

<!-- <!DOCTYPE> 声明不是 HTML 标签;它是指示 web 浏览器关于页面使用哪个 HTML 版本进行编写的指令。 -->
<!-- 下面这个是HTML5标志 -->
<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf8" />
		<title>SVG Fractal Tree</title>
		<style type="text/css">
			body {
				/* 背景是银色,注意CSS中仅"多行注释"这一种注释有效 */
				background-color: silver;
			}
			svg {
				border: 1px dashed black;
			}
			svg line {
				stroke: black;
				stroke-width: .05;
			}
		</style>
	</head>
	<body>
		<!-- SVG和Canvas一样,width和height设置在style中是无效的 -->
		<svg xmlns="http://www.w3.org/2000/svg"
			xmlns:xlink="http://www.w3.org/1999/xlink"
			id="svg" width="100" height="100" >
			<defs id="defs">
				<line id="stem" x1="0" y1="0" x2="0" y2="-1" />
			</defs>
		</svg>
		<script type="text/javascript">
			//SVG 名空间
			var xmlns_svg = "http://www.w3.org/2000/svg";
			//xlink 名空间
			var xmlns_xlink = "http://www.w3.org/1999/xlink";
			
			var svg = document.getElementById("svg");
			var defs = document.getElementById("defs");
			
			//茎ID
			var stemId = "stem";
			//树ID
			var treeId = "tree";
			
			//树的最大深度
			var maxDepth = 9;
			//绘制间隔(单位:毫秒)
			var drawInterval = 500;
			
			var dx = 400;
			var dy = 350;
			
			//创建SVG标签
			function createSvgElem(elemTag) {
				return document.createElementNS(xmlns_svg, elemTag);
			}
			
			//组合成树的transform属性
			function formTreeTransform(dx, dy) {
				return "translate(" + dx + "," + dy + ") scale(100)";
			}
			
			//组合成茎的transform属性
			function formStemTransform(degree) {
				return "translate(0,-1) rotate(" + degree + ") scale(.7)";
			}
				
			//树
			var Tree = function(elem, degree) {
				//当前深度
				this.depth = 1;
				this.stemLeftTransform = formStemTransform(-1 * degree);
				this.stemRightTransform = formStemTransform(degree);
				console.log(degree +","+this.depth+" init");
				
				//画下一级的枝干
				this.grow = function() {
					console.log(degree+","+this.depth);
					//这里用直接用诸如_this=tree是不合法的,因为没有使用var声明的都是全局变量,会呼吸影响
					var prevId = "#" + degree + "_" + (this.depth - 1) + "_" + stemId;
					var id = degree + "_" + this.depth + "_" + stemId;
					
					var g = createSvgElem("g");
					var use1 = createSvgElem("use");
					var use2 = createSvgElem("use");
					var use3 = createSvgElem("use");
					
					g.setAttribute("id", id);
					//g.setAttributeNS(null, "id", id);
					
					use1.setAttributeNS(xmlns_xlink, "xlink:href", prevId);
					use1.setAttribute("transform", this.stemLeftTransform);
					//use1.setAttributeNS(null, "transform", this.stemLeftTransform);
					
					use2.setAttributeNS(xmlns_xlink, "xlink:href", prevId);
					use2.setAttribute("transform", this.stemRightTransform);
					//use2.setAttributeNS(null, "transform", this.stemRightTransform);
					
					use3.setAttributeNS(xmlns_xlink, "xlink:href", "#" + stemId);
					
					defs.appendChild(g);
					g.appendChild(use1);
					g.appendChild(use2);
					g.appendChild(use3);
					
					elem.setAttributeNS(xmlns_xlink, "xlink:href", "#" + id);
					
					update(this);
				}
				
				//由于函数是全局的,因此内部没有定义this对象
				function update(tree) {
					if(tree.depth < maxDepth) {
						tree.depth++;
						//setTimeout的参数必须是一个函数,而不能是函数变量,所以必须使用匿名函数做转接
						setTimeout(function(){
							tree.grow();
						}, drawInterval);
					}
				}
			}

			function init() {
				var degrees = new Uint8Array([15, 25, 35, 45]);
				//svg只能用setAttribute的方式设置宽高,不能像canvas一样直接用width=value来设置
				svg.setAttribute("width", dx * 2);
				svg.setAttribute("height", dy * Math.floor(degrees.length / 2) + 20);
				for(var i=0; i < degrees.length; i++) {
					var id = degrees[i] + "_" + treeId;
					var use = createSvgElem("use");
					use.setAttributeNS(null, "transform", formTreeTransform((.5 + i % 2) * dx, Math.floor(i / 2 + 1) * dy));
					svg.appendChild(use);
					var tree = new Tree(use, degrees[i]);
					tree.grow();
					/*
					这里使用setTimeout,会导致只有循环的最后一个tree的grow方法有效,
					通过全局变量和参数传递的方式也不能解决
					setTimeout(function(){
						tree.grow();
					}, drawInterval);
					随着学习的深入知道,可以这么解决
					(function(tree){
						setTimeout(function(){
							tree.grow()
						}, drawInterval);
					})(tree);
					*/
				}
			}
			init();
		</script>
	</body>
</html>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153

这里写图片描述

答疑

1. 怎么把 line 换成文字

有人在评论里问我,我在这里简单的介绍一下

树的原理,主要是对defs中定义的元素(我称之为本体)进行一个平移和旋转,最后呈现出一个组合的图形。
知道原理后,要替换就很简单了,只要将其本体进行替换即可

但是有几点要注意的

  • svg 并没有提供设置文字大小的属性,文字大小需要通过 css 的 style 进行设置(文字最小 0.1)。
  • css 中的 transform,使用百分比会相对整个svg图进行换算,行不通。因此只能用写死的数值平移,让文字水平居中。
  • 文字默认的位置是左下角基线的坐标。
<svg width="100%" height="100%" version="1.1" xmlns="http://www.w3.org/2000/svg">
     <text x="100" y="100" dx="20 40 60 80 100" fill="black" style="font-size:40px;">我是中国人</text>
     <path d="M100,0 V200 M0,100 H200" stroke="red"/>
</svg>

  
 
  • 1
  • 2
  • 3
  • 4
我是中国人

要把 line 标签,改为 <text id="stem" x="0" y="0" style="font-size:0.8px;transform:translateX(-0.4px);">树</text>

未加 translate 的效果
在这里插入图片描述加了 translate 的效果
在这里插入图片描述

2. for 循环里的 setTimeout 为什么只有最后一个索引生效

JS是单线程环境,也就是说代码的执行是从上到下,依次执行。也就是同一个时间只能做一件事。

单线程就意味着,所有任务需要排队,前一个任务结束,才会执行后一个任务。如果前一个任务耗时很长,后一个任务就不得不一直等着。JavaScript将所有任务分成两种,一种是同步任务,另一种是异步任务。同步任务指的是,在主线程上排队执行的任务,只有前一个任务执行完毕,才能执行后一个任务;异步任务指的是,不进入主线程、而进入"任务队列"(task queue)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。

异步执行的运行机制

  1. 所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
  2. 主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
  3. 一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
  4. 主线程不断重复上面的第三步。主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。只要主线程空了,就会去读取"任务队列",这就是JavaScript的运行机制。这个过程会循环反复。

解决方案

因此就能解释之前的问题了,setTimeout的函数,是等for循环执行完毕后,才开始执行,此时迭代器已走到最后一个列表末端了。此时引用for循环中的局部变量都是最后一个的,所以就出现问题了。

解决方案就是,使用同步的匿名函数,将局部变量通过参数传递。由于匿名函数是同步执行的,因此setTimeout引用的也就成了匿名函数中的参数,也就是迭代器每走一步时的局部变量了。

文章来源: blog.csdn.net,作者:福州-司马懿,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/chy555chy/article/details/54561671

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。