SVG绝对变换计算对text元素返回错误值,求解决方案
SVG text元素绝对坐标获取异常问题
问题背景
使用getCTM()计算SVG元素绝对坐标时,circle元素结果正常,但text元素返回值全部相同,无法得到准确的绝对坐标。SVG的transform写法无法修改,需通过JavaScript获取各text元素的正确绝对坐标。
代码示例
JavaScript代码
//build gridline with svg tooltip with "title" element const widthPoints = []; const heightPoints = []; for (let i = 0; i <= 720; i += 10) { heightPoints.push(i) }; for (let i = 0; i <= 1280; i += 10) { widthPoints.push(i) }; const scaleY = d3.scaleLinear() .range([720, 0]) .domain(d3.extent(heightPoints, d => d)) const scaleX = d3.scaleLinear() .range([0, 1280]) .domain(d3.extent(widthPoints, d => d)); d3.select('svg') .append('g') .attr('class', 'yAxisLeft') .call(d3.axisLeft(scaleY) .ticks(heightPoints.length - 1) .tickSizeInner([(-(1280))]) ); const newHeight = JSON.parse(JSON.stringify(heightPoints)).reverse(); d3.selectAll('body > svg > g.yAxisLeft > g.tick') .each( function(d, i) { const selection = d3.select(this); selection.selectAll('title') .data(function() { return newHeight.filter((a, k) => k === i) }) .join('title') .text((d) => { return 'y Coordinate=' + `${d}` }) } ); d3.select('svg') .append('g').attr('class', 'xAxisBottom') .call(d3.axisBottom(scaleX) .ticks(widthPoints.length - 1) .tickSizeInner([(-(720))]) ) .style('transform', `translateY(720px)`) d3.selectAll('body > svg > g.xAxisBottom > g.tick') .each( function(d, i) { const selection = d3.select(this); selection.selectAll('title') .data([d]) .join('title') .text((d) => { return 'x Coordinate=' + `${d}` }) } ); // return x,y coordinate of each item as a result of all transform const svg = document.querySelector('svg'); const matrixViewBox = svg.getCTM(); const circCoord = (element) => { const matrixElement = element.getCTM(); const x = element.cx.baseVal.value; // takes care of unit conversion const y = element.cy.baseVal.value; const matrixFromElementToVbox = matrixViewBox.inverse().multiply(matrixElement); const coord = new DOMPoint(x, y).matrixTransform(matrixFromElementToVbox); return coord; }; const circleVal =[]; document.querySelectorAll('circle') .forEach( (a) => { circleVal.push(circCoord(a)); } ); console.log(circleVal); const textVal =[]; const textCoord = (element) => { const matrixElement = element.getCTM(); const x = element.x.baseVal.value; // takes care of unit conversion const y = element.y.baseVal.value; const matrixFromElementToVbox = matrixViewBox.inverse().multiply(matrixElement); const coord = new DOMPoint(x, y).matrixTransform(matrixFromElementToVbox); return coord; }; document.querySelectorAll('#textGroup > g > text') .forEach( (a) => { textVal.push(textCoord(a)); } ); console.log(textVal);
HTML代码
<!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>Document</title> </head> <script type="text/javascript" src="https://d3js.org/d3.v7.min.js"></script> <body> <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 1280 720"> <rect class="vBoxRect" width="1280" height="720" fill="#EFEFEF"></rect> <g transform="translate(10,10)"> <g transform="translate(100 100)"> <circle class="circ1" r="20" cx="25" cy="25" fill="green" transform="translate (100,400) scale(2)" /> </g> </g> <g transform="translate(10,10)"> <g transform="translate(100 100)"> <circle class="circ2" r="20" cx="25" cy="25" fill="red" transform="translate (-100,-40) scale(2)" /> </g> </g> <g id="textGroup" transform="translate(400,300)"> <g class="test"> <text x="-247.890625" y="-60" transform="translate(-247.890625,-60)translate(247.890625,60)">P</text> <text x="-227.015625" y="-60" transform="translate(-227.015625,-60)translate(227.015625,60)">i</text> <text x="-210.09375" y="-60" transform="translate(-210.09375,-60)translate(210.09375,60)">z</text> <text x="-193.171875" y="-60" transform="translate(-193.171875,-60)translate(193.171875,60)">z</text> <text x="-181.296875" y="-60" transform="translate(-181.296875,-60)translate(181.296875,60)">a</text> </g> </g> </svg> <script type="text/javascript" src="prod.js"> </script> </body> </html>
错误现象
textVal返回结果完全一致,无视每个text元素x属性的差异:
[ {"x": 400,"y": 300,"z": 0,"w": 1}, {"x": 400,"y": 300,"z": 0,"w": 1}, {"x": 400,"y": 300,"z": 0,"w": 1}, {"x": 400,"y": 300,"z": 0,"w": 1}, {"x": 400,"y": 300,"z": 0,"w": 1} ]
问题原因
circle元素的cx/cy是单个SVGAnimatedLength值,直接取.baseVal.value有效;但text元素的x/y是SVGAnimatedLengthList(支持多段文本坐标),直接访问.baseVal.value会获取到无效值(实际为undefined,计算时被当作0处理),最终所有text元素的坐标计算都基于(0,0),加上父元素的translate(400,300),结果全部相同。
解决方案
修改textCoord函数,正确获取text元素x/y列表中的第一个坐标值:
const textCoord = (element) => { const matrixElement = element.getCTM(); // text的x、y为长度列表,需取第一个元素的值 const x = element.x.baseVal.getItem(0).value; const y = element.y.baseVal.getItem(0).value; const matrixFromElementToVbox = matrixViewBox.inverse().multiply(matrixElement); const coord = new DOMPoint(x, y).matrixTransform(matrixFromElementToVbox); return coord; };
修改后即可得到每个text元素相对于SVG viewBox的正确绝对坐标,例如第一个text的坐标为(152.109375, 240),与预期一致。
内容的提问来源于stack exchange,提问作者smpa01
相关产品推荐
相关产品推荐

