SVG同变换矩阵下合并对象:计算tspan的x、y坐标
解决SVG倾斜变换矩阵下的tspan坐标计算问题
我之前也踩过类似的SVG变换合并的坑,你的核心问题其实是如何在共用父变换矩阵的情况下,计算子元素的本地坐标以保持原屏幕位置——本质是解一个简单的线性方程组,这是覆盖所有仿射变换(包括倾斜)的通用解法,而你之前的代码只处理了无倾斜的特殊场景,所以在倾斜矩阵下失效了。
先理清SVG变换矩阵的核心公式
SVG的matrix(a,b,c,d,e,f)变换对应的屏幕坐标计算规则是:
屏幕X = a * 本地X + c * 本地Y + e 屏幕Y = b * 本地X + d * 本地Y + f
这里的「本地坐标」是元素相对于父元素的坐标,「屏幕坐标」是最终渲染在视口中的位置。
你的场景推导
你有两个text元素:
- 第一个text用变换矩阵
M1 = [a, b, c, d, e1, f1],它的本地原点(0,0)最终渲染在屏幕位置(e1, f1)(带入上面公式可直接得出) - 第二个text用变换矩阵
M2 = [a, b, c, d, e2, f2],它的本地原点(0,0)最终渲染在屏幕位置(e2, f2)
现在要把第二个text改成第一个text下的tspan,要求这个tspan的本地坐标(x_tspan, y_tspan)经过M1变换后,屏幕位置和原第二个text的原点完全一致。也就是要满足:
a*x_tspan + c*y_tspan + e1 = e2 b*x_tspan + d*y_tspan + f1 = f2
解方程组得通用公式
把方程整理成标准二元一次方程组:
a*x + c*y = e2 - e1 b*x + d*y = f2 - f1
用克莱姆法则求解即可(只要变换矩阵可逆,也就是行列式D = a*d - b*c ≠ 0,所有合法的SVG仿射变换都满足这个条件):
- 计算行列式:
D = a*d - b*c - 计算x的分子:
Dx = (e2 - e1)*d - c*(f2 - f1) - 计算y的分子:
Dy = a*(f2 - f1) - b*(e2 - e1) - 最终坐标:
x = Dx/D,y = Dy/D
可直接复用的代码
用JavaScript实现的话,代码如下(假设你已经能解析出变换矩阵的六个参数):
// 解析第一个text的变换矩阵参数 [a, b, c, d, e1, f1] const [a, b, c, d, e1, f1] = parseMatrixTransform(firstText.getAttribute("transform")); // 解析第二个text的变换矩阵参数(前四位和第一个一致,只取平移项e2,f2) const [_, __, ___, ____, e2, f2] = parseMatrixTransform(secondText.getAttribute("transform")); // 计算行列式(确保不为0,SVG合法变换都满足) const determinant = a * d - b * c; if (Math.abs(determinant) < 1e-9) throw new Error("不可逆变换矩阵"); // 计算tspan的x和y坐标 const tspanX = ((e2 - e1) * d - c * (f2 - f1)) / determinant; const tspanY = (a * (f2 - f1) - b * (e2 - e1)) / determinant; // 创建tspan并设置坐标 const tspan = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); tspan.setAttribute("x", tspanX.toFixed(3)); tspan.setAttribute("y", tspanY.toFixed(3)); tspan.textContent = secondText.textContent; firstText.appendChild(tspan);
为什么之前的代码失效?
你原来的代码是针对无倾斜场景(比如b=0或c=0)的简化版,当倾斜矩阵的b和c都不为零时,简化公式就不再成立,而通用的克莱姆法则解法能覆盖所有合法的仿射变换(缩放、旋转、倾斜、平移的任意组合)。
测试示例(倾斜矩阵场景)
假设:
- M1:
matrix(1, 0.5, 0.5, 1, 100, 200)(倾斜变换,平移到(100,200)) - M2:
matrix(1, 0.5, 0.5, 1, 300, 400)(同倾斜参数,平移到(300,400))
计算得:
- 行列式D = 11 - 0.50.5 = 0.75
- tspanX = ((300-100)1 - 0.5(400-200)) / 0.75 = (200 - 100)/0.75 ≈ 133.333
- tspanY = (1*(400-200) - 0.5*(300-100)) / 0.75 = (200 - 100)/0.75 ≈ 133.333
把tspan的x和y设为这个值,渲染位置会和原第二个text完全一致。
内容的提问来源于stack exchange,提问作者Jacob Scheib
相关产品推荐
相关产品推荐

