使用D3.js实现多圆沿不同路径动画的问题排查
解决D3.js中多个圆形沿不同路径动画的问题
我来帮你搞定这个D3.js的动画问题!你现在遇到的情况是多个圆形没法各自沿对应的路径运动,要么全挤同一条路,要么报错找不到路径的getTotalLength方法,核心问题是元素绑定和索引对应没做好,咱们一步步来修复:
先理清核心错误原因
- 重复ID与选择器混乱:你给所有动画圆加了同一个
id='travel',HTML里ID是唯一标识,重复使用会导致选择器无法区分单个元素;而且画动画圆时复用了锚点圆的selectAll('circle'),导致数据绑定混乱。 - 路径索引未正确对应:之前用
mypath.node()[index]是错误的——node()只返回第一个路径DOM元素,不是数组,自然取[index]会得到undefined;改用nodes()后,又没在动画逻辑里把每个圆的索引和对应路径绑定起来。
修复步骤详解
步骤1:修正元素选择与数据绑定
给锚点圆和动画圆分别加专属类,避免选择器冲突,同时去掉重复ID:
// 画锚点圆(起点终点) group.selectAll(".anchor-circle") .data(points) .enter() .append("circle") .attr("class", "anchor-circle") // 专属类 .attr("cx", d => d.x) .attr("cy", d => d.y) .attr("r", d => d.r); // 画动画圆 var circle = group .selectAll('.travel-circle') // 用类选择器替代重复ID .data(linesArray) .enter() .append("circle") .attr("class", "travel-circle") // 专属类 .attr("r", 8) .style("fill", 'steelblue') .style('opacity', 0.5) .attr("transform", `translate(${points[0].x},${points[0].y})`);
步骤2:绑定每个圆到对应路径
先获取所有路径的DOM元素数组,之后在动画时用索引匹配:
// 获取所有路径的DOM节点数组 var pathNodes = mypath.nodes();
步骤3:修正动画逻辑与路径关联
利用D3each方法的索引参数,给每个圆传入对应的路径:
// 动画路径计算函数 function translateAlong(pathNode) { var length = pathNode.getTotalLength(); return function(x) { var p = pathNode.getPointAtLength(x * length); return `translate(${p.x},${p.y})`; }; } // 触发动画 setTimeout(() => { circle.each(function(d, i) { // 这里的i是当前圆的索引,对应pathNodes里的第i个路径 d3.select(this) .transition() .duration(10000) .style("fill", 'red') .attrTween('transform', translateAlong(pathNodes[i])) .remove(); }); }, 0);
完整修复后的代码
<div class='container'></div> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script> <script> var w = $(window).innerWidth(); var h = $(window).innerHeight(); var svg = d3 .select('.container') .append('svg') .attr('width', w) .attr('height', h); // 起点终点数组 var points = [{ x: 0, y: 0, r: 5 }, { x: 700, y: 100, r: 5 }]; // 添加分组 var group = svg .append('g') .attr('transform', 'translate(20,20)'); // 绘制起点终点锚点圆 group.selectAll(".anchor-circle") .data(points) .enter() .append("circle") .attr("class", "anchor-circle") .attr("cx", d => d.x) .attr("cy", d => d.y) .attr("r", d => d.r); // 计算曲线控制点范围 var cpx = points[1].x - points[0].x, cpy = points[1].y - points[0].y; // 生成随机曲线路径数组 var linesArray = []; for (let i = 0; i <= 10; i++) { var path = d3.path(); path.moveTo(points[0].x, points[0].y); path.quadraticCurveTo( Math.random() * cpx + points[0].x, Math.random() * cpy + points[0].y, points[1].x, points[1].y ); linesArray.push({ d: path.toString() }) } // 绘制所有路径 var mypath = group .selectAll('path') .data(linesArray) .enter() .append("path") .attr("d", d => d.d) .attr("stroke", "firebrick") .attr("stroke-width", 2) .attr("fill", "none"); // 获取路径DOM节点数组 var pathNodes = mypath.nodes(); // 绘制动画圆 var circle = group .selectAll('.travel-circle') .data(linesArray) .enter() .append("circle") .attr("class", "travel-circle") .attr("r", 8) .style("fill", 'steelblue') .style('opacity', 0.5) .attr("transform", `translate(${points[0].x},${points[0].y})`); // 路径动画计算函数 function translateAlong(pathNode) { var length = pathNode.getTotalLength(); return function(x) { var p = pathNode.getPointAtLength(x * length); return `translate(${p.x},${p.y})`; }; } // 触发动画 setTimeout(() => { circle.each(function(d, i) { d3.select(this) .transition() .duration(10000) .style("fill", 'red') .attrTween('transform', translateAlong(pathNodes[i])) .remove(); }); }, 0); </script>
内容的提问来源于stack exchange,提问作者louislugas
相关产品推荐
相关产品推荐

