このドキュメントは SKILL.md の Step 2D で参照される、@keyframes の詳細な命名規則と設計パターンをまとめたものである。
@keyframes の名前は必ず dashed ident(-- で始まる名前)を使用する。
/* OK */
@keyframes --fade-in {
...;
}
@keyframes --slide-up {
...;
}
/* NG: dashed ident でない */
@keyframes fadeIn {
...;
}
@keyframes slide-up {
...;
}理由:
- 近年の CSS では、ユーザー定義の名前を明確に区別するために
--を強制する流れがある(例:anchor-name、animation-timelineなど)。 - 「そのプロパティでは
--が必須か?」を個別に覚える必要が減る。 - 将来 CSS 標準側のキーワードが増えても、衝突リスクを下げられる。
- 自分が定義した名前だと即座に判別できる。
| スコープ | 命名形式 | 配置場所 | 例 |
|---|---|---|---|
| グローバル | --{animation-name} |
base/keyframes.css |
--fade-in, --scale-from, --translate-from |
| コンポーネント固有 | --{component-name}--{animation-name} |
コンポーネントの CSS ファイル | --shared-dialog--backdrop-fade, --shared-toast--slide-in |
- グローバル keyframes はプロジェクト全体で再利用される汎用アニメーションのみを対象とする
- コンポーネント固有の keyframes は、そのコンポーネントの CSS 内に定義する
- 迷ったらコンポーネント固有として定義し、再利用が明確になった時点(3回利用されるなど)でグローバルに昇格させる
グローバル keyframes は全て base/keyframes.css に集約する。
styles/
├── base/
│ ├── keyframes.css ← グローバル keyframes はここに全て配置
│ └── ...
├── components/
│ ├── shared-dialog.css ← コンポーネント固有の keyframes はここ
│ └── ...
└── tokens/
└── animation.css
グローバル keyframes は、ユーティリティクラスと同じ設計思想で扱う。1 つの keyframes が 1 つのプロパティ変化だけを担当し、組み合わせによって複雑なアニメーションを構成する。
- 1 keyframes = 1 プロパティの変化に限定する(
--fade-inはopacityのみ、--scale-fromはscaleのみ) - 複数プロパティを同時に動かしたい場合は、
animation-nameのカンマ区切りで複数の keyframes を組み合わせる - 各 keyframes は独立して意味を持ち、単体でも使えること
- 組み合わせ時に
animation-durationやanimation-timing-functionを個別に調整できるため、プロパティごとに最適なイージングやタイミングを選び分けられる
/* 利用側: 組み合わせて使う */
.dropdown {
--scale-from--x-value: 0.95;
--scale-from--y-value: 0.95;
animation-name: --fade-in, --scale-from;
animation-duration: 200ms;
animation-timing-function: var(--ease--out-quint), var(--ease--out-expo);
animation-fill-mode: both;
}
.toast {
--translate-from--y-value: 100%;
animation-name: --fade-in, --translate-from;
animation-duration: 200ms, 300ms;
animation-timing-function: var(--ease--out-quint);
animation-fill-mode: both;
}/* NG: グローバル keyframes が複数プロパティを持つ */
@keyframes --fade-in-and-scale {
from {
opacity: 0;
scale: 0.95;
}
}この設計では以下の問題が生じる:
opacityとscaleのイージングやタイミングを個別に制御できないopacityのみ、scaleのみが欲しい場面で再利用できない- API 的カスタムプロパティの命名が複雑になる(どのプロパティに対する値なのか曖昧になる)
- コンポーネントごとに微妙に異なる組み合わせが必要になった時点で、グローバルの意味を失う
グローバル keyframes は from / to の 2 フレームで完結するものに限定する。中間フレーム(25%, 50% など)を含む keyframes はコンポーネント固有として定義すること。
- 中間フレームを持つ keyframes は、特定の UI パターンや視覚効果に強く結びついている。例えば「50% で一度色が変わる」動きは汎用的ではなく、その挙動を必要とするコンポーネントなどに閉じるべきである
from/toのみの keyframes は「開始値と終了値」だけを定義するため、API 的カスタムプロパティとの相性が良い。中間フレームが入ると注入すべき値が増え、API の複雑さが跳ね上がる- ユーティリティクラス的な単一責務の設計原則と合致する。
from/toは「A から B への変化」という最小単位であり、それ以上の複雑さはコンポーネント固有の責務である
/* OK: from/to の 2 フレームで完結 */
@keyframes --fade-in {
from {
opacity: var(--fade-in--from-value, 0);
}
}
@keyframes --scale-from {
from {
scale: var(--scale-from--x-value, 1) var(--scale-from--y-value, 1);
}
}/* NG: 中間フレームを含むのでローカルで定義すべき */
@keyframes --pulse {
0% {
opacity: 1;
}
50% {
opacity: 0.4;
}
100% {
opacity: 1;
}
}CSS の仕様では、@keyframes の from または to が省略された場合、アニメーション対象要素がその時点で持っている値が自動的に補完される。この仕様を活用し、自明な from や to は書かないことを原則とする。
- 省略することで要素が現在持っている値を尊重し、カスケーディングに沿った自然なアニメーションが実行される
fromやtoに値をハードコードすると、要素の現在値が想定と異なる場合にジャンプが発生する- API 的カスタムプロパティで注入すべき値が減り、keyframes の設計がシンプルになる
/* OK: to を省略 — 要素の現在の opacity に向かってアニメーションする */
@keyframes --fade-in {
from {
opacity: var(--fade-in--from-value, 0);
}
}opacity: 0.8 の要素に適用すれば 0 → 0.8、opacity: 1 なら 0 → 1 に自然に収束する。
/* NG: to { opacity: 1 } を明記してしまう */
@keyframes --fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}この場合、opacity: 0.8 の要素に animation-fill-mode: both で適用すると、アニメーション終了時に opacity が 1 に強制され、アニメーション後に 0.8 に戻るフラッシュが発生する。animation-fill-mode: forwards なら 1 のまま留まり、要素本来の 0.8 が上書きされてしまう。
/* OK: from を省略 — 要素の現在の opacity から 0 に向かう */
@keyframes --fade-out {
to {
opacity: var(--fade-out--to-value, 0);
}
}opacity: 0.5 の半透明要素に適用すれば 0.5 → 0、opacity: 1 なら 1 → 0 になる。from { opacity: 1 } をハードコードしていた場合、半透明の要素が一瞬 1 にジャンプしてから消えるという不自然な挙動になる。
| 状況 | from/to |
|---|---|
| 「ゼロ / 非表示」からの出現(fade-in, scale-from 等) | from のみ記述、to は省略 |
| 「ゼロ / 非表示」への退場(fade-out 等) | to のみ記述、from は省略 |
| 開始値と終了値の両方を厳密に制御する必要がある | 両方記述(ただしローカル定義にすべきケースが多い) |
| 無限ループ(rotate 等) | to のみ記述 |
グローバル keyframes は汎用的に再利用可能であるべきである。そのため、条件によってアニメーションの起点や方向を変えたいケースでは、keyframes 内部にカスタムプロパティの参照を埋め込み、利用側から値を注入する。
keyframes 用の API 的カスタムプロパティは --{keyframes-name}--{value-name} の形式で命名する。
-- の接頭辞は keyframes 名に含まれるため、ローカルカスタムプロパティで必須としているアンダースコア始まり(--_)は不要である。
--{keyframes-name}--{value-name}
例: --scale-from--x-value, --translate-from--y-value
/* base/keyframes.css */
@keyframes --scale-from {
from {
scale: var(--scale-from--x-value, 1) var(--scale-from--y-value, 1);
}
}[popover] {
--scale-from--x-value: 0.96;
--scale-from--y-value: 0.96;
animation-name: --scale-from;
animation-duration: 200ms;
animation-timing-function: var(--ease--out-quint);
animation-fill-mode: both;
}
/* 配置方向に応じて起点を変える */
[popover][data-placement="top"] {
--scale-from--y-value: 1.04;
}/* base/keyframes.css */
@keyframes --translate-from {
from {
translate: var(--translate-from--x-value, 0) var(
--translate-from--y-value,
0
);
}
}.toast {
--translate-from--y-value: 100%;
animation-name: --translate-from;
animation-duration: 300ms;
animation-timing-function: var(--ease--out-quint);
animation-fill-mode: both;
}
/* 位置に応じて方向を変える */
.toast[data-position="top"] {
--translate-from--y-value: -100%;
}/* base/keyframes.css */
@keyframes --fade-in {
from {
opacity: var(--fade-in--from-value, 0);
}
}
@keyframes --fade-out {
to {
opacity: var(--fade-out--to-value, 0);
}
}animation プロパティはカンマ区切りで複数指定できる。グローバル keyframes を組み合わせることで、コンポーネント固有の keyframes を定義せずに済む場合がある。
/* fade + scale の組み合わせ */
.dropdown {
--scale-from--x-value: 0.95;
--scale-from--y-value: 0.95;
animation-name: --fade-in, --scale-from;
animation-duration: 200ms;
animation-timing-function: var(--ease--out-quint), var(--ease--out-expo);
animation-fill-mode: both;
}IntersectionObserver による状態トグルで発火させる scroll-linked animation では、グローバル keyframes と animation-play-state: paused を組み合わせることで、初期非表示と発火を一元管理できる。
.reveal-item {
--translate-from--y-value: 20px;
animation-name: --fade-in, --translate-from;
animation-duration: 600ms;
animation-timing-function: var(--ease--out-quint);
animation-fill-mode: both;
animation-play-state: paused;
/* アクティブ時に発火 */
&[data-revealed] {
animation-play-state: initial;
}
}animation-fill-mode: bothにより、paused状態でも keyframes のfromフレームが適用される。これにより要素は keyframes 上の開始地点(opacity: 0,translate: 0 20px)で描画される- アクティブ化(属性付与等)で
animation-play-stateをinitial(=running)に戻すだけでアニメーションが発火する - keyframes の外に
opacity: 0等を別途記述する必要がなく、keyframes が初期状態の単一の情報源(single source of truth)となる
コンポーネントや affordance の keyframes は、そのコンポーネントの CSS ファイル内に定義し、--{component-name}--{animation-name} の形式で命名する。
/* affordance/arrows.css */
@keyframes --arrows--fill-steps {
from,
to {
fill: var(--color--primary);
}
50% {
fill: transparent;
}
}
@scope (.arrows) to (:scope > *) {
:scope {
fill: none;
stroke: var(--color--primary);
animation-name: --arrows--fill-steps;
animation-duration: 200ms;
animation-timing-function: steps(1);
animation-direction: alternate;
animation-iteration-count: infinite;
}
}| 状況 | 選択 |
|---|---|
| 汎用的な動き(fade, scale, translate)で、起点だけ変えたい | グローバル keyframes + API 的カスタムプロパティ |
| コンポーネント固有の複数プロパティを同時にアニメーションする | ローカル keyframes |
中間フレーム(50% など)が必要な複雑なアニメーション |
ローカル keyframes |
| backdrop や特殊な疑似要素のアニメーション | ローカル keyframes |
@keyframes を使うべきか、transition + @starting-style を使うべきかの判断基準。
| 要件 | 推奨手法 |
|---|---|
display: none からの出現で、退場もアニメーションしたい |
transition + @starting-style + transition-behavior: allow-discrete |
| 出現のみアニメーションし、退場は即時でよい | @keyframes animation |
中間フレーム(25%, 50% など)が必要 |
@keyframes animation |
| JS から動的に制御したい(pause, reverse, cancel) | Web Animations API(element.animate()) |
| 中断・逆再生が頻発する | transition |
| 複数プロパティを異なるタイミングで段階的に動かしたい | @keyframes animation、または transition-delay の組み合わせ |
| scroll-linked で初期非表示 → トグル発火 | @keyframes + animation-play-state: paused / initial |