JavaScript SVG分叉树
使用 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)的任务,只有"任务队列"通知主线程,某个异步任务可以执行了,该任务才会进入主线程执行。
异步执行的运行机制
- 所有同步任务都在主线程上执行,形成一个执行栈(execution context stack)。
- 主线程之外,还存在一个"任务队列"(task queue)。只要异步任务有了运行结果,就在"任务队列"之中放置一个事件。
- 一旦"执行栈"中的所有同步任务执行完毕,系统就会读取"任务队列",看看里面有哪些事件。那些对应的异步任务,于是结束等待状态,进入执行栈,开始执行。
- 主线程不断重复上面的第三步。主线程从"任务队列"中读取事件,这个过程是循环不断的,所以整个的这种运行机制又称为Event Loop(事件循环)。只要主线程空了,就会去读取"任务队列",这就是JavaScript的运行机制。这个过程会循环反复。
解决方案
因此就能解释之前的问题了,setTimeout的函数,是等for循环执行完毕后,才开始执行,此时迭代器已走到最后一个列表末端了。此时引用for循环中的局部变量都是最后一个的,所以就出现问题了。
解决方案就是,使用同步的匿名函数,将局部变量通过参数传递。由于匿名函数是同步执行的,因此setTimeout引用的也就成了匿名函数中的参数,也就是迭代器每走一步时的局部变量了。
文章来源: blog.csdn.net,作者:福州-司马懿,版权归原作者所有,如需转载,请联系作者。
原文链接:blog.csdn.net/chy555chy/article/details/54561671
- 点赞
- 收藏
- 关注作者
评论(0)