css3でローディングアニメーションを作るチュートリアル

2013年04月01日

css3が各種ブラウザで使えるようになってきて、色々とcssで表現できることが増えて嬉しいですね。今回はその中でもちょっと異彩を放つ(と僕は思ってる)cssアニメーションを紹介したいと思います。

また、cssでアニメーションを実装する場合は僕としてはCSSプリプロセッサーの力を借りることで工数の短縮に繋がると感じたので、今回はsass(scss)とcompassを合わせて解説したいと思います。

というかね、cssアニメーションめんどくせーんすよ!

cssプリプロセッサーないと凄くめんどくさい!!

成果物

今回作成したcssアニメーションです。多分どこかで見たことがある感じのローディングアニメーションなんじゃないでしょうか。

ローディングアニメーション完成イメージ

特徴

  • 割と簡単に色を変えられます
  • 割と簡単にサイズを変えられます
  • 割と簡単に個数も変えられます
  • 上下中央揃えしてます
  • 個数とサイズを変えても上下中央は譲らない

まぁ最初のは当たり前じゃね?でしょうけど、それ以外の項目に関してはcssだと結構面倒な修正が必要なんですが、sass(scss)を使用する事で簡単に修正が可能になっています。凄いぜcssプリプロセッサー!!

ただ、マークアップ的には空のタグが入っているのでちょっとアレなんですがこれ何とかならないかなぁと今も思いながら書いてるので許してくれても良いと思います。

htmlの解説

まずはhtmlです。

<div class="circleLoading">
  <div><span></span></div>
  <div><span></span></div>
  <div><span></span></div>
  <div><span></span></div>
  <div><span></span></div>
  <div><span></span></div>
  <div><span></span></div>
  <div><span></span></div>
</div>

circleLoading が全体を包むボックスで、その中の div が一つの丸を内包するボックス、span が一つの丸になります。

さて、これにcssを付与して行くわけですが、まずはその考え方から解説します。

考え方

まず、ローディングアニメーションを作る際に気にしたポイントを上げていきます。

  • 1つ1つの円を丸く等間隔に配置
  • 1つの円は大きい状態から徐々に小さくなる
  • 小さくなった後は一気に大きくなる
  • その動きを各円で等間隔に時間をずらす
  • ずらす時間の合計は1つのアニメーションにかかる時間と合わせる

こんな感じを実現できれば良い感じになるんじゃないかと思います。

配置

小さな円を作成

まずは、spanで円をつくり、円を内包するdivを作りましょう。

円と円を内包するブロックのイメージ図

こんなんを作ります

scss
.circleLoading {
  div {
    width: 60px;
    height: 60px;
    span {
      display: block;
      background: #d14836;
      width: 16px;
      height: 16px;
      border-radius: 8px;
    }
  }
}

border-radius で縦横の半分を指定すれば円が出来ます。

大きな円に沿って小さな円を配置

次に、1つ1つの div を少しずつ傾けて配置し、全部配置したときには div よりもちょっと大きな円を描いている状態にします。

オブジェクトを傾かせる場合は、 position: absolute; と transform:rotateZ(); を使えば実現できます。

1. position: absolute; で同じ場所に div span を重ねる 2. 左上を起点にtransrate: rotatez(45n); で45度ずつかたむけると、8つでちょうど円っぽくなる(nはspanのnth-child-1、便宜上)

scssのソースはこんな感じになります。

scss
.circleLoading {
  div {
    position: absolute;
    width: 60px;
    height: 60px;
  }
}
// ↓角度をつけるための繰り返し
@for $i from 1 through 8 {
  .circleLoading div:nth-child(#{$i}) {
    $j: ($i - 1 ) * ( 360 / 8 );
    -webkit-transform: rotateZ(#{$j}deg);
  }
}

展開したcssの繰り返し部分(抜粋)
.circleLoading div:nth-child(1) {
  -webkit-transform: rotateZ(0deg);
}

.circleLoading div:nth-child(2) {
  -webkit-transform: rotateZ(45deg);
}

.circleLoading div:nth-child(3) {
  -webkit-transform: rotateZ(90deg);
}
〜

まず、div に position: absolute; をかけて、8つあるdivを全部同じ場所に重ねて配置されるようにします。

次に、各divに対して一つずつ360度を個数の8で割った角度ずつ変更したいので、傾きの変形を行う transform: rotateZ(~); を使用します。

その際、span:nth-child(1) から (8) まで一つずつ指定しても良いのですが、そうするとちょっと修正する度に全部の箇所を修正しなければならなくなりますね。

今回の例だと、等間隔に配置しなければ綺麗な縁になりませんので、例えば10個に変更したい場合、今まで45度ずつ角度を変えていた箇所を30度ずつに変更するので、全てのdivを書き直す必要があります。

でも、scssで記述していれば赤字で記述した箇所だけを変更するだけで全ての角度が変更できます。

ついでに@for ~ の解説

@for ~ の箇所は、sass の機能で、同じような処理を繰り返す場合に使います。

@for は、 $i を from で指定した数値から through で指定した数値まで、{ から } までの処理を繰り返し行います。なので、今回のケースだと

.circleLoading div:nth-child(#{$i}) {
  $j: ($i - 1 ) * ( 360 / 8 );
  -webkit-transform: rotateZ(#{$j}deg);
}

この部分が1から8までの8回繰り返されることになります。

nth-child(1) は角度を変えなくて良いので、0度、次からは360度を個数の8で割った数値ずつ傾けたいので、 $i 番目の傾きはこのように計算できますね。

i番目の傾き($j) = $i-1 × (360度 ÷ 8個)

これをscssの記述に変更したのが上記になります。

一つの円のアニメーション

円の配置が完了しましたので、次は一つの円に対するアニメーションを追加していきます。

今回の動きは、「大きい円が徐々に小さくなって、一番小さくなったらパッと大きくなる」って感じの動きを実装したいと思います。

なので、 animation と transform を使って時間によってサイズを変えるアニメーションを追加します。

scss
span {
  -webkit-animation: scale 1.6s linear infinite forwards;
}
@-webkit-keyframes 'scale' {
  0%   { -webkit-transform: scale( 0.1, 0.1 );}
  10%  { -webkit-transform: scale( 1, 1 );}
  100% { -webkit-transform: scale( 0.1, 0.1 );}
}

これはアニメーションの部分だけの抜粋ですが、まず、spanに対して animation プロパティを追加しています。

animationプロパティは要素にキーフレームアニメーションを追加する事が出来るプロパティで、どういうアニメーションをどういうタイミングで行いたいかの指定を別途 @-webkit-keyframes で指定します。

その他、「アニメーションに何秒かけるか」「イージングはどうするか」「一度きりか繰り返しか」「アニメーションが終わった後はどうするか」などの指定が可能です。

ここでは animation プロパティについては詳しく解説しませんので、こちらなどを見て頂けると分かるかと思いますよ。

ここでは、 scale という名前がついたキーフレームアニメーションを通常のイージングで1回1.6で行い、繰り返すという指定です。

キーフレームアニメーションの指定

キーフレームアニメーションの指定は、以下のような記述になります。

@-webkit-keyframes 'scale' {
  0%   { -webkit-transform: scale( 0.1, 0.1 );}
  10%  { -webkit-transform: scale( 1, 1 );}
  100% { -webkit-transform: scale( 0.1, 0.1 );}
}

キーフレームアニメーションでは、アニメーション開始から何%のタイミングでどのようなプロパティをどの値にするかを指定すれば、間の時間にアニメーションをしてくれます。

今回だと、「開始時は大きさを縦横0.1倍にサイズ変更して、10%(全体が1.6秒なので0.16秒)でサイズを元の大きさに戻す。そして100%(1.6秒後)には大きさを0.1倍に変更する」って感じです。

ちなみに、「最初は通常の大きさで、100%で0.1。これだけじゃダメなの?」って思う方もいるかな?と思いましたが、実際それでアニメーションをさせてみると、1周目だけちょっと不自然になってしまうのでこの動きの方が良いなと思いました。

動きを各円で等間隔に時間をずらす

これで配置も動きも出来ましたが、全ての円に対して一度にアニメーションをかけてしまうとローディングっぽくなりませんよね。なので、ずらしましょう。

今回の小さい円のアニメーションは一つが1.6秒に設定してあるので、8つで1.6秒を等間隔にわけた分ずつずらしていけば綺麗な動きのアニメーションになります。

なので、先ほど使った @for ~ を使っていきましょう。

scss
@for $i from 1 through $objNum {
  .circleLoading div:nth-of-type(#{$i}) span {
    $j: ($i - 1) * (1.6 / 8);
    -webkit-animation-delay: #{$j}s;
  }
}

展開後のcss(抜粋)
.circleLoading div:nth-of-type(1) span {
  -webkit-animation-delay: 0s;
}

.circleLoading div:nth-of-type(2) span {
  -webkit-animation-delay: 0.2s;
}

先ほど @for ~ については説明をすでにしているのでここでは省きます。

$i個目の div の中の span に対して、アニメーションの開始するタイミングをanimation-delay プロパティで、1.6秒を個数の8で割った数だけ遅らせる、という指定をしています。

これで完成。そして更なる高みへ

一応これでアニメーション自体は完成しました。

でも、これを更に便利に使い回しが効くように、一手間二手間追加して行きたいと思います。

  • ちゃんと上下中央揃え
  • 変数を使って簡単にカスタマイズ

ちゃんと上下中央揃え

今回作成したアニメーションは、transform でボックスを傾けているため、css上のサイズと実際のボックスサイズに相違が出来てしまっています。

css上のサイズより実際のサイズの方が大きくなってしまうので、ちゃんと上下中央にならない!

そのため、上下中央揃えのスタンダードプランである下記の記述では正しく上下中央に揃ってくれません。

scss
.content {
  position: relative;
  .circleLoading {
    position: absolute;
    width: 60px;
    height: 60px;
    left: 50%;
    top: 50%;
    margin-left: -30px; // 横幅の半分
    margin-top: -30px; // 高さの半分
  }
}

これを解消するためには、 .circleLoading の横幅と高さを取得する必要があります。

.circleLoading の高さは、図で見ると分かりやすいですが、 「div(正方形)の高さの対角線の長さ」ですね。という事は、正方形の横と縦と対角線の比率は 1:1:ルート2 になります。

ここで、sassでcompassを使用している場合には、compassの機能で平方根の計算が使えますので、ありがたく使わせてもらいましょう。

compassの平方根の計算は以下の通り

sqrt($number)

今回は div の横幅×2の平方根なので、このような記述になります。

.circleLoading {
  width: 60px * sqrt(2);
  height: 60px * sqrt(2);
  margin-left: -60px * sqrt(2) /2; 
  margin-top: -60px * sqrt(2) /2; 
}

現状

これでサイズは正しいサイズになったのですが、結局起点の位置がずれているため、これではちゃんと上下中央に揃いません。

Loading04

なので、 内包する div にマージンを追加してあげて、位置を揃えましょう。

追加するマージンサイズは、三角形の飛び出している部分の長さになるので、以下で求めることが出来ますね。

マージンサイズ = ( 対角線の長さ – 一辺の長さ ) ÷ 2

これを scss に記述します。

scss
.circleLoading {
  div {
    margin: ( 60px * sqrt(2) - 60px ) /2 0 0 ( 60px * sqrt(2) - 60px ) /2;
  }
}

これでやっと上下中央揃えが実装できました。めんどくさいねーw

変数を使ってカスタマイズを簡単に

今まで書いてきたコードは scss と compass の機能を使って随分簡潔に書けるようになりました。ただ、まだまだ改善する余地は残されています。

例えば、今回は円の個数を8つにしてますが、それを10個にした場合、8個を前提としていた計算の箇所を全部書き直さなくてはいけないですよね。それでも全部計算し直した数値を入力していく素の css に比べれば楽ですが面倒くさい感はハンパないですね。人間の欲とはすさまじいです。

なので、そういった事にも対応できるように今まで計算した箇所は全て変数にして、修正を容易にしていきましょう。

改善できそうな箇所

枠サイズ

.circleLoading {
  width: 60px * sqrt(2);
  height: 60px * sqrt(2);
  margin-left: -60px * sqrt(2) /2; 
  margin-top: -60px * sqrt(2) /2; 
}

これは、そもそもの内包する div のサイズを変数として計算変数化できます。

// ラップする要素の幅とかマージンとか
// 要素を斜めにするので対角線の計算とかが必要
$divWidth : 60px;
$wrapSize : $divWidth * sqrt(2);
$halfWrapSize : $wrapSize / 2;

.circleLoading {
  width: $wrapSize;
  height: $wrapSize;
  margin: -#{$halfWrapSize} 0 0 -#{$halfWrapSize};
}

小さい円の要素

span {
  width: 16px;
  height: 16px;
  border-radius: 8px;
  background: #d14836;
}

円の各要素変数にします。

$objSize: 16px;
$objColor: #d14836;
span {
  width: $objSize;
  height: $objSize;
  border-radius: $objSize / 2;
  background: $objColor;
}

内包するdiv

div {
  width: 60px;
  height: 60px;
  margin: margin: ( 60px * sqrt(2) - 60px ) /2 0 0 ( 60px * sqrt(2) - 60px ) /2;
}

先ほど変数化した $divWidth がそのまま使えますね。

$wrapDiff: ($wrapSize - $divWidth) / 2;
div {
  width: $divWidth;
  height: $divWidth;
  margin: #{$wrapDiff} 0 0 #{$wrapDiff};
}

アニメーション関連の計算

アニメーションの各種計算には、「要素の個数」、「アニメーションの秒数」を使って計算をしていたので、それらを変数化します。

// オブジェクトの個数
// オブジェクトの個数に合わせて傾きとかを変更する
$objNum: 8;

// 点滅の秒数
// 1つのオブジェクトが点滅する秒数と点滅のディレイを合わせないと
// 見た目繋がって見えない
$blinkTime: 1.6;
$objDelay: $blinkTime / $objNum;

// 角度
// 一周を個数で割れば均等な角度に出来ますね
$objRotate: 360 / $objNum;

これらを使って計算を変数化していきます。

@for $i from 1 through $objNum {
  .circleLoading div:nth-child(#{$i}) {
    $j: ($i - 1 ) * $objRotate;
    -webkit-transform: rotateZ(#{$j}deg);
  }
}

内包する div を傾ける計算を変数化しています。

@for $i from 1 through $objNum {
  .circleLoading div:nth-of-type(#{$i}) span {
    $j: ($i - 1) * $objDelay;
    -webkit-animation-delay: #{$j}s;
  }
}

変数化することで嬉しいこと

このように変数化してまとめて記述しておけば、個数や秒数に変更が入った際にも簡単に対応することが出来ます。

scss(変数記述箇所)
// オブジェクト
$objSize: 16px;
$objColor: #d14836;
$objNum: 8;

// ラップする要素の幅とかマージンとか
// 要素を斜めにするので対角線の計算とかが必要
$divWidth : 60px;
$wrapSize : $divWidth * sqrt(2);
$halfWrapSize : $wrapSize / 2;
$wrapDiff : ($wrapSize - $divWidth) / 2;

// 点滅の秒数
// 1つのオブジェクトが点滅する秒数と点滅のディレイを合わせないと
// 見た目繋がって見えない
$blinkTime: 1.6;
$objDelay: $blinkTime / $objNum;

// 角度
// 一周を個数で割れば均等な角度に出来ますね
$objRotate: 360 / $objNum;

例えば、秒数を変更したい場合は

$blinkTime: 1;

このように、1箇所だけ変更すれば対応が完了します。

個数を変えたいとき、ついでに色も変えたいときは

$objNum: 10;
$objColor: yellow;

このように簡単に変更できます。あ、個数を変更する場合は html にも div を追加する必要があります。

おわりに

死ぬほど長くなってしまいました。果たしてここまで読んでくれる方がいるんでしょうか、凄く不安です。

でも、Web制作をやり出してしばらくしてからは常々思っていることなんですが、「他の人が作った資産を活用して楽する」事も凄く凄く大事だと思うんですが、結局ものを言うのは「自力」なんじゃないかなぁと思うんです。解決法を見つけ出す力というか何というか。

なので、「コッペでできる!cssローディングアニメーション」じゃなくて、ちゃんとある程度の説明をして、「よし!次は自分で考えて作れるぜ!オレは!」って思ってほしいなぁという思いがあり、出来るだけちゃんと説明したつもりですが、いかがでしたでしょうか。

「変数化してできる限り柔軟にする」という考え方はプログラムにも通じるお話しですので、もし今はマークアップのみをしているという方が今後はJSにもチャレンジしたい!という場合にも通用する考え方なので、普段から心がけてみると良い事が多いんじゃないかと思います。

今回のファイル一式

こちらにscssファイルもろともアップしております。

ローディングアニメーションサンプル

他にこんな事も書いてます

FBでコメント

4個のコメント

  1. @sou_lab より:

    SS | css3でローディングアニメーションを作るチュートリアル http://t.co/McDrCFKt5A @anticyborgさんから

  2. 【css】【css3】とても詳しく解説されています RT css3でローディングアニメーションを作るチュートリアル http://t.co/HU0qOjAzOl @anticyborgさんから

  3. SS | css3でローディングアニメーションを作るチュートリアル http://t.co/awATHYV37X @anticyborgさんから

  4. 【昨日書いてた】css3でローディングアニメーションを作るチュートリアル http://t.co/YGwOH5LO5E

トラックバック/ピンバックはありません。