CSSアニメーションを使用すると、JavaScriptを全く使用せずに簡単なアニメーションを作成できます。
JavaScriptを使用してCSSアニメーションを制御し、少ないコードでさらに優れたアニメーションを作成できます。
CSSトランジション
CSSトランジションの考え方はシンプルです。プロパティと、その変更をどのようにアニメーション化する必要があるかを記述します。プロパティが変更されると、ブラウザはアニメーションを描画します。
つまり、プロパティを変更するだけで、滑らかなトランジションはブラウザによって行われます。
たとえば、以下のCSSは、`background-color`の変更を3秒間アニメーション化します。
.animated {
transition-property: background-color;
transition-duration: 3s;
}
要素に`.animated`クラスがある場合、`background-color`の変更は3秒間アニメーション化されます。
以下のボタンをクリックして、背景をアニメーション化します。
<button id="color">Click me</button>
<style>
#color {
transition-property: background-color;
transition-duration: 3s;
}
</style>
<script>
color.onclick = function() {
this.style.backgroundColor = 'red';
};
</script>
CSSトランジションを記述するためのプロパティは4つあります。
transition-property
transition-duration
transition-timing-function
transition-delay
これらについては後ほど説明しますが、ここでは、共通の`transition`プロパティを使用すると、`property duration timing-function delay`の順序でまとめて宣言できること、また、複数のプロパティを一度にアニメーション化できることに注意してください。
たとえば、このボタンは`color`と`font-size`の両方をアニメーション化します。
<button id="growing">Click me</button>
<style>
#growing {
transition: font-size 3s, color 2s;
}
</style>
<script>
growing.onclick = function() {
this.style.fontSize = '36px';
this.style.color = 'red';
};
</script>
それでは、アニメーションのプロパティを1つずつ見ていきましょう。
transition-property
`transition-property`には、アニメーション化するプロパティのリストを記述します。たとえば、`left`、`margin-left`、`height`、`color`などです。または、"すべてのプロパティをアニメーション化する"という意味の`all`を記述することもできます。
アニメーション化できないプロパティがあることに注意してください。ただし、一般的に使用されるほとんどのプロパティはアニメーション化可能です。
transition-duration
`transition-duration`では、アニメーションの時間を指定できます。時間はCSS時間形式で指定する必要があります。秒の場合は`s`、ミリ秒の場合は`ms`を使用します。
transition-delay
`transition-delay`では、アニメーションの*開始前*の遅延を指定できます。たとえば、`transition-delay`が`1s`で`transition-duration`が`2s`の場合、アニメーションはプロパティの変更から1秒後に開始され、合計時間は2秒になります。
負の値も可能です。その場合、アニメーションはすぐに表示されますが、アニメーションの開始点は指定された値(時間)の後になります。たとえば、`transition-delay`が`-1s`で`transition-duration`が`2s`の場合、アニメーションは中間点から開始され、合計時間は1秒になります。
ここでは、CSSの`translate`プロパティを使用して、数字を`0`から`9`にシフトするアニメーションを示します。
stripe.onclick = function() {
stripe.classList.add('animate');
};
#digit {
width: .5em;
overflow: hidden;
font: 32px monospace;
cursor: pointer;
}
#stripe {
display: inline-block
}
#stripe.animate {
transform: translate(-90%);
transition-property: transform;
transition-duration: 9s;
transition-timing-function: linear;
}
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="style.css">
</head>
<body>
Click below to animate:
<div id="digit"><div id="stripe">0123456789</div></div>
<script src="script.js"></script>
</body>
</html>
`transform`プロパティは次のようにアニメーション化されます。
#stripe.animate {
transform: translate(-90%);
transition-property: transform;
transition-duration: 9s;
}
上記の例では、JavaScriptが要素に`.animate`クラスを追加し、アニメーションが開始されます。
stripe.classList.add('animate');
トランジションの途中から、たとえば現在の秒に対応する正確な数値から、負の`transition-delay`を使用して開始することもできます。
ここで数字をクリックすると、現在の秒からアニメーションが開始されます。
stripe.onclick = function() {
let sec = new Date().getSeconds() % 10;
stripe.style.transitionDelay = '-' + sec + 's';
stripe.classList.add('animate');
};
#digit {
width: .5em;
overflow: hidden;
font: 32px monospace;
cursor: pointer;
}
#stripe {
display: inline-block
}
#stripe.animate {
transform: translate(-90%);
transition-property: transform;
transition-duration: 9s;
transition-timing-function: linear;
}
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="style.css">
</head>
<body>
Click below to animate:
<div id="digit"><div id="stripe">0123456789</div></div>
<script src="script.js"></script>
</body>
</html>
JavaScriptは1行追加することでこれを実現します。
stripe.onclick = function() {
let sec = new Date().getSeconds() % 10;
// for instance, -3s here starts the animation from the 3rd second
stripe.style.transitionDelay = '-' + sec + 's';
stripe.classList.add('animate');
};
transition-timing-function
タイミング関数は、アニメーションプロセスがタイムラインに沿ってどのように分散されるかを記述します。ゆっくりと開始して速くなるのか、それともその逆か。
最初は最も複雑なプロパティのように見えますが、少し時間をかければ非常にシンプルになります。
このプロパティは、ベジェ曲線またはステップの2種類の値を受け入れます。ベジェ曲線の方がよく使用されるため、まずはそこから見ていきましょう。
ベジェ曲線
タイミング関数は、以下の条件を満たす4つの制御点を持つベジェ曲線として設定できます。
- 最初の制御点:`(0,0)`。
- 最後の制御点:`(1,1)`。
- 中間点の場合、`x`の値は`0..1`の範囲内でなければならず、`y`は何でもかまいません。
CSSにおけるベジェ曲線の構文:`cubic-bezier(x2, y2, x3, y3)`。最初の制御点は`(0,0)`に固定され、4番目の制御点は`(1,1)`に固定されているため、2番目と3番目の制御点のみを指定する必要があります。
タイミング関数は、アニメーションプロセスの速度を記述します。
- `x`軸は時間です。`0`は開始時、`1`は`transition-duration`の終了時です。
- `y`軸はプロセスの完了度を指定します。`0`はプロパティの開始値、`1`は最終値です。
最も簡単なケースは、アニメーションが同じ線形速度で均一に進む場合です。これは、曲線`cubic-bezier(0, 0, 1, 1)`で指定できます。
この曲線は次のようになります。
…ご覧のとおり、これはただの直線です。時間(`x`)が経過するにつれて、アニメーションの完了(`y`)は着実に`0`から`1`に進みます。
以下の例では、電車が一定の速度で左から右に移動します(クリックしてください)。
.train {
position: relative;
cursor: pointer;
width: 177px;
height: 160px;
left: 0;
transition: left 5s cubic-bezier(0, 0, 1, 1);
}
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="style.css">
</head>
<body>
<img class="train" src="https://js.cx/clipart/train.gif" onclick="this.style.left='450px'">
</body>
</html>
CSSの`transition`はこの曲線に基づいています。
.train {
left: 0;
transition: left 5s cubic-bezier(0, 0, 1, 1);
/* click on a train sets left to 450px, thus triggering the animation */
}
…では、減速する電車をどのように表示すればよいでしょうか?
別のベジェ曲線`cubic-bezier(0.0, 0.5, 0.5 ,1.0)`を使用できます。
グラフ
ご覧のとおり、プロセスは高速に開始されます。曲線は高く舞い上がり、その後は徐々に速度が低下します。
タイミング関数の動作を確認してください(電車をクリックしてください)。
.train {
position: relative;
cursor: pointer;
width: 177px;
height: 160px;
left: 0px;
transition: left 5s cubic-bezier(0.0, 0.5, 0.5, 1.0);
}
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="style.css">
</head>
<body>
<img class="train" src="https://js.cx/clipart/train.gif" onclick="this.style.left='450px'">
</body>
</html>
CSS
.train {
left: 0;
transition: left 5s cubic-bezier(0, .5, .5, 1);
/* click on a train sets left to 450px, thus triggering the animation */
}
組み込みの曲線はいくつかあります。`linear`、`ease`、`ease-in`、`ease-out`、`ease-in-out`です。
`linear`は`cubic-bezier(0, 0, 1, 1)`の省略形です。これは、上記で説明した直線です。
他の名前は、以下の`cubic-bezier`の省略形です。
`ease`* | ease-in |
ease-out |
ease-in-out |
---|---|---|---|
(0.25, 0.1, 0.25, 1.0) |
(0.42, 0, 1.0, 1.0) |
(0, 0, 0.58, 1.0) |
(0.42, 0, 0.58, 1.0) |
`*` - デフォルトでは、タイミング関数が指定されていない場合、`ease`が使用されます。
そのため、減速する電車には`ease-out`を使用できます。
.train {
left: 0;
transition: left 5s ease-out;
/* same as transition: left 5s cubic-bezier(0, .5, .5, 1); */
}
ただし、少し異なって見えます。
ベジェ曲線を使用すると、アニメーションが範囲を超える可能性があります。
曲線の制御点は、負の値や非常に大きな値など、任意の`y`座標を持つことができます。その場合、ベジェ曲線も非常に低くまたは高く拡張され、アニメーションが通常の範囲を超えてしまいます。
以下の例では、アニメーションコードは次のとおりです。
.train {
left: 100px;
transition: left 5s cubic-bezier(.5, -1, .5, 2);
/* click on a train sets left to 450px */
}
プロパティ`left`は`100px`から`400px`にアニメーション化する必要があります。
ただし、電車をクリックすると、次のようになります。
- まず、電車が*後退*します。`left`が`100px`未満になります。
- 次に、`400px`よりも少し आगे बढ़ります。
- そして、再び`400px`に戻ります。
.train {
position: relative;
cursor: pointer;
width: 177px;
height: 160px;
left: 100px;
transition: left 5s cubic-bezier(.5, -1, .5, 2);
}
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="style.css">
</head>
<body>
<img class="train" src="https://js.cx/clipart/train.gif" onclick="this.style.left='400px'">
</body>
</html>
なぜこうなるのかは、指定されたベジェ曲線のグラフを見れば明らかです。
2番目の点の`y`座標をゼロより下に移動し、3番目の点の`y`座標を`1`より大きくしたため、曲線が"通常の"象限から外れています。`y`は"標準の"範囲`0..1`から外れています。
ご存知のとおり、`y`は"アニメーションプロセスの完了"を測定します。値`y = 0`は開始プロパティ値に対応し、`y = 1`は終了値に対応します。したがって、`y<0`の値はプロパティを開始`left`よりも先に移動し、`y>1`の値は最終`left`を超えて移動します。
これは確かに"ソフトな"バリアントです。`y`値を`-99`や`99`のように設定すると、電車は範囲からはるかに大きく飛び出します。
しかし、特定のタスクのためのベジェ曲線をどのように作成すればよいでしょうか?多くのツールがあります。
- たとえば、https://cubic-bezier.comのサイトで作成できます。
- ブラウザの開発者ツールにも、CSSのベジェ曲線を編集するための特別なサポートが用意されています。
- F12(Mac:Cmd+Opt+I)で開発者ツールを開きます。
- `要素`タブを選択し、右側にある`スタイル`サブパネルに注目します。
- `cubic-bezier`という単語を含むCSSプロパティには、この単語の前にアイコンが表示されます。
- このアイコンをクリックして、曲線を編集します。
ステップ
タイミング関数`steps(ステップ数[, start/end])`を使用すると、トランジションを複数のステップに分割できます。
数字の例で見てみましょう。
ここでは、アニメーションなしの数字のリストをソースとして示します。
#digit {
border: 1px solid red;
width: 1.2em;
}
#stripe {
display: inline-block;
font: 32px monospace;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="digit"><div id="stripe">0123456789</div></div>
</body>
</html>
HTMLでは、数字のストライプが固定長の`<div id="digits">`で囲まれています。
<div id="digit">
<div id="stripe">0123456789</div>
</div>
`#digit`divは固定幅と境界線があるため、赤いウィンドウのように見えます。
タイマーを作成します。数字は1つずつ、個別の方法で表示されます。
これを実現するために、`overflow: hidden`を使用して`#digit`の外側にある`#stripe`を非表示にし、`#stripe`をステップごとに左にシフトします。
各数字に1ステップずつ、9ステップあります。
#stripe.animate {
transform: translate(-90%);
transition: transform 9s steps(9, start);
}
`steps(9, start)`の最初の引数はステップ数です。変換は9つのパート(それぞれ10%)に分割されます。時間間も自動的に9つのパートに分割されるため、`transition: 9s`はアニメーション全体で9秒、つまり数字ごとに1秒になります。
2番目の引数は、`start`または`end`のいずれかの単語です。
`start`は、アニメーションの開始時に最初のステップをすぐに実行する必要があることを意味します。
動作中
#digit {
width: .5em;
overflow: hidden;
font: 32px monospace;
cursor: pointer;
}
#stripe {
display: inline-block
}
#stripe.animate {
transform: translate(-90%);
transition-property: transform;
transition-duration: 9s;
transition-timing-function: steps(9, start);
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="style.css">
</head>
<body>
Click below to animate:
<div id="digit"><div id="stripe">0123456789</div></div>
<script>
digit.onclick = function() {
stripe.classList.add('animate');
}
</script>
</body>
</html>
数字をクリックすると、すぐに`1`(最初のステップ)に変更され、次の秒の開始時に変更されます。
プロセスは次のように進行します。
- `0s` - `-10%`(最初の変更は1秒目の開始時、すぐに)
- `1s` - `-20%`
- …
- `8s` - `-90%`
- (最後の秒は最終値を示します)。
ここでは、`steps`で`start`が使用されているため、最初の変更はすぐに発生しました。
代替値であるend
は、変更が各秒の開始時ではなく、終了時に適用されることを意味します。
つまり、steps(9, end)
の処理は次のようになります。
0秒
–0
(最初の1秒間は何も変わりません)1秒
–-10%
(最初の変更は1秒目の終わりに発生します)2秒
–-20%
- …
9秒
–-90%
steps(9, end)
の動作例です(最初の数字が変わる前のポーズに注目してください)
#digit {
width: .5em;
overflow: hidden;
font: 32px monospace;
cursor: pointer;
}
#stripe {
display: inline-block
}
#stripe.animate {
transform: translate(-90%);
transition-property: transform;
transition-duration: 9s;
transition-timing-function: steps(9, end);
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="style.css">
</head>
<body>
Click below to animate:
<div id="digit"><div id="stripe">0123456789</div></div>
<script>
digit.onclick = function() {
stripe.classList.add('animate');
}
</script>
</body>
</html>
steps(...)
には、いくつかの定義済み shorthand もあります。
step-start
–steps(1, start)
と同じです。つまり、アニメーションはすぐに開始し、1ステップで完了します。そのため、アニメーションがないかのように、すぐに開始して終了します。step-end
–steps(1, end)
と同じです。transition-duration
の最後に1ステップでアニメーションを実行します。
これらの値は、実際のアニメーションではなく、1ステップの変更を表すため、めったに使用されません。完全を期すためにここで言及しています。
イベント: “transitionend”
CSSアニメーションが終了すると、transitionend
イベントがトリガーされます。
これは、アニメーションの完了後にアクションを実行するために広く使用されています。また、アニメーションを結合することもできます。
たとえば、以下の例の船は、クリックされるたびに左右に increasingly 動きます。
アニメーションは、トランジションが終了するたびに再実行され、方向を反転させる関数go
によって開始されます。
boat.onclick = function() {
//...
let times = 1;
function go() {
if (times % 2) {
// sail to the right
boat.classList.remove('back');
boat.style.marginLeft = 100 * times + 200 + 'px';
} else {
// sail to the left
boat.classList.add('back');
boat.style.marginLeft = 100 * times - 200 + 'px';
}
}
go();
boat.addEventListener('transitionend', function() {
times++;
go();
});
};
transitionend
のイベントオブジェクトには、いくつかの特定のプロパティがあります。
event.propertyName
- アニメーションが終了したプロパティ。複数のプロパティを同時にアニメーション化する場合は便利です。
event.elapsedTime
- アニメーションにかかった時間(秒単位)。
transition-delay
は含まれません。
キーフレーム
@keyframes
CSSルールを使用して、複数の単純なアニメーションを結合できます。
これは、アニメーションの「名前」とルール(何を、いつ、どこでアニメーション化するか)を指定します。次に、animation
プロパティを使用して、アニメーションを要素にアタッチし、追加のパラメーターを指定できます。
説明付きの例を次に示します。
<div class="progress"></div>
<style>
@keyframes go-left-right { /* give it a name: "go-left-right" */
from { left: 0px; } /* animate from left: 0px */
to { left: calc(100% - 50px); } /* animate to left: 100%-50px */
}
.progress {
animation: go-left-right 3s infinite alternate;
/* apply the animation "go-left-right" to the element
duration 3 seconds
number of times: infinite
alternate direction every time
*/
position: relative;
border: 2px solid green;
width: 50px;
height: 20px;
background: lime;
}
</style>
@keyframes
に関する記事は多数あり、詳細な仕様書もあります。
サイト上のすべてが常に動いている場合を除き、@keyframes
はそれほど頻繁には必要ないでしょう。
パフォーマンス
ほとんどのCSSプロパティは数値であるため、アニメーション化できます。たとえば、width
、color
、font-size
はすべて数値です。これらをアニメーション化すると、ブラウザはこれらの数値をフレームごとに徐々に変更し、スムーズな効果を作成します。
ただし、CSSプロパティによって変更のコストが異なるため、すべてのアニメーションが思いどおりにスムーズに見えるとは限りません。
より技術的な詳細として、スタイルが変更されると、ブラウザは新しい外観をレンダリングするために3つのステップを実行します。
- レイアウト: 各要素のジオメトリと位置を再計算し、次に
- ペイント: 背景、色などを含め、すべてがどのように見えるかを再計算し、
- コンポジット: 最終結果を画面上のピクセルにレンダリングし、CSS変換が存在する場合は適用します。
CSSアニメーション中は、このプロセスがフレームごとに繰り返されます。ただし、color
のようにジオメトリや位置に影響を与えないCSSプロパティは、レイアウトステップをスキップする場合があります。color
が変更された場合、ブラウザは新しいジオメトリを計算せず、ペイント→コンポジットに進みます。また、コンポジットに直接進むプロパティもいくつかあります。CSSプロパティのより長いリストと、それらがどのステージをトリガーするかは、https://csstriggers.comで見つけることができます。
計算には、特に要素が多くレイアウトが複雑なページでは、時間がかかる場合があります。また、遅延は実際にはほとんどのデバイスで目に見え、「ぎくしゃくした」、滑らかでないアニメーションにつながります。
レイアウトステップをスキップするプロパティのアニメーションは高速です。ペイントもスキップされるとさらに良くなります。
transform
プロパティは、次の理由から最適な選択肢です。
- CSS変換は、ターゲット要素ボックス全体に影響を与えます(回転、反転、ストレッチ、シフト)。
- CSS変換は、隣接要素に影響を与えません。
…そのため、ブラウザは既存のレイアウトとペイントの計算に加えて、コンポジットステージでtransform
を適用します。
言い換えれば、ブラウザはレイアウト(サイズ、位置)を計算し、ペイントステージで色、背景などでペイントし、次に必要に応じて要素ボックスにtransform
を適用します。
transform
プロパティの変更(アニメーション)は、レイアウトとペイントのステップをトリガーしません。さらに、ブラウザはCSS変換にグラフィックアクセラレータ(CPUまたはグラフィックカード上の特別なチップ)を活用するため、非常に効率的です。
幸いなことに、transform
プロパティは非常に強力です。要素にtransform
を使用することで、回転、反転、ストレッチ、縮小、移動など、さまざまな操作を実行できます。そのため、left/margin-left
プロパティの代わりにtransform: translateX(…)
を使用したり、要素のサイズを増やすためにtransform: scale
を使用したりできます。
opacity
プロパティもレイアウトをトリガーしません(Mozilla Geckoではペイントもスキップします)。表示/非表示またはフェードイン/フェードアウト効果に使用できます。
transform
とopacity
を組み合わせることで、通常、ほとんどのニーズを満たすことができ、滑らかで見た目も良いアニメーションを提供できます。
たとえば、ここで#boat
要素をクリックすると、transform: translateX(300px)
とopacity: 0
を持つクラスが追加され、300px右に移動して消えます。
<img src="https://js.cx/clipart/boat.png" id="boat">
<style>
#boat {
cursor: pointer;
transition: transform 2s ease-in-out, opacity 2s ease-in-out;
}
.move {
transform: translateX(300px);
opacity: 0;
}
</style>
<script>
boat.onclick = () => boat.classList.add('move');
</script>
@keyframes
を使用した、より複雑な例を次に示します。
<h2 onclick="this.classList.toggle('animated')">click me to start / stop</h2>
<style>
.animated {
animation: hello-goodbye 1.8s infinite;
width: fit-content;
}
@keyframes hello-goodbye {
0% {
transform: translateY(-60px) rotateX(0.7turn);
opacity: 0;
}
50% {
transform: none;
opacity: 1;
}
100% {
transform: translateX(230px) rotateZ(90deg) scale(0.5);
opacity: 0;
}
}
</style>
まとめ
CSSアニメーションを使用すると、1つまたは複数のCSSプロパティをスムーズに(または段階的に)アニメーション化して変更できます。
ほとんどのアニメーショんタスクに適しています。JavaScriptを使用してアニメーションを作成することもできます。次の章では、それについて説明します。
JavaScriptアニメーションと比較したCSSアニメーションの制限
- 単純なことは単純にできる。
- CPUに高速で軽量。
- JavaScriptアニメーションは柔軟です。要素の「爆発」など、あらゆるアニメーションロジックを実装できます。
- プロパティの変更だけではありません。アニメーションの一部としてJavaScriptで新しい要素を作成できます。
この章の最初の例では、font-size
、left
、width
、height
などをアニメーション化しました。実際のプロジェクトでは、パフォーマンスを向上させるために、transform: scale()
とtransform: translate()
を使用する必要があります。
ほとんどのアニメーションは、この章で説明したようにCSSを使用して実装できます。また、transitionend
イベントを使用すると、アニメーションの後にJavaScriptを実行できるため、コードと適切に統合されます。
しかし、次の章では、より複雑なケースをカバーするために、いくつかのJavaScriptアニメーションを作成します。
コメント
<code>
タグを使用し、数行の場合は<pre>
タグで囲み、10行を超える場合はサンドボックス(plnkr、jsbin、codepen…)を使用してください。