css - Почему порядок преобразований имеет значение? SVG rotate/scale не дает такой же результат, как масштабирование/поворот




css3 css-transforms (2)

То, как Темани Афиф объяснил это, следует системам координат, которые охватывает каждое преобразование. Вы начинаете с области просмотра, и каждая последовательная система координат получается и располагается где-то по-другому на холсте. Эти системы координат могут оказаться не декартовыми («растянутая вселенная»). Они создаются в дереве DOM снаружи, а когда они связаны в атрибуте, слева направо.

Но вы можете представить то же преобразование и в противоположном направлении, изнутри: сначала вы рисуете прямоугольник в его декартовой системе координат пространства пользователя, а затем преобразуете его с помощью цепочки шкал, поворотов и т. Д. До тех пор, пока не начертите его. в системе координат окна просмотра он искажается чем-то другим.

Но если вы посмотрите на это вторым способом, цепочечные преобразования в атрибуте нужно обрабатывать справа налево: transform: scale(2, 1) rotate(10deg) означает взять прямоугольник, сначала поверните его на 10deg, а затем масштабировать повернутый прямоугольник в горизонтальном направлении.

Короче говоря, эти два эквивалентны:

  • Если вы рисуете графику в преобразованной системе координат , создайте систему координат, применяя преобразования к этим системам координат слева направо .
  • Если вы рисуете преобразованный график в исходной системе координат, создайте график, применяя преобразования к графику справа налево .

После изучения спецификации SVG и таких руководств, как this и this , я все еще пытаюсь понять, как именно работают преобразования цепочек.

Избранные релевантные цитаты

Когда вы применяете атрибут transform к элементу SVG, этот элемент получает «копию» текущей используемой пользовательской системы координат.

А также:

Когда преобразования связаны друг с другом, самое важное, о чем следует помнить, это то, что, как и при преобразованиях элементов HTML, каждое преобразование применяется к системе координат после преобразования этой системы предыдущими преобразованиями.

А также:

Например, если вы собираетесь применить вращение к элементу с последующим переводом, перевод происходит в соответствии с новой системой координат, а не с исходной, не повернутой.

А также:

Последовательность преобразований имеет значение. Последовательность, в которой функции преобразования указаны внутри атрибута transform, является последовательностью, к которой они применяются к фигуре.

Код

Текущая система координат первого прямоугольника масштабируется, а затем поворачивается (обратите внимание на порядок). Текущая система координат второго прямоугольника поворачивается, затем масштабируется.

svg {
  border: 1px solid green;
}
<svg xmlns="http://www.w3.org/2000/svg">
  <style>
    rect#s1 {
      fill: red;
      transform: scale(2, 1) rotate(10deg);
    }
  </style>
  <rect id="s1" x="" y="" width="100" height="100" />
</svg>

<svg xmlns="http://www.w3.org/2000/svg">
  <style>
    rect#s2 {
      fill: blue;
      transform: rotate(10deg) scale(2, 1);
    }
  </style>
  <rect id="s2" x="" y="" width="100" height="100" />
</svg>

Вопрос

Мы знаем, что когда мы объединяем преобразования, делается копия текущей системы координат, используемой для этого элемента, тогда преобразования применяются в указанном порядке.

Когда у нас есть пользовательская система координат, которая уже масштабирована, и мы применяем к ней вращение, прямоугольник (как видно) эффективно перекошен (обратите внимание на измененные углы). Этого не произойдет, если мы сделаем два преобразования наоборот (вращение, затем масштабирование).

Экспертная помощь о том, как именно масштабированная текущая система координат вращается, будет высоко ценится. Я пытаюсь понять с технической (внутренней рабочей) точки зрения, почему именно перекос происходит в первом прямоугольнике.

Спасибо.


Чтобы проиллюстрировать, как это работает, давайте рассмотрим анимацию, чтобы показать, как эффект масштабирования изменяет вращение.

.red {
  width:80px;
  height:20px;
  background:red;
  margin:80px;
  transform-origin:left center;
  animation: rotate 2s linear infinite;
}
@keyframes rotate {
  from{transform:rotate(0)}
  to{transform:rotate(360deg)}

}
<div class="container">
<div class="red">
</div>
</div>

Как вы можете видеть выше, вращение создает идеальную форму круга.

Теперь давайте масштабируем контейнер и увидим разницу:

.red {
  width:80px;
  height:20px;
  background:red;
  margin:80px;
  transform-origin:left center;
  animation: rotate 5s linear infinite;
}
@keyframes rotate {
  from{transform:rotate(0)}
  to{transform:rotate(360deg)}

}
.container {
  display:inline-block;
  transform:scale(3,1);
  transform-origin:left center;
}
<div class="container">
<div class="red">
</div>
</div>

Обратите внимание, что у нас больше нет круга, но теперь это эллипс. Это как если бы мы взяли круг и стерли его, создавая эффект перекоса внутри нашего прямоугольника.

Если мы сделаем противоположный эффект и начнем с эффекта масштаба, а затем применим вращение, у нас не будет никакого перекоса.

.red {
  width:80px;
  height:20px;
  background:red;
  margin:80px;
  animation: rotate 2s linear infinite;
}
@keyframes rotate {
  from{transform:scale(1,1)}
  to{transform:scale(3,1)}

}
.container {
  display:inline-block;
  transform:rotate(30deg);
  transform-origin:left center;
}
<div class="container">
<div class="red">
</div>
</div>

Чтобы объяснить это по-разному: применение поворота сохранит одинаковое соотношение между осями X и Y, поэтому вы не увидите никакого плохого эффекта при последующем масштабировании, но масштабирование только по одной оси нарушит соотношение, поэтому наша фигура выглядит плохо, когда мы пытаемся применить вращение.

Вы можете проверить эту ссылку, если хотите получить более подробную информацию о том, как преобразованы цепочки и как рассчитывается матрица: https://www.w3.org/TR/css-transforms-1/#transform-rendering . Речь идет об элементе HTML, но, как сказано в спецификации SVG, это то же самое.

Вот соответствующие части:

Преобразования кумулятивны. То есть элементы устанавливают свою локальную систему координат в системе координат своего родителя.

С точки зрения пользователя, элемент эффективно накапливает все свойства преобразования своих предков, а также любое локальное преобразование, примененное к нему.

Давайте сделаем немного математики, чтобы увидеть разницу между обеими трансформациями. Давайте рассмотрим матричное умножение, и, поскольку мы имеем дело с двумерным линейным преобразованием, мы сделаем это на ℝ² для простоты 1 .

Для scale(2, 1) rotate(10deg) мы будем иметь

 |2 0|   |cos(10deg) -sin(10deg)|   |2*cos(10deg) -2*sin(10deg) |
 |0 1| x |sin(10deg) cos(10deg) | = |1*sin(10deg) 1*cos(10deg)  |

Теперь, если мы применим эту матрицу к (Xi,Yi) мы получим (Xf,Yf) как (Xf,Yf) ниже:

 Xf = 2* (Xi*cos(10deg) - Yi*sin(10deg))
 Yf =     Xi*sin(10deg) + Yi*cos(10deg)

Обратите внимание, что у Xf есть дополнительный множитель, который является виновником создания эффекта перекоса. Как будто мы изменили поведение или Xf и сохранили Yf

Теперь давайте рассмотрим rotate(10deg) scale(2, 1) :

 |cos(10deg) -sin(10deg)|   |2 0|   |2*cos(10deg) -1*sin(10deg) |
 |sin(10deg) cos(10deg) | x |0 1| = |2*sin(10deg) 1*cos(10deg)  |

И тогда у нас будет

 Xf =  2*Xi*cos(10deg) - Yi*sin(10deg)
 Yf =  2*Xi*sin(10deg) + Yi*cos(10deg)

Мы можем рассматривать 2*Xi как Xt и мы можем сказать, что мы повернули элемент ( Xt,Yi ), и этот элемент был первоначально масштабирован с учетом оси X.

1 CSS использует также аффинное преобразование (например, translate), поэтому использование ℝ² (декартовы координаты) недостаточно для выполнения наших вычислений, поэтому мы должны рассмотреть ℝℙ² (однородные координаты). Наш предыдущий расчет будет:

 |2 0 0|   |cos(10deg) -sin(10deg) 0|   |2*cos(10deg) -2*sin(10deg) 0|
 |0 1 0| x |sin(10deg) cos(10deg)  0| = |1*sin(10deg) 1*cos(10deg)  0|
 |0 0 1|   |0          0           1|   |0            0             1|

В этом случае ничего не изменится, потому что аффинная часть равна нулю, но если у нас есть перевод, объединенный с другим преобразованием (например: scale(2, 1) translate(10px,20px) ), у нас будет следующее:

 |2 0 0|   |1 0 10px|   |2 0 20px|
 |0 1 0| x |0 1 20px| = |0 1 20px|
 |0 0 1|   |0 0 1   |   |0 0  1  |

А также

Xf =  2*Xi + 20px;
Yf =  Yi + 20px;
1  =  1 (to complete the multiplication) 






css-transforms