You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 16:24:14