
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>svg力导向图动画-可以修改小球数量</title> <style> svg { border: 1px solid #eee; } </style></head><body> <svg xmlns="http://www.w3.org/2000/svg" viewBox="-400 -400 800 800" width="800" height="800"> <path id="links" fill="none" stroke="#eeeeee" /> <text id="text" fill="white"></text> </svg> <script> //辅助类 Vector: 向量==矢量,有方向的标量; (function () { function Vector(x, y) { this.x = x || 0; this.y = y || 0; } Vector.prototype = { constructor: Vector, square: function () { return this.x ** 2 + this.y ** 2; }, length: function () { return Math.sqrt(this.square()); }, add: function (q) { return new Vector(this.x + q.x, this.y + q.y) }, minus: function (q) { return new Vector(this.x - q.x, this.y - q.y) }, multiply: function (scale) { return new Vector(this.x * scale, this.y * scale) }, //标准化: 乘以一个方向向量,相当于length在某向量上的投影 normalize: function (length) { if (length == undefined) { length = 1; } return this.multiply(length / this.length()); } }; Vector.fromPoints = function (p1, p2) { //函数可以有自己的方法 return new Vector(p2.x - p1.x, p2.y - p1.y); }; window.Vector = Vector; })(); //end of Vector // 力导图: 近乎弹簧的互作,近排斥,远吸引; var XML_NS = "http://www.w3.org/2000/svg"; var k = 0.1; //弹性系数,弹簧变形的距离和力之间的系数 F=k*delta; 尝试0.05比较好 //建立点,简化为等质量的点,可以人一多个点 //todo map()函数 var points = "a,b,c,d,e,f,g,h".split(",").map(function (name, index, arr) { return { name: name, color: 'hsl(' + (360 / arr.length * index) + ', 100%, 60%)', } }); var relation = 300; //互作为0的距离; var svg = document.querySelector('svg'), text = document.querySelector('text'); var Vector = window.Vector; function random(min, max) { return Math.round(min + (max - min) * Math.random()); } // todo forEach()函数 points.forEach(function (point) { //console.log(point) //{name: "a", color: "hsl(0, 100%, 60%)"} var circle = document.createElementNS(XML_NS, 'circle'); //这个namespace写错是不能显示的! //init position var x = random(-300, 300), y = random(-300, 300); //调整到屏幕中心区域 circle.setAttribute('cx', x); circle.setAttribute('cy', y); circle.setAttribute('r', "10"); circle.setAttribute('fill', point.color); //绑定 point.circle = circle; point.s = new Vector(x, y);//初始化位移 point.v = new Vector();//初始没有速度 point.a = new Vector();//初始没有加速度 //加入到dom svg.appendChild(circle); }); //获取上一帧的时间 var lastFrameTime = +new Date(); //更新当前帧 var w = 0 var links = document.getElementById('links'); function update() { var frameTime = +new Date();//拿到当前帧的时间 var t = frameTime - lastFrameTime;//两帧时间差 //animation begin //对时间差(单位是ms)进行缩放, t /= 100; //缩放到1/5s为单位; 物理学单位是m,屏幕单位是px,尝试出来的换算关系; //更新点的位移、速度、加速度、力等 points.forEach(function (pa) { //console.log(pa) var f = new Vector(); //定义一个空矢量,表示力 //1.计算合力 points.forEach(function (pb) { //两两互作,排除掉自己和自己 if (pa.name == pb.name) return; //console.log(w,'两两',pa.name, pb.name) var x = Vector.fromPoints(pa.s, pb.s);//点之间的距离 var delta = x.length() - relation;//自然长度之外的 弹性变量 //求合力 f=k*x f = f.add(x.normalize(delta * k)); //在x方向上,力(delta*k) 的投影的长度 }); //2.计算加速度 a=F/m; 简化质量都是m=1 pa.a = f; //3. 计算速度 vt=v0+a*t; //如果没有能量损耗,系统将会一直运动下去。此处假设速度每次损耗2%,设置为0.98 pa.v = pa.v.add(pa.a.multiply(t)).multiply(0.98); //bug: 0.999时停止的慢,但是全部小球的质心不固定! //4. 计算位置 st=s0+v*t pa.s = pa.s.add(pa.v.multiply(t)); //设置到dom上 pa.circle.setAttribute('cx', pa.s.x); pa.circle.setAttribute('cy', pa.s.y); }); //前面计算 //接着,根据计算结果,更新画面 render(); //for debug only; w += 1; if (w % 100 == 0) { console.log(' >>> steps=', w); } //2000步差不多正好,可以停止了, //也可以设置其他停止条件,比如全部小球的变动小于某个值的时候,需要记录上一个小球的位置 if (w > 2000) { return; } //animation end lastFrameTime = frameTime;//当前帧的时间已经成历史 window.requestAnimationFrame(update);//更新画面 } //渲染 function render() { //画点和点之间的连线 var linkPath = []; points.forEach(function (pa) { var sa = pa.s; points.forEach(function (pb) { if (pa.name == pb.name) return; var sb = pb.s; linkPath = linkPath.concat([ "M", sa.x, sa.y, 'L', sb.x, sb.y, ]); }); }); //绘制连线 links.setAttribute('d', linkPath.join(' ')); //为小球添加文字 text.innerHTML = ""; function n2str(n) { return new String(n); } points.forEach(function (p) { var tspan = document.createElementNS(XML_NS, 'tspan'); tspan.setAttribute('x', n2str(p.s.x - 5)) tspan.setAttribute('y', n2str(p.s.y + 5)) tspan.innerHTML = p.name; text.appendChild(tspan); }); svg.append(text);//重新把文字放到最后,也就是顶层 } window.requestAnimationFrame(update); </script></body></html>