FireShot というChrome拡張機能を使って、制作したWebページのフルページスクリーンショットを撮ったところ、ページがまるごと真っ白になった。
最初は単純なバグかと思ったが、原因を追っていくと「CSS animation の仕組みそのもの」が関係していた。そしてこの問題を解決するために、自作のアニメーションライブラリ一式を transition ベースに書き直すことになった。
その経緯と対処法をまとめておく。
CSSアニメーションが原因だった
自作の .chassis コンポーネントには、ページロード時のレイアウトシフト(CLS)を防ぐためのホワイトアウト演出がある。
.chassis::after {
content: "";
position: fixed;
inset: 0;
background: white;
animation: chassisAfterIn 1.8s ease 0.1s;
animation-fill-mode: forwards;
}
@keyframes chassisAfterIn {
100% { opacity: 0; }
}ページが読み込まれると白いオーバーレイが表示され、1.8秒かけてフェードアウトする。ブラウザ上では正常に動作している。
しかし FireShot でスクリーンショットを撮ると、真っ白な画像が保存されるだけだった。
FireShot(の一部モード)は、Chromeの印刷エンジンを使ってページをキャプチャするらしい。
印刷エンジンの特性として、CSSアニメーションはアニメーション開始前の初期フレームで固定される。
つまり .chassis::after は:
- 通常のブラウザ →
animationが再生され、1.8秒後にopacity: 0になる ✅ - FireShot(印刷エンジン) →
animationが再生されず、初期状態(opacity: 1 = 白)のまま固定される ❌
これが真っ白になる原因で、以前は問題が出ていなかった理由も判明した。.chassis::after に animation を使い始めたのが比較的最近で、それ以前は別の方法(transitionベース)を使っていたからだ。
解決策:animation → transition に切り替える
.chassis::after については、transition + JSによるクラス付与方式に書き換えた。
/* Before: animation */
.chassis::after {
animation: chassisAfterIn 1.8s ease 0.1s;
animation-fill-mode: forwards;
}
@keyframes chassisAfterIn {
100% { opacity: 0; }
}
/* After: transition + .is-loaded */
.chassis::after {
opacity: 1;
transition: opacity 1.8s ease 0.1s;
}
.chassis.is-loaded::after {
opacity: 0;
}JSでページロード後に .is-loaded を付与することで、transition が発火する。
transition は「プロパティの値が変化したとき」に発火するため、印刷エンジンのように「アニメーションを再生しない」環境でも、クラス付与時点の状態(最終値)が適用されるか、transition: none で即時反映される。
派生:アニメーションライブラリ も書き直した
この問題を契機に、自作のアニメーションライブラリを見直した。
scrollin.scss
スクロールトリガーのアニメーションライブラリ。scroll-fadeIn 等のクラスを付与すると、スクロールで要素がフェードインする。
従来はすべて animation で実装していたが、スクロールトリガーの演出(フェードイン・ブラーイン・クリップイン等)は「一度きりの開始→終了」の2状態遷移なので、transition に置き換えられる。
/* Before: animation */
.scroll-fadeIn {
opacity: 0;
animation: none 1s ease-out 0.25s;
animation-fill-mode: forwards;
}
.scroll-fadeIn.is-scloaded {
animation-name: scrollFadeIn;
}
@keyframes scrollFadeIn {
100% { opacity: 1; transform: translate(0); }
}
/* After: transition */
:not([unscrollin]) .scroll-fadeIn {
opacity: 0;
transition: opacity 1s ease-out 0.25s, transform 1s ease-out 0.25s;
}
:not([unscrollin]) .scroll-fadeIn:where(.is-scloaded) {
opacity: 1;
transform: translate(0);
}なお、animation-iteration-count: infinite を使うループ系(回転・マーキー等)は transition では再現できないため、animation のまま維持している。
Scrollin — スクロールトリガーアニメーションライブラリ(Vanilla JS / jQuery不要)
hoverllin.scss
ホバーアニメーションのライブラリ。こちらはもともと transition ベースだったので、animation から transition への変更は不要だった。
ただし今回の整理のタイミングで、:not([unhoverllin]) 属性セレクタを追加し、特定のセクション内でホバーエフェクトを無効化できるようにした。
Hoverllin — ホバーアニメーションライブラリ(CSS only / Vanilla JS不要)
animation と transition の使い分けまとめ
| 用途 | 推奨 | 理由 |
|---|---|---|
| 一度きりの開始→終了(フェードイン等) | transition | コードがシンプル、FireShot問題が起きない |
| 繰り返し再生(ループ・無限回転等) | animation | transition では無限繰り返しが不可能 |
| 中間フレームが必要(バウンス・フラッシュ等) | animation | 複数の%指定が必要 |
| ホバーエフェクト | transition | 状態変化ベースなのでtransitionが自然 |
FireShot が真っ白になったのは、animation が原因でした。
関連ライブラリ
Scrollin — スクロールトリガーアニメーション
(transitionベースに移行済み)Hoverllin — ホバーアニメーション
(transitionベース)Textillin — テキスト文字アニメーション
(animationベースだが、対処済み)

