Web编程技术营地
研究、演示、创新

SVG Transforms

撰写时间:2026-02-19

修订时间:2026-02-20

transform属性

可通过SVG元素的transform属性来设置其变换。该属性值由CSS Transforms Module Level 1规范。

transform属性值所支持的函数有:

  • 平移:translate(<length-percentage>[,<length-percentage>]?)。参数单位为px
  • 缩放:scale(<number>[,<number>]?)
  • 旋转:rotate([<angle>|<zero>])。只支持一个参数,参数单位为deg。仅调用该函数不能像CSS一样可以通过transform-origin属性来指定旋转中心。
  • 沿X 轴扭曲:skewX([<angle>|<zero>])。
  • 沿Y 轴扭曲:skewY([<angle>|<zero>])。
  • 指定矩阵:matrix(<number>[,<number>]{5,5})。6个参数值分别对应于矩阵的a, b, c, d, e, f

CSS不同的是,SVG元素的transform属性值不支持以下变种函数:

  • translateX()
  • translateY()
  • scaleX()
  • scaleY()
  • skew()

translate

下面代码进行平移变换。

g { stroke-dasharray: 5; stroke: gray; fill: rgb(66 66 66 / 0.5); } use { stroke: silver; fill: teal; fill-opacity: 0.5; }

关于SVG 2.0中use元素的样式

为突出平移前与平移后的效果对比,这里使用了use元素来重复引用同一个元素。

使用use元素,将自动产生一个Shadow Tree,并且,所引用元素的CSS样式也将被自动复制到Shadow Tree下面。基于此特点,如果我们直接在rect元素上面设置fillstroke属性值,则这些属性值也将自动复制且不能被覆盖,从而不能产生两种不同的效果。因此,我们不能直接在被引用的rect上面直接设置这些属性值。

解决的方法是,将rect包裹在一个g元素之下,而仅对g元素设置fillstroke属性值。此时,由于rect元素本身无相应属性值,故在Shadow Tree中我们可对use元素设置相应的属性值来表示平移后的效果;而平移前的属性值,由父容器g的属性值自动传递给rect元素。

这样,仅额外使用一个父容器g元素,就可完美解决在引用时需使用两种CSS样式的问题。

translate的平移效果

上面代码,通过将transform的值设置为translate(10, 10),从而进行了平移变换。若省略第二个参数值,则该参数值取零值,如下所示。

g { stroke-dasharray: 5; stroke: gray; fill: rgb(66 66 66 / 0.5); } use { stroke: silver; fill: teal; fill-opacity: 0.5; }

scale

缩放的本质

缩放的本质是,按缩放系数来重新计算SVG元素每个点的位置。

g { stroke-dasharray: 5; stroke: gray; fill: rgb(66 66 66 / 0.5); } use { stroke: silver; fill: teal; fill-opacity: 0.5; }
let rectElement = document.querySelector('#org'); let x = parseInt(rectElement.getAttribute('x')); let y = parseInt(rectElement.getAttribute('y')); let width = parseInt(rectElement.getAttribute('width')); let height = parseInt(rectElement.getAttribute('height')); let left = x; let top = y; let right = left + width; let bottom = top + height; pc.group('before scaling'); pc.log('left: %d', left); pc.log('top: %d', top); pc.log('right: %d', right); pc.log('bottom: %d', bottom); pc.groupEnd(); let scaleFactor = document.querySelector('use').getAttribute('transform'); scaleFactor = parseFloat(scaleFactor.match(/scale\((.+)\)/)[1]); pc.log('%s: ', 'scale factor', scaleFactor); left *= scaleFactor; top *= scaleFactor; right *= scaleFactor; bottom *= scaleFactor; pc.group('after scaling'); pc.log('left: %d', left); pc.log('top: %d', top); pc.log('right: %d', right); pc.log('bottom: %d', bottom); pc.groupEnd();

默认情况下,坐标系原点位于(0, 0)的位置,因此,当参数为正数时,矩形从原点向右向下生长;当参数为负数时,矩形从原点向左向上生长;当参数为0时,矩形将坍缩为一个点。

开始时,矩形左上角的坐标为(70, 70),按0.5倍的系数来缩放,则左上角的坐标变为(35, 35),且其宽高值也缩小为一半。可见,基于坐标轴原点的缩放不仅会改变元素的尺寸大小,还会同时改变元素的起始位置,效果不太直观。

现在,我们准备在元素的中心位置上缩放,这样,缩放后元素的大小及位置均可容易地预先判断,并且缩放后,元素不会跑偏。

很显然,我们同时需要平移及缩放两种变换效果,但问题是,是先平移再缩放,还是先缩放再平移?下面分别考查。

先平移再缩放

第一种方式,将缩放前及缩放后的两个元素都置于一个统一的父容器之下,然后对该父容器统一进行平移,以模拟坐标系原点平移的效果。

g.attrs-provider { stroke-dasharray: 5; stroke: gray; fill: rgb(66 66 66 / 0.5); } use { stroke: silver; fill: teal; fill-opacity: 0.5; }
let rectElement = document.querySelector('#org'); let x = parseInt(rectElement.getAttribute('x')); let y = parseInt(rectElement.getAttribute('y')); let width = parseInt(rectElement.getAttribute('width')); let height = parseInt(rectElement.getAttribute('height')); let left = x; let top = y; let right = left + width; let bottom = top + height; pc.group('before scaling'); pc.log('left: %d', left); pc.log('top: %d', top); pc.log('right: %d', right); pc.log('bottom: %d', bottom); pc.groupEnd(); let scaleFactor = document.querySelector('use').getAttribute('transform'); scaleFactor = parseFloat(scaleFactor.match(/scale\((.+)\)/)[1]); pc.log('%s: ', 'scale factor', scaleFactor); left *= scaleFactor; top *= scaleFactor; right *= scaleFactor; bottom *= scaleFactor; pc.group('after scaling'); pc.log('left: %d', left); pc.log('top: %d', top); pc.log('right: %d', right); pc.log('bottom: %d', bottom); pc.groupEnd();

这种方式,是先在内部进行缩放,然后再由父容器统一进行平移,因此,缩放前后的两个元素的相对位置与上一节的相对位置完全一样,只是缩放完毕后,两个元素再因父容器的原因,整体向右向下平移了10px

第二种方式,先平移缩放前的元素,然后再缩放。

g.attrs-provider { stroke-dasharray: 5; stroke: gray; fill: rgb(66 66 66 / 0.5); } use { stroke: silver; fill: teal; fill-opacity: 0.5; }
let rectElement = document.querySelector('#org'); let x = parseInt(rectElement.getAttribute('x')); let y = parseInt(rectElement.getAttribute('y')); let width = parseInt(rectElement.getAttribute('width')); let height = parseInt(rectElement.getAttribute('height')); let left = x; let top = y; let right = left + width; let bottom = top + height; pc.group('before scaling'); pc.log('left: %d', left); pc.log('top: %d', top); pc.log('right: %d', right); pc.log('bottom: %d', bottom); pc.groupEnd(); let scaleFactor = document.querySelector('use').getAttribute('transform'); scaleFactor = parseFloat(scaleFactor.match(/scale\((.+)\)/)[1]); pc.log('%s: ', 'scale factor', scaleFactor); left *= scaleFactor; top *= scaleFactor; right *= scaleFactor; bottom *= scaleFactor; pc.group('after scaling'); pc.log('left: %d', left); pc.log('top: %d', top); pc.log('right: %d', right); pc.log('bottom: %d', bottom); pc.groupEnd();

可以看出,这种方式下,计算缩放时只取值于缩放前元素的自身数据,效果是,缩放后,源元素再向右向下平移了40px

先缩放再平移

第一步,先求出缩放前后两个元素的中心坐标值。

g.attrs-provider { stroke-dasharray: 5; stroke: gray; fill: rgb(66 66 66 / 0.5); } use { stroke: silver; fill: teal; fill-opacity: 0.5; }
let rectElement = document.querySelector('#org'); let x = parseInt(rectElement.getAttribute('x')); let y = parseInt(rectElement.getAttribute('y')); let width = parseInt(rectElement.getAttribute('width')); let height = parseInt(rectElement.getAttribute('height')); let left = x; let top = y; let right = left + width; let bottom = top + height; pc.group('before scaling'); pc.log('left: %d', left); pc.log('top: %d', top); pc.log('right: %d', right); pc.log('bottom: %d', bottom); pc.log('center: (%f, %f)', left + width / 2, top + height / 2); pc.groupEnd(); let scaleFactor = document.querySelector('use').getAttribute('transform'); scaleFactor = parseFloat(scaleFactor.match(/scale\((.+)\)/)[1]); pc.log('%s: ', 'scale factor', scaleFactor); left *= scaleFactor; top *= scaleFactor; right *= scaleFactor; bottom *= scaleFactor; width = right - left; height = bottom - top; pc.group('after scaling'); pc.log('left: %d', left); pc.log('top: %d', top); pc.log('right: %d', right); pc.log('bottom: %d', bottom); pc.log('center: (%f, %f)', left + width / 2, top + height / 2); pc.groupEnd();

第二步,根据两个元素的中心坐标值计算出偏移值,再对缩放后的元素平移该偏移值。

g.attrs-provider { stroke-dasharray: 5; stroke: gray; fill: rgb(66 66 66 / 0.5); } use { stroke: silver; fill: teal; fill-opacity: 0.5; }
let rectElement = document.querySelector('#org'); let x = parseInt(rectElement.getAttribute('x')); let y = parseInt(rectElement.getAttribute('y')); let width = parseInt(rectElement.getAttribute('width')); let height = parseInt(rectElement.getAttribute('height')); let left = x; let top = y; let right = left + width; let bottom = top + height; let centerX = left + width / 2; let centerY = top + height / 2; pc.group('before scaling'); pc.log('left: %d', left); pc.log('top: %d', top); pc.log('right: %d', right); pc.log('bottom: %d', bottom); pc.log('center: (%f, %f)', centerX, centerY); pc.groupEnd(); let scaleFactor = document.querySelector('use').getAttribute('transform'); scaleFactor = parseFloat(scaleFactor.match(/scale\((.+)\)/)[1]); pc.log('%s: ', 'scale factor', scaleFactor); left *= scaleFactor; top *= scaleFactor; right *= scaleFactor; bottom *= scaleFactor; width = right - left; height = bottom - top; let scaledCenterX = left + width / 2; let scaledCenterY = top + height / 2; pc.group('after scaling'); pc.log('left: %d', left); pc.log('top: %d', top); pc.log('right: %d', right); pc.log('bottom: %d', bottom); pc.log('center: (%f, %f)', scaledCenterX, scaledCenterY); pc.groupEnd(); pc.group('offset'); pc.log('x: %f - %f = %f', centerX, scaledCenterX, centerX - scaledCenterX); pc.log('y: %f - %f = %f', centerY, scaledCenterY, centerY - scaledCenterY); pc.groupEnd();

出问题了:缩放后的元素没有移至缩放前元素的中心点的位置。

问题在于,缩放后的偏移值将受缩放因子的影响而变小。解决方法是,在直接计算出两个中心点偏移值后,需将它们再除以缩放因子。修正如下:

g.attrs-provider { stroke-dasharray: 5; stroke: gray; fill: rgb(66 66 66 / 0.5); } use { stroke: silver; fill: teal; fill-opacity: 0.5; }
let rectElement = document.querySelector('#org'); let x = parseInt(rectElement.getAttribute('x')); let y = parseInt(rectElement.getAttribute('y')); let width = parseInt(rectElement.getAttribute('width')); let height = parseInt(rectElement.getAttribute('height')); let left = x; let top = y; let right = left + width; let bottom = top + height; let centerX = left + width / 2; let centerY = top + height / 2; pc.group('before scaling'); pc.log('left: %d', left); pc.log('top: %d', top); pc.log('right: %d', right); pc.log('bottom: %d', bottom); pc.log('center: (%f, %f)', centerX, centerY); pc.groupEnd(); let scaleFactor = document.querySelector('use').getAttribute('transform'); scaleFactor = parseFloat(scaleFactor.match(/scale\((.+)\)/)[1]); pc.log('%s: ', 'scale factor', scaleFactor); left *= scaleFactor; top *= scaleFactor; right *= scaleFactor; bottom *= scaleFactor; width = right - left; height = bottom - top; let scaledCenterX = left + width / 2; let scaledCenterY = top + height / 2; pc.group('after scaling'); pc.log('left: %d', left); pc.log('top: %d', top); pc.log('right: %d', right); pc.log('bottom: %d', bottom); pc.log('center: (%f, %f)', scaledCenterX, scaledCenterY); pc.groupEnd(); pc.group('offset'); pc.log('x: (%f - %f) / %f = %f', centerX, scaledCenterX, scaleFactor, (centerX - scaledCenterX) / scaleFactor); pc.log('y: (%f - %f) / %f = %f', centerY, scaledCenterY, scaleFactor, (centerY - scaledCenterY) / scaleFactor); pc.groupEnd();

由此,我们得出在元素中心缩放时如何计算偏移值的公式如下:

offsetX = (orgCenter.x - scaledCenter.x) / scaleFactor; offsetY = (orgCenter.y - scaledCenter.y) / scaleFactor;

下面将核心功能提炼为一个名为scaleCenter的函数,传入要引用元素的id以及缩放因子,函数将自动生成缩放后的元素:

g.attrs-provider { stroke-dasharray: 5; stroke: gray; fill: rgb(66 66 66 / 0.5); } use { stroke: silver; fill: teal; fill-opacity: 0.5; }
function scaleCenter(elementId, scaleFactor) { let rectElement = document.querySelector(`#${elementId}`); let x = parseInt(rectElement.getAttribute('x')); let y = parseInt(rectElement.getAttribute('y')); let width = parseInt(rectElement.getAttribute('width')); let height = parseInt(rectElement.getAttribute('height')); let orgCenterX = x + width / 2; let orgCenterY = y + height / 2; pc.group('before scaling'); pc.log(x, y, width, height, orgCenterX, orgCenterY); pc.groupEnd(); pc.log('%s: ', 'scale factor', scaleFactor); let scaledX = x * scaleFactor; let scaledY = y * scaleFactor; let scaledWidth = width * scaleFactor; let scaledHeight = height * scaleFactor; let scaledCenterX = scaledX + scaledWidth / 2; let scaledCenterY = scaledY + scaledHeight / 2; pc.group('after scaling'); pc.log(scaledX, scaledY, scaledWidth, scaledHeight, scaledCenterX, scaledCenterY); pc.groupEnd(); let translateX = (orgCenterX - scaledCenterX) / scaleFactor; let translateY = (orgCenterY - scaledCenterY) / scaleFactor; pc.group('offset'); pc.log(translateX, translateY); pc.groupEnd(); let svgElement = document.querySelector('svg'); let useElement = document.createElementNS('http://www.w3.org/2000/svg', 'use'); useElement.setAttribute('href', `#${elementId}`); useElement.setAttribute('transform', `scale(${scaleFactor}) translate(${translateX},${translateY})`); svgElement.appendChild(useElement); } scaleCenter('org', 0.5);

随意修改要缩放的元素的位置及大小,修改缩放因子,然后再运行,所自动生成的元素的中心位置应能与原来的元素的中心位置始终对齐。

多种变换属性值

svg { > rect { fill: teal; stroke: silver; } }

上面代码依序进行了平移、旋转、缩放的变换。

transform属性

可通过SVG元素的transform属性来设置其变换。

let rectElement = document.querySelector('svg > rect'); pc.log(rectElement.transform); pc.log(rectElement.getAttribute('transform'));
svg { > rect { fill: teal; stroke: silver; } }

参考资源

  1. SVG2: The 'transform' property
  2. CSS Transforms Module Level 1
  3. SVG transform functions
  4. The 'use' element