SVG 元素的自定义 transform origin
不要使用 transform-origin 属性
SVG 支持 transform
,而且写法似乎与 CSS 中相同,但是它的标准里并不支持 transform-origin
属性。虽然在部分浏览器中,给 SVG 元素指定 transform-origin
似乎是有效果的(写法和结果也与 CSS 一样),但是无法指望这个行为在所有浏览器里都有效。
自行解释 transformOrigin
既然不能通过 attribute 来指定变换原点,我们只好通过对其他 transform 值的变动来实现想要的效果了。
首先建立一个对象系统
class Display {
x: number = 0
y: number = 0
width: number
height: number
scale: [number, number] = [1, 1]
transformOrigin?: [number, number]
parent?: Display
element: SVGElement
constructor() {
this.element = this.createElement() as any
}
createElement() {
return document.createElement('g')
}
addChild(child: Display) {
child.parent = this
this.element.appendChild(child.element)
}
}
class Rect extends Display {
createElement() {
return document.createElement('rect')
}
}
此时有
const r1 = new Rect({
name: "r1",
x: 10,
y: 10,
width: 100,
height: 50,
})
const r2 = new Rect({
name: "r2",
x: 10,
y: 10,
width: 100,
height: 50,
scale: [2, 2],
})
经过简单的属性到 dom 的操作,得到
<svg>
<rect name="r1" transform="translate(10,10)" width="100" height="50" fill="blue" opacity="0.8" />
<rect name="r2" transform="translate(10,10) scale(2,2)" width="100" height="50" fill="red" opacity="0.8" />
</svg>
r2 的变换,先平移再缩放,平移的结果就是缩放的原点。
此处将 x/y 转为 translate 而不是 x
和 y
属性,是为了以统一的方式做坐标系的转换和运算,且考虑到许多元素没有 x
和 y
属性(如 circle
就只有 cx
和 cy
),但所有 SVG 元素都支持 transform 。
function formTransform(d: Display) {
const scales = d.scale
const scaleX = scales[0]
const scaleY = scales[1]
return `translate(${d.x},${d.y}) scale(${scaleX},${scaleY})`
}
带位移补偿的缩放
计算缩放的位移补偿值,使得缩放再位移后效果就与以变换原点为中心缩放一样。
假设在缩放系数为 S 时,我们需要的 translate 为 ,变换完的结果:
当以变换原点为特征点时,方程易于构建与求解。
令 和 为变换原点相对于原坐标系左上角的坐标,当 时,代入得到:
所以:
带缩放修正值的 transform 计算方法改为:
const scales = d.scale
const scaleX = scales[0]
const scaleY = scales[1]
- return `translate(${d.x},${d.y}) scale(${scaleX},${scaleY})`
+
+ let xToOrigin = d.width / 2
+ let yToOrigin = d.height / 2
+ if (d.transformOrigin) {
+ xToOrigin = d.transformOrigin[0]
+ yToOrigin = d.transformOrigin[1]
+ }
+ const revisedX = (1 - scaleX) * xToOrigin
+ const revisedY = (1 - scaleY) * yToOrigin
+
+ return `translate(${d.x},${d.y}) scale(${scaleX},${scaleY}) translate(${revisedX},${revisedY})`
}
例如一个缩放为2倍,
const r3 = new Rect({
x: 50,
y: 50,
width: 100,
height: 50,
scale: [2, 2],
transformOrigin: [50, 25],
})
对应于缩放的变换应该是 scale(2,2) translate(-25,-12.5)
,再加上元素本身的位移,最后得到:
<svg>
<rect name="r3" transform="translate(50,50) scale(2,2) translate(-25,-12.5)" width="100" height="50"/>
</svg>
变换过程示意: