ダークモード実装時、iPhoneで確認したときにアイコンがモーフィングせずに「じわー」っとトランジション したので気になって調べました。
Safariではpathを入れ替えたアニメーションはできない
Javascriptでpathのdを入れ替えることで綺麗にモーフィングしてくれます。 ただしパス同士のセグメント数(パスコマンド)が一致している必要があります。

👆これをSafariで確認するとディゾルブしていました👇

いろいろ試したところ2つのことがわかりました。
- Safariではpathのd属性を入れ替えてもアニメーション(モーフィング)されない
- arcコマンドで半径が0ではパスが表示されない
セグメント数をそろえるために、消したい要素は座標を指定して(上記の例では中央に)大きさを0にしていたのですが、 そもそもそういった作法をSafariは許してはくれませんでした。
animateタグ
調べていると、同じようにd属性を入れ替えている人の嘆きや、GSAPなどのライブラリの紹介等しかなく、
何となく自分で行きついた答えを紹介しておきます。
それはanimate
タグをJavascript駆動することです。
animateの属性には以下のものがあります。
基本属性 | 説明 |
---|---|
attributeName | アニメーション対象の属性(例:x , y , opacity , d など) |
attributeType | 属性のタイプ。CSS 、XML 、auto (省略可) |
値と変化の制御 | 説明 |
---|---|
from | アニメーション開始値 |
to | アニメーション終了値 |
by | 開始値からの相対変化値 |
values | ; 区切りで複数の値を指定(例:values="10;20;10" ) |
keyTimes | values に対応したタイミング(0〜1)を指定(例:keyTimes="0;0.5;1" ) |
keySplines | calcMode="spline" 時のイージングをベジェ曲線で指定(例:.42 0 .58 1 ) |
calcMode | 補間の方法。discrete (不連続)、linear (線形)、paced 、spline など |
時間制御 | 説明 |
---|---|
begin | 開始タイミング(例:2s 、click 、indefinite ) |
dur | アニメーションの継続時間(例:3s ) |
end | アニメーションを終了するタイミング(あまり使われない) |
repeatCount | 繰り返し回数(例:indefinite , 1 , 3 ) |
repeatDur | 繰り返し全体の期間(例:10s ) |
fill | 終了後の状態:remove (初期値)か freeze (終了状態を保持) |
イベント連携とID関連 | 説明 |
---|---|
id | アニメーション要素のID |
begin="otherID.end" | 他アニメーションの終了をトリガーに開始 |
begin="click" | 対象要素がクリックされたら開始 |
begin="indefinite" | JSの beginElement() で手動再生するために使用 |
restart | always , whenNotActive , never などでリスタート可否を指定 |
svgのpathでモーフィングアニメーション
pathで初期状態のd値を設定しておき、begin="indefinite" fill="freeze"
で
Javascript駆動と終了状態の保持を設定したうえで、それぞれto属性に変化後のセグメント情報を入力しておきます。
Javascriptでd属性やto属性をどうのこうのしようとしてもSafariは許してくれません。 初期状態の値と変化後の値、そして戻るときの値(初期状態と同じ)を入力しておかなくてはいけないので ムダが嫌いな効率人間には厳しい鍛錬となります。
そして変化後のセグメント情報のarcコマンドでは、半径を0.001として存在を認めてあげることも忘れてはなりません。 「一寸の虫にも五分の魂」については思うところがあり、 小さな虫けらにはしょせん小さな魂しか入っていないという印象を受けて育ちました。
<svg width="24" height="24" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg">
<path d="M 64 9.3 A 7.3 7.3 0 0 0 56.7 16.6 ... Z" >
<animate id="to_dark" attributeName="d" fill="freeze"
begin="indefinite" dur="1s" to="M 64,64 a 0.001,0.001 0 0 0 0,0 ... Z"/>
<animate id="to_light" attributeName="d" fill="freeze"
begin="indefinite" dur="1s" to="M 64 9.3 A 7.3 7.3 0 0 0 56.7 16.6 ... Z"/>
</path>
</svg>
<script>
function toggleDark(to_dark) {
if (to_dark) document.getElementById('to_dark').beginElement();
else document.getElementById('to_light').beginElement();
}
</script>
終わりに
今回の騒動でsvgについて少しは理解が深まった気がします。対応ブラウザのことが気になりますが、 見えている世界しか見えないので気にしないようにします。