如何获取SVG文档内元素的显示偏移?为何getCTM()方法失效?
问题描述
- 如何获取SVG文档中元素的显示偏移?尤其是将SVG作为独立文档加载到内存后,获取其中元素的偏移。尝试使用
SVGGraphicsElement.getCTM()方法获取<g>标签的变换矩阵,但该方法在这种加载方式下不可用。
测试代码:
let content = new DOMParser().parseFromString(` <svg> <g id="layer1"> <rect id="rect1" style="fill:#ffffff;stroke:#000000;stroke-width:1.67619;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" class="hub hub-container" width="352" height="117.33334" x="0.19999915" y="0.19999835"/> </g> </svg>`, "image/svg+xml" ); let rect = content.querySelector("g.port_1"); let offset = ?
- 以下测试代码在Chrome 115中,第一个
<g>元素能正常打印变换矩阵,但第二个通过DOMParser加载后添加到页面的<g>元素调用getCTM()时,抛出“Uncaught TypeError: g2.getCTM is not a function”错误,原因是什么?
测试代码:
<!DOCTYPE html> <html> <body> <div> <svg width="1000" height="1000" style="width: 100vw; height: 50vh;"> <g> <rect style="fill:#ffffff;stroke:#000000;stroke-width:1.67619;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" class="hub" width="352" height="117.33334" x="0.19999915" y="0.19999835" id="rect14" /> </g> </svg> </div> <div class="script-container"></div> <script type="text/javascript"> let g = document.body.querySelector("g"); let rect = g.querySelector("rect"); console.log(g.getCTM()); let svg = ` <svg width="1000" height="1000" style="width: 100vw; height: 50vh;"> <g class="test-class"> <rect style="fill:#ffffff;stroke:#000000;stroke-width:1.67619;stroke-linecap:round;stroke-miterlimit:4;stroke-dasharray:none" class="hub" width="352" height="117.33334" x="0.19999915" y="0.19999835" id="rect14" /> </g> </svg> `; let content = new DOMParser().parseFromString(svg, "image/svg+xml"); document.querySelector("div.script-container").appendChild(content.documentElement); let g2 = document.body.querySelector("g.test-class"); console.log(g2.getCTM()); </script> </body> </html>
原因分析
- 用
DOMParser以"image/svg+xml"类型解析SVG时,生成的是独立的SVG文档,其中的元素属于SVG命名空间。直接将这些元素插入HTML文档后,浏览器不会自动将其转换为HTML环境下的SVG元素对象,导致元素无法继承HTML环境中SVG元素的方法(如getCTM())。 - 第一个
<g>元素是直接写在HTML内的SVG节点,属于当前HTML文档的一部分,元素对象是标准的SVGGElement类型,自然拥有getCTM()方法;而第二个<g>元素来自独立的SVG文档,插入HTML后仍属于外部SVG命名空间,未被正确绑定到当前HTML环境的SVG接口,因此getCTM()方法不存在。
解决方案
方法1:改用HTML命名空间解析SVG
将DOMParser的解析类型改为"text/html",生成的元素直接属于当前HTML文档环境,会自动实现SVG相关接口:
let content = new DOMParser().parseFromString(svg, "text/html"); document.querySelector("div.script-container").appendChild(content.querySelector("svg"));
方法2:在独立SVG文档内先获取CTM
如果需要先在内存中计算偏移,可在将元素插入HTML前,直接在独立的SVG文档内调用getCTM():
let content = new DOMParser().parseFromString(svg, "image/svg+xml"); let g2 = content.querySelector("g.test-class"); // 在独立SVG文档内获取变换矩阵 let ctm = g2.getCTM(); console.log(ctm); // 再将元素插入HTML document.querySelector("div.script-container").appendChild(content.documentElement);
方法3:手动创建SVG命名空间元素并导入内容
通过document.createElementNS()创建当前HTML文档下的SVG命名空间元素,再将解析后的内容导入,确保元素接口正常:
let content = new DOMParser().parseFromString(svg, "image/svg+xml"); // 创建当前文档下的SVG元素 let svgElement = document.createElementNS("http://www.w3.org/2000/svg", "svg"); // 复制原SVG的属性 Array.from(content.documentElement.attributes).forEach(attr => { svgElement.setAttribute(attr.name, attr.value); }); // 复制原SVG的子元素 while (content.documentElement.firstChild) { svgElement.appendChild(content.documentElement.firstChild); } // 插入到页面 document.querySelector("div.script-container").appendChild(svgElement); // 此时调用getCTM()正常 let g2 = svgElement.querySelector("g.test-class"); console.log(g2.getCTM());
获取元素显示偏移的通用方法
要获取元素相对于SVG视口或页面的绝对偏移,可结合getCTM()和SVGPoint计算:
// 假设element是有效的SVGGraphicsElement let ctm = element.getCTM(); let svg = element.ownerSVGElement; let point = svg.createSVGPoint(); // 取元素自身的x、y属性作为起始点 point.x = parseFloat(element.getAttribute("x")) || 0; point.y = parseFloat(element.getAttribute("y")) || 0; // 应用变换矩阵得到绝对偏移 let offset = point.matrixTransform(ctm); // offset.x 和 offset.y 即为元素相对于SVG视口的绝对位置
内容的提问来源于stack exchange,提问作者Tom
相关产品推荐
相关产品推荐

