如何使用D3.js在IE11中对齐tspan的X轴位置?
初始问题
使用D3.js构建力导向图时,遇到标签渲染兼容性问题:除IE11外,其他浏览器标签显示正常,但IE11中第一个<tspan>无法居中对齐。尝试过添加/移除x属性、调整dx值等方法,但因每组标签长度不同,dx的计算逻辑无法通用。
当前SVG结构
<text class="edgelabel" id="edgelabel0" font-size="10" style="text-anchor: middle;"> <textPath xlink:href="#edgepath0" dominant-baseline="middle" startOffset="50%" style="cursor: pointer;"> <tspan class="edgelabels" dx="0">label one</tspan> <tspan class="edgelabels" x="0" dy="22" dx="0">label two</tspan> <tspan class="edgelabels" x="0" dy="22" dx="0">label three</tspan> </textPath> </text>
对应渲染代码
const edgelabels = zoomContainer .selectAll('.edgelabel') .data(links) .enter() .append('text') .attr('class', 'edgelabel') .style('text-anchor', 'middle') .attr('id', function (d, i) { return 'edgelabel' + i; }) .attr('font-size', 10); edgelabels .append('textPath') .attr('xlink:href', function (d, i) { return '#edgepath' + i; }) .style('cursor', 'pointer') .attr('dominant-baseline', 'middle') .attr('startOffset', '50%') .selectAll('div.textPath') .data(function (d, i) { return d.label; }) .enter() .append('tspan') .attr('class', 'edgelabels') .text(function (d, i) { return console.log(d), d; }) .attr('x', function (d, i) { if (i > 0) { return 0; } }) .attr('dy', function (d, i) { if (i > 0) { return 22; } }) .attr('dx', 0);
想了解:
- 是否有其他人遇到过类似问题?
- 代码中是否存在错误?
- 还有哪些方法可以实现每组标签的
dx属性统一? - 是否需要重构代码?
更新后的问题
发现IE11不支持<textPath>内嵌套<tspan>,于是将<tspan>直接放在<text>内,通过动态计算x/y坐标让文本定位到路径上,重构了ticked()方法以获取最新坐标,但出现新问题:所有文本元素使用相同的坐标,p1和p2始终获取最后一条边的坐标,导致所有文本都在同一条路径上。
相关代码
// 获取edgepath坐标用于<text>定位 edgepaths.each(function (d) { source_x = d.source.x; source_y = d.source.y; target_x = d.target.x; target_y = d.target.y; }); let p1 = { x: source_x, y: source_y }; let p2 = { x: target_x, y: target_y }; // 将<text>居中到路径上 let textAnchor = this.interpolatedPoint(p1, p2, 0.5); // 给<text>元素添加x/y属性 edgelabels.attr('x', textAnchor.x).attr('y', textAnchor.y)
问题解答
关于初始IE11 <tspan> 居中问题
这是IE11对SVG <textPath> 内 <tspan> 的兼容性bug——IE11不会继承<textPath>的startOffset和text-anchor设置给子<tspan>,导致第一个<tspan>无法居中。你的代码逻辑本身没问题,核心是浏览器兼容性限制。
关于更新后坐标复用的问题
问题出在edgepaths.each()的循环逻辑:你在循环里直接覆盖全局的source_x、source_y等变量,循环结束后这些变量只会保留最后一条边的坐标,所以所有文本都用了同一个位置。
修复方法很简单,不要用全局变量存坐标,而是直接在edgelabels.attr()的回调里针对每条边单独计算坐标:
// 直接在edgelabels的attr回调中处理每条边的坐标 edgelabels.attr('x', function(d) { const midX = (d.source.x + d.target.x) / 2; return midX; }).attr('y', function(d) { const midY = (d.source.y + d.target.y) / 2; return midY; });
如果你的interpolatedPoint是自定义方法(比如处理曲线中点),也可以在回调里调用:
edgelabels.attr('x', function(d) { const p1 = {x: d.source.x, y: d.source.y}; const p2 = {x: d.target.x, y: d.target.y}; return interpolatedPoint(p1, p2, 0.5).x; }).attr('y', function(d) { const p1 = {x: d.source.x, y: d.source.y}; const p2 = {x: d.target.x, y: d.target.y}; return interpolatedPoint(p1, p2, 0.5).y; });
这样每条边的文本都会基于自身的source和target坐标计算中点,不会出现复用最后一条边坐标的问题。
其他优化建议
- 不需要给非第一个
<tspan>设置x="0",因为<tspan>默认会继承父元素的位置,只需要设置dy来换行即可。 - 若要兼容IE11的多行标签,除了直接用
<text>+坐标定位,还可以考虑给每个标签组创建独立的<text>元素,而不是用<tspan>,这样更易控制位置。
内容的提问来源于stack exchange,提问作者Tom Rudge

