cateyes
cateyes

Reputation: 6158

The applying order of SVG transforms

In W3C's spec, it says:

The value of the ‘transform’ attribute is a <transform-list>, which is defined as a list of transform definitions, which are applied in the order provided.

...

If a list of transforms is provided, then the net effect is as if each transform had been specified separately in the order provided

When I tried out the follow markups in firefox, chrome and IE10, all three rendered by doing translate first, then following by rotate! See the codepen snippet. What have I missed here? Or the implementations from the 3 browsers are not W3C compliant?

<svg width="180" height="200"
  xmlns="http://www.w3.org/2000/svg" 
  xmlns:xlink="http://www.w3.org/1999/xlink">

  <!-- This is the element before translation and rotation are applied -->
  <rect x="0" y="0" height="100" width="100" style="stroke:#000; fill: #0086B2" fill-opacity=0.2 stroke-opacity=0.2></rect>

  <!-- Now we add a text element and apply rotate and translate to both -->
  <rect x="0" y="0" height="100" width="100" style="stroke:#000; fill: #0086B2" transform="rotate(45 100 50) translate(50)"></rect>
</svg>

Upvotes: 28

Views: 7498

Answers (3)

Uber Kluger
Uber Kluger

Reputation: 502

TLDR; All implementations are wrong but too many sites would break if it were fixed.

The spec has been totally misinterpreted by all browsers (and those here who say it hasn't).

If a list of transforms is provided, then the net effect is as if each transform had been specified separately in the order provided.

This can be interpreted two ways:

  1. The transforms are defined separately "in the order listed". This would produce the nested example given by @robert-longson.
<g transform="translate(-10,-20) scale(2) rotate(45) translate(5,10)">
  <!-- graphics elements go here -->
</g>

is equivalent to

<g transform="translate(-10,-20)">
  <g transform="scale(2)">
    <g transform="rotate(45)">
      <g transform="translate(5,10)">
        <!-- graphics elements go here -->
      </g>
    </g>
  </g>
</g>
  1. The transforms are applied separately "in the order listed". This would produce the inverse nesting where the inner-most definition applied the first listed transformation.
<g transform="translate(-10,-20) scale(2) rotate(45) translate(5,10)">
  <!-- graphics elements go here -->
</g>

is equivalent to

      <g transform="translate(5,10)">
    <g transform="rotate(45)">
  <g transform="scale(2)">
<g transform="translate(-10,-20)">
        <!-- graphics elements go here -->
      </g>
    </g>
  </g>
</g>

(Note: didn't reformat the indenting to clearly show order reversal.)

I suspect the confusion occurs because ALL transformations can be performed by an appropriate matrix multiplication (as shown in the matrix transform operation specification). However, when applying consecutive transforms by this method, the matrix multiplications must be performed in reverse order (at least insofar as it would be described by common matrix notation).

That is, to transform point P by applying the transform matrices in the (applied) order A then B then C, the common notation would be:

C x B x A x P

I suspect that implementers have simply converted each individual transformation into its corresponding matrix and then multiplied them in the same order thereby resulting in the transforms being applied in reverse order of listing, ignoring/unaware of the fact that matrix multiplication is not commutative, i.e. A x B != B x A (mostly).

To counter those who would contend that each interpretation is equally valid just as long as everyone is on the same page (O.P.'s confusion not withstanding), I present an argument that supports the second interpretation.

Consider the use element. It has attributes/properties x and y (among others). These are defined in the spec:

The x and y properties define an additional transformation (translate(x,y), where x and y represent the computed value of the corresponding property) to be applied to the ‘use’ element, after any transformations specified with other properties (i.e., appended to the right-side of the transformation list).

Note the wording, the translate(x,y) transformation is to be applied after any other transformations as if it were "appended to the right-side of the transformation list". This strongly suggests that the intent is that the transformations are applied in the order listed (left to right). To further reinforce this argument, consider the following example:

<defs>
<polygon id="diode" points="25,29 25,0 -25,-25 -25,25 25,0 25,-29" fill="black" />
<!-- a diode circuit symbol pointing left centered at 0,0 -->
</defs>
<use href="#diode" transform="rotate(90)" x=300 y=300 />

It would seem clear here that the intent is to rotate the symbol 90° (pointing down) and then render it centred at 300,300. The x and y properties specify an equivalent transform of translate(300,300) which is to be applied after any other transformation (the rotate(90)). Indeed, this gives the desired result. However, the alternative of placing the translate at the right-most end of the transformation list (resulting in transform="rotate(90) translate(300,300)") and then using the current implementations (i.e. right to left application) results in the symbol being rendered correctly pointing down but at -300,300 (outside the viewBox). What about if I wanted the symbol twice as big (at 300,300). I could just do

<use href="#diode" transform="rotate(90) scale(2)" x=300 y=300 />

By the applied after interpretation, this gives exactly the desired result. However, the alternative interpretation (right append and right to left application) results in scale(2) applying after the translate(300,300) so the symbol ends up at -600,600.

In order to achieve the desired result with current implementations, x and y are either not used (moved into the transform property at the left-most end):

<use href="#diode" transform="translate(300,300) rotate(90)" />

or must include the effect of any transform

<use href="#diode" transform="rotate(90)" x=300 y=-300 />
<!-- or anchor the rotate -->
<use href="#diode" transform="rotate(90,300,300) x=300 y=300 />

All three of these are convoluted and/or counter-intuitive. The idea that you have some shape, you transform that shape and THEN you draw it somewhere in your viewBox is clearly the most obvious interpretation.

My suspicion is that, indeed, all implementers got it wrong (either independently or by copying the error made by whoever implemented it first). Unfortunately, it wasn't fixed quickly enough and has entered into the class of bugs that have so many active work-arounds that fixing it now (just to be consistent with the spec.) would break too many sites so it is just left as legacy cruft. Probably the only possible fix would be to introduce two new properties (e.g. dx and dy) which perform the translate as intended (applied last).


As always, after spending several hours explaining my reasoning, I found the definitive answer in a single quote, thanks to the comment by @JamesHoux

The spec does offer a more detailed explanation that helps clear things up: w3.org/TR/SVG11/coords.html#NestedTransformations – JamesHoux CommentedOct 26, 2022 at 4:13

While the link provided gives a definition of the behaviour of nested transforms (not in contention here), a quick glance at the next item 7.6 The ‘transform’ attribute reveals all.

The value of the ‘transform’ attribute is a <transform-list>, which is defined as a list of transform definitions, which are APPLIED in the order provided.

Upvotes: -1

Chandu D
Chandu D

Reputation: 501

If you want to sequentially change these transformations 
you have to try this one

<g transform="translate(-10,-20)  onmouseover=changeTransform(evt)">

function changeTransfrom(evt)
{
var id=evt.target;
id.setAttribute('transform',scale(0.5));
id.setAttribute('transform',rotate(30));
id.setAttribute('transform',skewY(45));
id.setAttribute('transform',matrix(2,2));
}

Upvotes: -1

Robert Longson
Robert Longson

Reputation: 123985

The specification is pretty clear about the order which is that the rightmost transform is applied first.

If a list of transforms is provided, then the net effect is as if each transform had been specified separately in the order provided.

I.e,

<g transform="translate(-10,-20) scale(2) rotate(45) translate(5,10)">
  <!-- graphics elements go here -->
</g>

is functionally equivalent to:

<g transform="translate(-10,-20)">
  <g transform="scale(2)">
    <g transform="rotate(45)">
      <g transform="translate(5,10)">
        <!-- graphics elements go here -->
      </g>
    </g>
  </g>
</g>

Upvotes: 29

Related Questions