JavaScript SVG分叉树
Posted 福州-司马懿
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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. 怎么把 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>
要把 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引用的也就成了匿名函数中的参数,也就是迭代器每走一步时的局部变量了。
开发者涨薪指南 48位大咖的思考法则、工作方式、逻辑体系以上是关于JavaScript SVG分叉树的主要内容,如果未能解决你的问题,请参考以下文章
使用 Javascript 动态添加时,SVG 元素无法正确缩放