2021年12月15日

CSS アニメーション

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 の順番で一緒に宣言できること、複数のプロパティを一度にアニメーションすることができることに留意しておいてください。

例えば、このボタンは colorfont-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 を指定した場合、アニメーションはある変更の1秒後に始まります。

負の値も可能です。その場合、アニメーションは途中から始まります。例えば、transition-duration2s で、遅延が -1s の場合、アニメーションは1秒を取り、半分から開始します。

これは CSS translate プロパティを使って、0 から 9 までの数字をシフトするアニメーションです:

結果
script.js
style.css
index.html
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 を使って、例えば、現在の秒数に対応する正確な数値から始めることができます。

ここでは、数字をクリックすると – 現在の秒数からアニメーションが始まります。:

結果
script.js
style.css
index.html
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 は追加の行でそれをしています。:

stripe.onclick = function() {
  let sec = new Date().getSeconds() % 10;
  // 例えば、ここで -3s は3番目からアニメーションを開始します
  stripe.style.transitionDelay = '-' + sec + 's';
  stripe.classList.add('animate');
};

transition-timing-function

タイミング関数はアニメーションプロセスが時間と共にどのように広がっていくかを記述します。ゆっくりと始まりその急速に進む、またはその逆もありえます。

これは一見すると最も複雑なプロパティです。しかし少し時間をかけて見れば、非常に簡単のものになります。

このプロパティは2種類の値を受け取ります: ベジェ曲線またはステップです。より頻繁に使われる曲線から見ていきましょう。

ベジェ曲線

タイミング関数は次の条件を満たす4つの制御点をもつ ベジェ曲線 として設定できます。:

  1. 最初の制御点: (0,0).
  2. 最後の制御点: (1,1).
  3. 中間点については、x の値は区間 0..1 になければならず、y は何でも構いません。

CSS でのベジェ曲線の構文です: cubic-bezier(x2, y2, x3, y3)。 ここでは 2番目と3番目の制御点だけを指定します。なぜなら、最初の点は (0,0) 固定であり、4番目は (1,1) 固定だからです。

タイミング関数は時間の中でアニメーション処理がどのような速さで進むかを記述します。

  • x 軸は時間です: 0 – は開始時点、1 – は transition-duration の最後の瞬間です。
  • y 軸は処理の完了を指定します: 0 – はプロパティの開始値であり, 1 – は終わりの値です。

最もシンプルなバリアントは、同じ線形の速度でアニメーションが均一に進む場合です。それは曲線 cubic-bezier(0, 0, 1, 1) として指定することができます。

これは、その曲線がどのように見えるかを示したものです:

…ご覧の通り、単なる直線です。時間(x)が過ぎるに連れて、アニメーションの完了(y)は着実に 0 から 1 に進みます。

下の例にある電車は、左から右へ一定の速度で移動します(クリックしてみてください):

結果
style.css
index.html
.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);
  /* JavaScript sets left to 450px */
}

…そして電車をスローダウンさせるにはどうすれば良いでしょうか?

別のベジェ曲線を使うことで実現できます: cubic-bezier(0.0, 0.5, 0.5 ,1.0).

グラフは次のようになります:

見てわかるように、処理は速く始まります: 曲線は高くなっていき、その後遅くなっていきます。

タイミング関数は次のように動作します(電車をクリックしてください):

結果
style.css
index.html
.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);
  /* JavaScript sets left to 450px */
}

いくつかの組み込みの曲線があります: linear, ease, ease-in, ease-out そして ease-in-out です。

linearcubic-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;
  /* transition: left 5s cubic-bezier(0, .5, .5, 1); */
}

しかし、実際は少し異なって見えます。

ベジェ曲線はアニメーションがその範囲から “飛び出す” ようにすることができます。

曲線上の制御点は、負または巨大な値の y 座標を持つことができます。すると、ベジェ曲線も非常に低くまたは高くジャンプし、アニメーションが通常の範囲を超えます。

下の例のアニメーションコードは次の通りです:

.train {
  left: 100px;
  transition: left 5s cubic-bezier(.5, -1, .5, 2);
  /* JavaScript sets left to 400px */
}

プロパティ left100px から 400px までアニメーションするはずです。

しかし、電車をクリックすると、次のようになります:

  • まず、電車は バック します: left100px よりも小さくなります。
  • 次に前に進み、400px よりも少し先に進みます。
  • その後再びバックし – 400px になります。
結果
style.css
index.html
.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番目の点は 1 を越えています。そのため、曲線は “通常” の象限から外れています。y は “標準” の範囲 0..1 から外れています。

ご存知の通り、y は “アニメーション処理の完了” を表します。値 y = 0 は開始プロパティ値に対応し、y = 1 は – 終わりの値に対応します。そのため、値 y<0 はプロパティを開始時の left よりも小さい値に移動させ、y>1 は – 最後の left を越えます。

これは確実に “ソフトな” バリアントです。もし y の値を -9999 といった値にした場合、電車はその範囲から遥か遠くに飛び出します。

しかし、特定のタスクのためのベジェ曲線はどうやって作るのでしょう?そのための多くのツールがあります。例えば、http://cubic-bezier.com/ などで行うことができます。

Steps

タイミング関数 steps(number of steps[, start/end]) はアニメーションをステップに分割することができます。

数字を使った例を見てみましょう。私たちは数字を滑らかではなく、離散的に変化させます。

そのために、アニメーションを 9 つのステップに分割します:

#stripe.animate  {
  transform: translate(-90%);
  transition: transform 9s steps(9, start);
}

steps(9, start) の動作です:

結果
style.css
index.html
#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>

最初の steps の引数はステップの数です。変換は 9 つのパートに分割されます(それぞれ 10%)。時間間隔も同様に分割されます: 9秒は1秒間隔になります。

2つ目の引数は start または end いずれかの単語です。

start はアニメーション開始時にすぐに最初のステップを行うことを意味します。

アニメーションでそれが確認できます。数字をクリックすると、すぐに 1 (最初のステップ) に変わり、以降は次の秒のはじめに変化していきます。

プロセスはこのように処理されます:

  • 0s-10% (1秒目の頭で最初の変更がされます, 開始直後)
  • 1s-20%
  • 8s-80%
  • (最後の秒は最後の値を表示します)。

もう1つの値 end は、変化は各秒の最初ではなく最後に適用されるようにすることを意味します。

したがって、処理は次のように進みます:

  • 0s0
  • 1s-10% (最初の変更は1秒目の最後です)
  • 2s-20%
  • 9s-90%

steps(9, end) の動作です:

結果
style.css
index.html
#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>

簡略表記もあります:

  • step-start – は steps(1, start) と同じです。つまり、アニメーションはすぐに始まり、ステップは1つです。なので、これは開始後すぐに終わり、まるでアニメーションがないかのように見えます。
  • step-end – は steps(1, end) と同じです。: transition-duration の終わりに単一ステップのアニメーションを行います。

これらの値はほとんど使われません。なぜなら、実際にはアニメーションではなく、単なる単一ステップの変更だからです。

transitionend イベント

CSS アニメーションが終了すると、transitionend イベントがトリガされます。

これはアニメーション完了後になにかをするのに広く使われています。また、アニメーションを付け加える事もできます。

例えば、下の例にある船は、クリックで往復し始めます。時間が経つごとにどんどん右に行きます。

アニメーションは関数 go によって開始され、遷移が終了して方向を反転する度に go が再実行されます:

boat.onclick = function() {
  //...
  let times = 1;

  function go() {
    if (times % 2) {
      // 右に進みます
      boat.classList.remove('back');
      boat.style.marginLeft = 100 * times + 200 + 'px';
    } else {
      // 左に進みます
      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)

@keyframes という CSS のルールを使用して、複数の簡単なアニメーションを一緒に動作させることができます。

この方法では、アニメーションの “名前” と、何を/いつ/どこでアニメーションさせるかのルールを指定します。その後、animation プロパティを使ってアニメーションと要素の紐づけを行い、追加のパラメータを指定していきます。

これは説明付きの例です:

<div class="progress"></div>

<style>
  @keyframes go-left-right {        /* 名前を指定します: "go-left-right" */
    from { left: 0px; }             /* left: 0px からアニメーションを開始します */
    to { left: calc(100% - 50px); } /* left: 100%-50px までアニメーションします */
  }

  .progress {
    animation: go-left-right 3s infinite alternate;
    /* アニメーション "go-left-right" を要素に適用します
       期間は 3 秒 (3s)
       回数: 無限 (infinite)
       順方向/逆方向を毎回交互に (alternate)
    */

    position: relative;
    border: 2px solid green;
    width: 50px;
    height: 20px;
    background: lime;
  }
</style>

@keyframes詳細な仕様 について多くの記事があります。

ただし、あなたのサイト上で常に動いているものがない限り、恐らく @keyframes を頻繁に必要とはしないでしょう。

サマリ

CSS アニメーションは、1つ以上の CSS プロパティの変更をなめらかにアニメーション化できます。

これはほとんどのアニメーションのタスクに適しています。なお、アニメーションに JavaScript を使うこともできます。次のチャプターではそれを見ていきます。

JavaScript アニメーションと比較した場合の CSS アニメーションの制限は次の通りです:

メリット
  • 簡単なことは簡単にできます。
  • CPU に対し高速であり軽量です。
デメリット
  • JavaScript アニメーションは柔軟です。 要素の “爆発” のような任意のアニメーションロジックを実装することができます。
  • 単なるプロパティの変更ではありません。JavaScript ではアニメーション目的で新しい要素を作成すると言ったことが可能です。

大部分のアニメーションはこのチャプターで説明した CSS を使用して実装することができます。そして transitionend イベントはアニメーションの後に JavaScript を実行することができるので、コードともうまく統合できます。

しかし、次のチャプターではより複雑なケースを取り扱うため、 JavaScript アニメーションをいくつか見ていきます。

タスク

重要性: 5

下の図のようなアニメーションを表示してください(飛行機をクリックしてください)。:

  • 写真はクリックで 40x24px から 400x240px まで大きくなります(10倍)
  • アニメーションは 3秒です
  • 最後に “Done!” を出力します
  • アニメーション処理中にも飛行機がクリックがされる可能性がありますが、それらは何も “壊すべきではありません”。

タスクのためのサンドボックスを開く

widthheight 両方をアニメーションする CSS です:

/* 元の class */

#flyjet {
  transition: all 3s;
}

/* JS で .growing を追加*/
#flyjet.growing {
  width: 400px;
  height: 240px;
}

transitionend は 2回トリガすることに注意してください – すべてのプロパティに対して1度トリガします。したがって、追加のチェックをしない場合、メッセージは2度表示されます。

サンドボックスで解答を開く

重要性: 5

前のタスク 飛行機をアニメーションする (CSS) の解答を修正して、元のサイズ 400x240px よりも大きくなり(飛び出し)、その後元のサイズに戻るようにしてください。

次のように見えるようにします(飛行機をクリックしてください):

このアニメーションに対する正しいベジェ曲線を選ぶ必要があります。“飛び出す” ようにするため、どこかで y>1 となるタイミングが必要です。

例えば、cubic-bezier(0.25, 1.5, 0.75, 1.5) のように、両方の制御点が y>1 を取ることができます。

グラフは次の通りです:

サンドボックスで解答を開く

重要性: 5

アニメーションで大きくなる円を表示する関数 showCircle(cx, cy, radius) を作成してください。

  • cx,cy は、円の中心のウィンドウ相対座標です。
  • radius は円の半径です。

どのように表示されるかは下のボタンをクリックしてください:

ソースとなるドキュメントには正しいスタイルの円の例があるので、このタスクは正しいアニメーションを行うようにすることです。

タスクのためのサンドボックスを開く

タスク アニメーションサークル には、アニメーションで大きくなる円があります。

今、ただの円ではなくその中にメッセージを表示する必要があるとしましょう。メッセージはアニメーションが完了した(円が完全に大きくなった) に出現させたほうが良いです。そうでないと醜いためです。

このタスクの解答では、関数 showCircle(cx, cy, radius) が円を描きます。が、いつ準備ができたかを追跡する方法は提供していません。

アニメーションが完了したときに呼ばれるコールバック引数を追加してください: showCircle(cx, cy, radius, callback)callback は引数として円の <div> を受け取ります。

例:

showCircle(150, 150, 100, div => {
  div.classList.add('message-ball');
  div.append("Hello, world!");
});

デモ:

タスク アニメーションサークル の解答を、このタスクのベースに使ってください。

チュートリアルマップ