バッジを円軌道で回したら、CSSとJSのtransformが取り合いになった話

きっかけは思いつきでした。トップページのヒーローに置いてある4つのバッジ(WordPressとかFigmaとか、使ってるツールを並べてるやつ)、あれをぐるっと円軌道で回したら面白いんじゃないかと思いついて、軽い気持ちで実装を始めました。結果的に、思ってたよりだいぶ寄り道することになったので、今日はその記録です。

円軌道自体はそんなに難しくなかった

やりたいことはシンプルで、4つのバッジを中心(肉球ロゴ)から等間隔で円周上に配置して、ゆっくり回転させたいだけです。CSSのカスタムプロパティで各バッジに角度(0deg/90deg/180deg/270deg)を持たせておいて、rotate(角度) → translateX(半径) → rotate(-角度)という順番でtransformを重ねる、よくあるテクニックを使いました。最後にもう一度逆方向に回すのがポイントで、これをやらないとバッジ自体も一緒に回転してしまって文字が斜めになってしまいます。

ここまでは特に詰まらず、数式通りに動いてくれました。円周上の座標計算だけの話なので、当たり前といえば当たり前です。

ブラウザで見たら、バッジが1個も表示されない

結果は謎でした。いざブラウザで確認しようとしたら、バッジが4つとも透明で、影も形もありませんでした。自分が書いたコードのせいだと思って見直しましたが、CSSはどう見ても正しい。おかしいなと思って要素を調べてみると、既存の(自分が書いたわけではない)GSAPのアニメーションが、バッジの初期状態としてopacity: 0をセットしたまま、表示用のアニメーションが一度も再生されていないことが分かりました。

冷や汗ものでした。本来はスクロールでヒーローが画面に入ったタイミングで1回だけフェードインする仕組みだったんですが、これがなぜか発火していない。念のため本番サイトも見てみたら、同じようにバッジが表示されていませんでした。「これ、地味に前からずっと壊れてたやつなのでは」と、結構本気で焦りました。

「バグを直した」つもりが、直す必要のないバグだった

これは実際のユーザーの表示には影響しない、既存のバグではないやつだったんですが、原因を探るのに結構な時間を溶かしました。

犯人はタブの非表示。深追いした結果たどり着いたのは、自分が使っていた検証用ブラウザのタブが「非表示」扱い(document.hiddentrue)になっていたという、身も蓋もない理由でした。ブラウザは省エネのために、裏に回っているタブのrequestAnimationFrameをほぼ止めてしまいます。GSAPのアニメーションはこの仕組みの上で動いているので、タブが裏にある間はずっと足踏み状態になっていたわけです。本番サイトでも同じように見えたのも、たまたま同じ検証環境から確認していたからで、実際にページを開いて使っているユーザーの手元では、普通に動いていたはずでした。

教訓はシンプルです。一度は「これは直さないと」と、既存のアニメーションに強制再生のコードまで書き足したんですが、原因が分かった時点でそれを丸ごと取り消しました。存在しない不具合のために防御コードを足すのは、後から読む人を余計に混乱させるだけだなと思ったからです。「動いてないように見える」は、そのまま鵜呑みにせず、なんで動いてないのかまで潰してから手を動かした方がいい、というのを身をもって学びました。

本命は、CSSとJSがtransformを取り合っていたこと

本題に戻ります。寄り道から戻って、円軌道のCSSアニメーションを本来の場所に実装し直しました。ところが今度は、バッジがスクロール時にふらっと揺れる、別の演出(これもGSAP側の仕組みで、スクロール量に応じて上下にちょっとパララックスさせるやつ)が効かなくなっていることに気づきました。

原因は単純で、円軌道のCSSアニメーションも、スクロール連動のJSアニメーションも、どちらも同じ要素の同じtransformプロパティを取り合っていたからです。片方が回転とスクロールで座標を計算してtransformを書き込んでも、もう片方がすぐ別の値で上書きしてしまう。CSSのアニメーションとJSのインラインスタイルがどちらも生きていると、こういう衝突が起きるというのは知識としては知っていたつもりだったんですが、実際に自分の手で踏むとやっぱり「あ、これか」となりました。

直し方は、要素をひとつ増やして役割を分けることでした。外側の要素には円軌道の回転だけを任せて、内側の要素(これまで通りGSAPが触る対象)にはフェードインとスクロールパララックスを任せる。transformを触る主体が要素ごとに1つになるので、取り合いが起きなくなります。

動いているかどうかの確認方法も、ちょっと工夫が必要だった

検証方法は力技のスクショ比較。余談ですが、今回の検証中は例のタブ非表示問題のせいで、アニメーションがリアルタイムで進んでいるところを、そのまま画面で見られませんでした。なので、JS側からアニメーションの再生位置を直接指定して(0%地点、50%地点、100%地点、みたいに)、そのつどスクリーンショットを撮って見た目を確認する、という力技で検証しました。動きそのものを見られない代わりに、要所要所の静止画を並べて「ちゃんと計算通りの場所に来ているか」を確かめる形です。地味ですが、これはこれで役に立つ検証方法だったなと思います。

まとめ

教訓は2つです。円軌道のアニメーション自体は素直な数式で済んだんですが、実際に手こずったのは「既存のアニメーションと同じプロパティを取り合ってしまうこと」と、「検証環境が嘘の情報を返してくること」の2つでした。どちらも実装のロジックそのものとは関係ない、外側の要因です。新しいアニメーションを既存のページに足すときは、同じ要素の同じCSSプロパティを他の仕組みも触っていないか、一度確認してから手を動かした方がよさそうです。

この記事の内容を、YouTube Shortsでも紹介しています

この記事を書いた人

Web制作歴10年近く。WordPressのテーマカスタマイズやコーディングを中心に、副業・本業ともにweb制作に携わっています。このブログでは、実際に手を動かして分かったことを書いています。

Cat's Handをフォローする

この記事が参考になったら、応援クリックをお願いします

タイトルとURLをコピーしました