🕰️

[CSS]時計を作ってちょっと分かった気になるanimationプロパティ

2024/12/07に公開

この記事は「Commune Advent Calendar 2024」シリーズ2の7日目の記事です。

1. はじめに

はじめまして。Communeでエンジニアをしています、佐々木です。普段の業務ではフロントエンド・サーバーサイド両方の実装を行なっています。

今回は、実際に動く時計をできるだけCSSだけで作ってみて、CSSアニメーションでできることを紹介しようと思います。

2. この記事で取り上げる内容

クラシカルな感じの時計を作成しました。この記事では、時計の針が時間を刻むようにするためのアニメーション設定部分のみを取り上げます。

コード全文はCodePen上で公開しています。

3. CSSアニメーションについて

CSSではanimationプロパティと@keyframesルールを使って、javascriptを使わずにアニメーションを実装することができます。animationプロパティはアニメーションの再生の仕方(再生時間やタイミングなど)を指定し、@keyframesはアニメーション中の外観の遷移を指定します。

参照:https://developer.mozilla.org/ja/docs/Web/CSS/CSS_animations/Using_CSS_animations

3.1 animationプロパティ

animationプロパティには以下のサブプロパティがあります。太字になっているものが今回取り上げるプロパティになります。

  • animation-name: @keyframesを指定
  • animation-duration: 再生時間
  • animation-timing-function: アニメーションの進め方
  • animation-delay: 遅延時間
  • animation-iteration-count: アニメーションの繰り返し回数
  • animation-direction: アニメーションの向き(順再生、巻き戻しなど)
  • animation-fill-mode: アニメーション実行前後のスタイル指定
  • animation-play-state: アニメーションの一時停止と再開

3.2 @keyframesルール

CSSのアットルールの一つで、アニメーション中に外観がどのように変化していくかを指定することができます。

書き方は以下のようになります。

@keyframes slidein {
  0% {
    transform: translateX(0%);
  }
  100% {
    transform: translateX(100%);
  }
}

slideinの部分はそのkeyframesの名前で、予約語以外なら任意の値にすることができます。また、0%はfrom、100%はtoと書いても同じ意味になります。

4. まずは動かしてみる:@keyframesで針を回転させる

最初に@keyframesルールを作成します。
時計の針のアニメーション中の外観変化は、12時の方向からぐるっと回って一周するだけなので、@keyframesとしてはこのようになるかと思います。

(時計の針は英語では"hand"と言うそうです)

@keyframes rotateHand {
  from {
    transform: rotate(0deg); //最初は0度
  }
  to {
    transform: rotate(360deg); //最終的に360度回転
  }
}

このkeyframesを使って秒針を回転させてみます。使うkeyframesを指定するには、animation-nameを設定します。また、とりあえず秒針を動かすために、animation-duration(再生時間)とanimation-iteration-count(繰り返し設定)も適当に設定します。

.second {
  ...
  animation-name: rotateHand;
  animation-duration: 3s;
  animation-iteration-count: infinite; //無限にアニメーションを繰り返す
}

動きました。

ただ、針の動きに不要な緩急がついているので、修正していきます。

5. 針の動きを調整する:animation-timing-function

@keyframesではアニメーション中のある地点におけるスタイルを指定しました。
animation-timing-functionは、keyframe間のアニメーション遷移の仕方を指定することができます。

デフォルトではeaseという値が設定されます。easeにすると、アニメーションの中間地点で最も変化量が大きくなり、始まりと終わりは変化量が小さくなるようになります。なので、先ほどの設定では、秒針の動きに緩急がついたようなアニメーションになりました。

変化量が常に同じになるようにするにはanimation-timing-functionにlinearを設定します。

.second {
  ...
  animation-name: rotateHand;
  animation-duration: 3s;
  animation-iteration-count: infinite;
  animation-timing-function: linear;
}

針が滑らかに動くようになりました。

針がチクタクと1秒ごとに進むようにしたい場合は、stepsを使うことで実現できます。アニメーションの開始から終わりまでをn分割することができます。

.second {
  ...
  animation-name: rotateHand;
  animation-duration: 3s;
  animation-iteration-count: infinite;
  animation-timing-function: steps(60); //60stepで針が1週する
}


animation-timing-functionに指定できる値は他にもあります。
https://developer.mozilla.org/ja/docs/Web/CSS/animation-timing-function

6. 時針・分針・秒針を動かす:animation-duration

秒針以外の針も動かしてみましょう。針が1周する時間はそれぞれ以下のようになります。

  • 秒針→60秒
  • 分針→60 * 60 = 3600秒
  • 時針→60 * 60 * 24 = 43200秒

アニメーションの再生時間はanimation-durationで指定できます。

.second {
  ...
  animation-name: rotateHand;
  animation-duration: 60s; //60秒で1週
  animation-iteration-count: infinite;
  animation-timing-function: linear;
}

.minute {
  ...
  animation-name: rotateHand;
  animation-duration: 3600s; //60分で1週
  animation-iteration-count: infinite;
  animation-timing-function: linear;
}

.hour {
  ...
  animation-name: rotateHand;
  animation-duration: 43200s; //24時間で1週
  animation-iteration-count: infinite;
  animation-timing-function: linear;
}

これで全ての針が時間を刻んで動くようになりました。

7. 現在時刻を反映する:animation-delay

現在時刻を反映するにはjavascriptを使って現在時刻を取得し、animation-delayを使ってアニメーションのスタート位置を調整します。

animation-delayとは名前の通りアニメーションの開始を遅らせるプロパティです。2sと設定するとアニメーションが2秒遅れで開始されるのですが、マイナスの値を設定することで、アニメーションを設定したマイナスの値だけ経過した状態からスタートさせることができます。

例として秒針の場合を考えます。
現在時刻の秒数が40秒だった場合、秒針は40秒の位置からスタートさせたいですよね。
そこでanimation-delay: -40sとすると、アニメーションがあたかも40秒経過した状態からスタートしたかのように見せることができます。

分針・時針についても同じですが、注意点として、分針であれば秒針の進み分を考慮する必要があります。秒針が59秒であれば分針はほぼ次の分に近い位置まで進んでいるようにしてあげます。時針の場合は分針・秒針の進み分を考慮します。

javascriptで書くと以下のようになります。animation-delayはjavascriptからstyleに設定しています。

function setInitialTime() {
  const now = new Date();
  const seconds = now.getSeconds();
  const minutes = now.getMinutes();
  const hours = now.getHours();

  const secondHand = document.querySelector('.second');
  const minuteHand = document.querySelector('.minute');
  const hourHand = document.querySelector('.hour');

  // 秒、分、時のアニメーション開始位置を計算
  const secondDelay = -seconds; // 秒針の遅延
  const minuteDelay = -(minutes * 60 + seconds); // 分針の遅延
  const hourDelay = -(hours * 3600 + minutes * 60 + seconds); // 時針の遅延

  // アニメーション開始時刻を設定
  secondHand.style.animationDelay = `${secondDelay}s`;
  minuteHand.style.animationDelay = `${minuteDelay}s`;
  hourHand.style.animationDelay = `${hourDelay}s`;
}

setInitialTime();

これで実際に動く時計の完成です!

8. 効率よく書く:animationプロパティ

ここまではanimationプロパティのサブプロパティを一つずつ記述してきましたが、animationプロパティを使ってショートハンドで一括で指定することもできます。

.second {
  ...
  animation: rotateHand 60s infinite linear;
}

それぞれの値は基本的にはそれぞれのサブプロパティに固有の値となっているため、どんな並び順で書いても*問題ありません。

*サブプロパティの予約語(infiniteやlinear)を@keyframesの名前に使用してしまうとうまく動かない原因になります。
*animation-delayはanimation-durationと指定値の型が同じ[s]または[ms]なので、ショートハンドで両方書く場合はdurationを先に書く必要があります。

9. まとめ

時計のようにCSSアニメーションに用意されているプロパティでちょうどよく作成できるアニメーションは少ないかもしれませんが、使い方自体はとてもシンプルで使いやすいと思いました。

知らないだけでCSSの表現力はもっと高いと思っているので、よりリッチでユーザビリティの高いプロダクト開発に向けて今後さらに勉強してみたいです。

コミューン株式会社

Discussion