如何在SVG中设置变换原点

134

我需要使用JavaScript调整SVG文档中某些元素的大小和旋转。问题是,默认情况下它总是在原点(0,0)——左上角周围应用变换。

如何重新定义此变换锚点?

我尝试使用transform-origin属性,但它没有影响任何内容。

这是我的做法:

svg.getDocumentById('someId').setAttribute('transform-origin', '75 240');

尽管我可以在Firefox中看到属性被正确设置,但它似乎没有将关键点设置为我指定的点。我尝试了center bottom50% 100%等不同的方式,有些加括号,有些不加。目前为止都没用。

有人能帮忙吗?


顺便说一句,据说在Firefox 19 beta 3中已经修复了,但我在Firefox 22中仍然遇到了问题。Mozilla bugzilla列表:https://bugzilla.mozilla.org/show_bug.cgi?id=828286 - Toph
@CTheDark,您能否重新考虑一下这个问题的已接受答案?我们现在有一个更现代化的解决方案:https://dev59.com/H2w15IYBdhLWcg3wSJzQ#62720107 - George
8个回答

174

要进行旋转,请使用 transform="rotate(deg, cx, cy)",其中 deg 是您想要旋转的角度,(cx, cy) 定义了旋转中心。

对于缩放/调整大小,您必须先按 (-cx, -cy) 进行平移,然后进行缩放,最后再平移回 (cx, cy)。您可以使用 矩阵变换 来完成这个过程:

transform="matrix(sx, 0, 0, sy, cx-sx*cx, cy-sy*cy)"

其中sx是x轴上的缩放因子,sy是y轴上的缩放因子。


6
难道不应该是 transform="matrix(sx, 0, 0, sy, cx-sxcx, cy-sycy)" 吗?因为你只想要通过差异进行翻译。我认为这个逻辑是正确的,但仍然给了我错误的位置... - CTheDark
1
@JayStevens 是的,矩阵变换对于任何系统都适用,这只是数学问题。唯一的区别可能在于符号表示法。据我所知,CSS 矩阵变换使用与 SVG 相同的符号表示法,因此相同顺序的六个数字应该可以使用。 - Peter Collingridge
1
只是为了澄清,cx和cy需要是你要缩放的盒子中心的偏移量。这使我困惑了一段时间,因为我在考虑CSS盒模型。@forresto在他下面的答案中提到了这一点。 - Jason Farnsworth
@PeterCollingridge 感谢您提供的信息。我想根据鼠标移动调整svg元素的大小。我应该如何计算sx,sy? - Ashok kumar
@PeterCollingridge 这是否意味着当我也应用 translate 时,矩阵会变成这样 transform="matrix(sx, 0, 0, sy, tx+cx-sx*cx, ty+cy-sy*cy)" - thednp
显示剩余6条评论

127

全新的2020解决方案

svg * { 
  transform-box: fill-box;
}

应用transform-box: fill-box将使SVG内的元素表现为普通的HTML元素。然后,您可以像平常一样应用transform-origin: center(或其他内容)

没错,就是transform-box: fill-box。现在,不需要任何复杂的矩阵操作了。


20
非常有用的解决方案,应该得到更高的赞同。这正是我所需要的,谢谢!在转换和过渡时,SVG和HTML元素存在微妙的差异。我曾经因为一个动画错误困扰了数小时,直到找到这个答案...通过使用transform-box: fill-box;,你可以以一种合理的方式使SVG元素遵守transform-origin,就像你所期望的那样! - zfogg
2
放心吧,伙计!当我找到它时(感谢@Robert Longson)我也有同样的解脱感!!它解决了我在尝试控制SVG地图上的点时遇到的巨大头痛:sweat: :sweat: :sweat: - George
4
请注意:transform-box在2017年完全引入,比这个问题被提出和第一次回答的时间晚了6年。因此,你可能需要原谅那些“错过这个技巧”的人。 - SimplSam
1
嘿 SimplSam,我已经更新了答案,使其更加友好地面向过去。 - George
3
这是最好的答案。 - Bugbeeb
显示剩余4条评论

22
如果您可以使用固定值(而不是“center”或“50%”),则可以使用CSS:
-moz-transform-origin: 25px 25px;
-ms-transform-origin:  25px 25px;
-o-transform-origin: 25px 25px;
-webkit-transform-origin:  25px 25px;
transform-origin: 25px 25px;

一些浏览器(例如Firefox)无法正确处理相对值。


1
哇,如果可以的话我会给+10分。这个救了我的一天!谢谢。 - Cullub
如何在JavaScript中只做这个? - Andrew S
1
element.setAttribute('transform-origin', '25px 25px') 这样的吗? - nikk wong

15

如果不想使用matrix变换来进行缩放:


transform="translate(cx, cy) scale(sx sy) translate(-cx, -cy)"

以下是 CSS 代码:

transform: translate(cxpx, cypx) scale(sx, sy) translate(-cxpx, -cypx)

14

如果您和我一样想要使用transform-origin来进行平移和缩放,那么您需要更多的帮助。

// <g id="view"></g>
var view = document.getElementById("view");

var state = {
  x: 0,
  y: 0,
  scale: 1
};

// Origin of transform, set to mouse position or pinch center
var oX = window.innerWidth/2;
var oY = window.innerHeight/2;

var changeScale = function (scale) {
  // Limit the scale here if you want
  // Zoom and pan transform-origin equivalent
  var scaleD = scale / state.scale;
  var currentX = state.x;
  var currentY = state.y;
  // The magic
  var x = scaleD * (currentX - oX) + oX;
  var y = scaleD * (currentY - oY) + oY;

  state.scale = scale;
  state.x = x;
  state.y = y;

  var transform = "matrix("+scale+",0,0,"+scale+","+x+","+y+")";
  //var transform = "translate("+x+","+y+") scale("+scale+")"; //same
  view.setAttributeNS(null, "transform", transform);
};

这里是它的工作示例:http://forresto.github.io/dataflow-prototyping/react/


你的 GitHub 链接返回 404 错误。 - NuclearPeon
你好 @NuclearPeon,这很棒,但我似乎无法在我的代码中实现它。我想要应用它的SVG元素已经是一个名为“mainGrid”的变量了。我尝试将“view”的实例替换为“mainGrid”,但没有效果。我错过了什么吗?谢谢! - sakeferret
@sakeferret 最明显的检查点是getElementById中的 id 与文档中的id="..."属性相匹配。不看代码很难确定问题所在。如果您不想将其发布为 SO 问题,可以尝试使用属性名称创建最简单的示例,直到它正常工作/找到问题,然后再将其余部分添加回去。 - NuclearPeon

2

对于我来说,在DOM中设置嵌入元素的属性(transform-origin="center")就解决了问题。

          <circle
          fill="#FFFFFF"
          cx="82"
          cy="81.625"
          r="81.5"
          transform-origin="center"
        ></circle>

使用CSS变换效果非常好


这并不总是有效的。在某些情况下,您需要添加“transform-box: fill-box”。请参阅此答案https://dev59.com/H2w15IYBdhLWcg3wSJzQ#62720107。 - George
1
我所说的是在DOM中使用属性(transform-origin="center"),而不是(transform-origin: center)。已在所有现代浏览器中进行测试,而且无需使用fill-box即可正常工作。 - unx

0

我遇到了类似的问题。但是我使用D3来定位我的元素,并希望transformtransition由CSS处理。这是我的原始代码,在Chrome 65中可以工作:

//...
this.layerGroups.selectAll('.dot')
  .data(data)
  .enter()
  .append('circle')
  .attr('transform-origin', (d,i)=> `${valueScale(d.value) * Math.sin( sliceSize * i)} 
                                     ${valueScale(d.value) * Math.cos( sliceSize * i + Math.PI)}`)
//... etc (set the cx, cy and r below) ...

这使我能够在javascript中使用相同的数据设置cxcytransform-origin值。

但是这在Firefox中不起作用!我所要做的是将circle包装在g标签中,并使用上面相同的定位公式来translate它。然后,我将circle附加到g标签中,并将其cxcy值设置为0。从那里开始,transform:scale(2)将按预期从中心缩放。最终代码如下。

this.layerGroups.selectAll('.dot')
  .data(data)
  .enter()
  .append('g')
  .attrs({
    class: d => `dot ${d.metric}`,
    transform: (d,i) => `translate(${valueScale(d.value) * Math.sin( sliceSize * i)} ${valueScale(d.value) * Math.cos( sliceSize * i + Math.PI)})`
  })
  .append('circle')
  .attrs({
    r: this.opts.dotRadius,
    cx: 0,
    cy: 0,
  })

在进行了这个更改之后,我修改了我的 CSS 以针对 circle 而不是 .dot,来添加 transform: scale(2)。我甚至都不需要使用 transform-origin
注意事项:
1. 在第二个例子中,我使用了 d3-selection-multi。这使我可以将一个对象传递给 .attrs,而不是为每个属性重复使用 .attr。 2. 当使用字符串模板时,请注意换行符,如第一个示例所示。这将在输出中包含一个换行符,并可能破坏您的代码。

2
也许如果您设置transform-box: fill-box;,Firefox将按照您的期望工作。 - Robert Longson
尝试过了,似乎没有起作用。在Firefox的“about:config”页面中更改了svg:transform-origin.enabled偏好设置后,它确实起作用了。但是...那似乎不是一个充分的解决方案。我还发现了transform-origin: 50% 50%transform-origin: center center之间存在差异。 - Jamie S
svg.transform-origin.enabled 首选项已经在许多版本之前被移除。除非您使用的是非常旧的版本,否则在50%和中心之间不应该有任何区别。如果在Firefox 59或更高版本中存在差异,请随时提出bugzilla错误报告。 - Robert Longson

0
你可以在0,0原点周围构建任何你想要的东西,然后将其放入一个组<g></g>中,并矩阵平移整个组。 这样,对象将始终围绕0,0旋转,但整个组被移动(平移)到其他地方,并且平移矩阵是在旋转之后应用的。

<g transform="matrix(1 0 0 1 160 200)">
  <polygon id="arrow-A" class="arrow" points="0,4 -90,0 0,-4 "/>
</g>


<style>

.arrow {
  transform: rotate(60deg);
}

/* OR */

#arrow-A {
  transform: rotate(60deg);
}

</style>

OR scripting: 

<script> 
  document.getElementById("arrow-A").setAttribute("transform", "rotate(60)");
</script>

这将创建一个箭头(例如,用于仪表),宽端在[0,0]处,并将其移动到[160, 200]。应用于“arrow”类的任何旋转都将使其围绕[160, 200]旋转。
测试过:Chrome,Firefox,Opera,MS Edge。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接