transform:rotate() と rotate は動作が違う

### 背景

2022年ごろから、独立transformプロパティとして`translate` `rotate` `scale`が各ブラウザに実装されました。
これにより、`transform`の上書きや処理順などを気にせずに座標変換をすることができるようになり、手軽に使用しやすくなりました。
`transform: rotate()`と`rotate`は、一般に同じ動作をすると思われており、実際にほとんどのケースでそれは間違っていないのですが、そうではない場合もあります。

### 概要

`transform:rotate()`と`rotate`はだいたい同じ動作をするが、`offset`が絡むと違う動作をする。

### 実際の動作

以下のCodePenは、円形のモーションパスを移動させながら`transform:rotate()`と`rotate`をそれぞれ`360deg`までアニメーションさせているものです。回転アニメーション以外のプロパティは全く同じです。

<p class="codepen" data-height="300" data-default-tab="result" data-slug-hash="ogvbYMe" data-pen-title="transform:rotate() vs rotate" data-user="lhankor_mhy" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/lhankor_mhy/pen/ogvbYMe">
  transform:rotate() vs rotate</a> by lhankor_mhy (<a href="https://codepen.io/lhankor_mhy">@lhankor_mhy</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

初見ではなぜこうなったかよくわかりませんでした。
なので、モーションパスを止めてみるとこうなりました。

<p class="codepen" data-height="300" data-default-tab="result" data-slug-hash="RNbWMrv" data-pen-title="transform:rotate() vs rotate" data-user="lhankor_mhy" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/lhankor_mhy/pen/RNbWMrv">
  transform:rotate() vs rotate</a> by lhankor_mhy (<a href="https://codepen.io/lhankor_mhy">@lhankor_mhy</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

ご覧の通り、回転の中心点が異なります。

### 解説

#### 簡単な解説

`rotate`は`transform-origin`が`offset`による変形の前の位置になります。言い換えると、`offset-path: none`の時の`transform-origin`の場所を変形の原点とする、ということです。
逆に`transform: rotate()`は、`offset`による変形後の、見たままの`transform-origin`です。

#### 図示

<p class="codepen" data-height="300" data-default-tab="result" data-slug-hash="RNbWJQy" data-pen-title="rotate and offset" data-user="lhankor_mhy" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/lhankor_mhy/pen/RNbWJQy">
  rotate and offset</a> by lhankor_mhy (<a href="https://codepen.io/lhankor_mhy">@lhankor_mhy</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

#### 仕様

仕様書によると、このようになっています。

> The transformation matrix is computed from the transform, transform-origin, translate, rotate, scale, and offset properties as follows:
> 
> 0.    Start with the identity matrix.
> 0.    Translate by the computed X, Y, and Z values of transform-origin.
> 0.    Translate by the computed X, Y, and Z values of translate.
> 0.    Rotate by the computed <angle> about the specified axis of **rotate**.
> 0.    Scale by the computed X, Y, and Z values of scale.
> 0.    Translate and rotate by the transform specified by **offset**.
> 0.    Multiply by each of the **transform functions** in transform from left to right.
> 0.    Translate by the negated computed X, Y and Z values of transform-origin.
> 
> [CSS Transforms Module Level 2](https://drafts.csswg.org/css-transforms-2/#ctm)

変形行列の合成について正直しっかり理解できていないのですが、`translate`→`rotate`→`scale`→`offset`→`transform`の順番に処理がされるようです。
`rotate`が処理された後に`offset`が処理され、さらにその後に`transform`が処理されていることが、この動作の違いを生んでいるのだろうと思います。

ですので、`rotate`だけではなくて、`translate`や`scale`もこの影響を受けています。