ローディングのくるくる

上のアイコンはサンプルとして表示しているだけなので、待っていても何も起こりませんけれど、一眼ひとめでいかにも読み込み中だとわかるし、待たなきゃとさえ思わせてくるアイコンですよね;)。そんな、ローディング中を代表するくるくる動くアイコンの作り方のメモです。

仕組み

ローディングの動きは、いっぺんにやろうとするとこんがらがっちゃうので、「くるくる回る動き」と、「伸び縮みする動き」に分けて考えるとわかり良いです:)

くるくる回る動き+伸び縮みする動き

伸び縮みする動きを、円じゃなくて、直線に適用してみると下サンプルのようになっています。線の上にカーソルを乗せると(タップすると)仕組みが見えます。
線が伸び縮みしているように見えるけれど、実は伸びても縮んでもなくて、同じ長さの線が左から右へ通り過ぎてるだけなんですね:D

伸び縮みする動き

作り方

くるくる動くアイコンは、SVGCSSを使って作ります。
SVG円を描画するため。CSSは円の回転と、線を動かすために使います。

図形を用意

まずは土台となる図形を、SVGで用意します。と言っても正円なので、イラレを起動するまでもなく、がんばって覚えれば自力で描けちゃえます;D。 htmlファイルに直接、下ソースコードのように記述します。

<svg width="100" height="100" viewBox="0 0 100 100"><circle cx="50" cy="50" r="40"/></svg>
HTML

SVGを100px×100pxの正方形として表示して、その中央に半径40pxの正円を描く、ということになります:)
svg要素で、SVGの表示サイズ表示範囲(※詳しくは後述を指定。circle要素は、cxcyが円の中心点rが円の半径を表しています。まだ真っ黒な玉ですけれど、ローディングらしい見た目になるように、CSSを追加します。

見た目をぽくする

さっきの真っ黒の玉に、塗り色はなし、線の色は青く、線の太さはちょっと太めの10pxになるように、CSSで以下のように指定します。

svg {
	fill: none;
	stroke: dodgerblue;
	stroke-width: 10;
}
CSS

値に単位を付けなければpxとして適用されます。ただし、svg要素のサイズに対して相対的な太さなので注意。

輪郭線を分割する

SVGの線を分割するのには、stroke-dasharrayプロパティを使います。

stroke-dasharrayストローク・ダッシュアレイ
線を破線にするためのプロパティ。
指定した値を交互に、線・隙間・線・隙間…という具合に適用する。例えば、stroke-dasharray: 10, 20, 30と指定すると、10px(線)・20px(隙間)・30px(線)・10px(隙間)…、という具合の破線になる。値がひとつの場合は、線と隙間の間隔が一定な破線になる。
<svg width="250" height="10" viewBox="0 0 250 10" class="line1"><line x1="0" y1="5" x2="250" y2="5"/></svg><br>
<svg width="250" height="10" viewBox="0 0 250 10" class="line2"><line x1="0" y1="5" x2="250" y2="5"/></svg>
.line1 { stroke-dasharray: 10, 20, 30; }
.line2 { stroke-dasharray: 20; }

このプロパティを、さっきの正円に適用すると下サンプルみたくなります。

svg { stroke-dasharray: 20; }

破線の長さを、円周よりもちょっと短いくらいにすれば、ローディングぽい見た目になるってわけ:)
円周の長さは「直径×円周率(πパイ」なので、80 * 3.14ということ。ここでは、円周の長さよりも20px短めの破線になるように、calcキャルク()関数を使って、下記のように指定することにします。

svg { stroke-dasharray: calc(80 * 3.14 - 20); }

うん、ローディングぽい、いまにも廻り出しそうです;D! いよいよ、これを動かしてゆきます!


CSSのcalc()関数について、詳しくは下記ページを参照のこと。

動かし方

ローディングの「くるくる回る動き」と「伸び縮みする動き」を順番につくってゆきます。
まずは「伸び縮みする動き」から:)

伸び縮みする動き

「伸び縮み」とはいえ、実は、同じ長さの線が、表示範囲を通り過ぎれば、伸び縮みしているように見えるのでした。
SVGの線を動かすのには、stroke-dashoffsetプロパティを利用します。

stroke-dashoffsetストローク・ダッシュオフセット
線の始点を指定するためのプロパティ。
stroke-dashoffset: 10と指定すると、始点が10px手前(左方向)に移動する。stroke-dashoffset: -10とすると、始点が10px(右方向)へ移動する。
<svg width="250" height="10" viewBox="0 0 250 10" class="line1"><line x1="0" y1="5" x2="250" y2="5"/></svg><br>
<svg width="250" height="10" viewBox="0 0 250 10" class="line2"><line x1="0" y1="5" x2="250" y2="5"/></svg>
.line1 { stroke-dashoffset: 10; }
.line2 { stroke-dashoffset: -10; }

上サンプルでは、svg要素のサイズが分かるよう、破線の背景にもう一本、細い線を表示させてます。

線の始点の位置を徐々に変化させていけば、破線が動いているように見えるはずです。
下サンプルでは、stroke-dasharray: 20の破線に対して、stroke-dashoffsetプロパティの値を10pxずつ右方向へ動かしています。

.line1 { stroke-dashoffset: 0; }
.line2 { stroke-dashoffset: -10; }
.line3 { stroke-dashoffset: -20; }
.line4 { stroke-dashoffset: -30; }
.line5 { stroke-dashoffset: -40; }

始点が、破線の線と隙間の長さ分移動した時に、ちょうど元の位置に戻ってます。つまり、stroke-dasharrayの値の2倍移動するのを繰り返せば、破線が動き続けているように見えますね:D
線の始点の変化を@keyframes規則で定義したらば、そのキーフレームをanimationプロパティを使って、svg要素に適用します。
下サンプルでは、「線の位置を、0から右方向へ40px移動させる」という、loaderと名付けたアニメーションを、2秒2sかけて、無限infiniteに繰り返すように指定しています。

svg {
	stroke-dasharray: 20;
	animation: loader 2s infinite;
}
@keyframes loader {
	from { stroke-dashoffset: 0; }
	to { stroke-dashoffset: -40; }
}

Safariで変

と思いきや、Safariで見てみると、なんだか動きが変ですね…。ひとつ前のサンプルも、他のブラウザと違う感じになってます(下図):(

Safari(上)とChrome(下)

どうやらSafariでは、stroke-dashoffsetプロパティマイナスの値で指定すると、stroke-dasharrayプロパティで指定した値1ループ分で折り返しちゃうみたいなんです…X(
下のサンプルでは、stroke-dasharray: 20 20 10と指定した破線の始点を、10pxずつ右方向へ動かしています。

Safariで見ると下図みたく、stroke-dasharray1ループ分(50px)ズレたところで、始点が出戻っちゃってるわけですねー。-60pxとなった時、-10pxと同じ状態に戻ります。

stroke-dasharray「1ループ分」で折り返す

なので、Safariのために、stroke-dasharrayの値は必ず、線と隙間ワンセットにする必要があります。

svg {
	stroke-dasharray: 20 20;
}

よし、これで大丈夫;D

あとは、破線の長さをsvg要素の幅よりちょい短めにして、アニメーションすれば、下サンプルのような感じ。冒頭の「伸び縮みする動き」と同じ感じのができました!
ここでは、svg要素の幅250pxよりも20px短い230pxの破線にしています。

svg {
	stroke-dasharray: 230 230;
	animation: loader 2s infinite;
}
@keyframes loader {
	from { stroke-dashoffset: 0; }
	to { stroke-dashoffset: -460; }
}

正円に適用

前述までの「伸び縮みする動き」の破線を、正円にも、円周よりちょい短めで適用します。80 * 3.14が、251.2で割り切れるので、もうcalc()関数は使わずに、ちゃんと計算した値でもって指定することにします。251.2 - 20 = 231.2ですけど、キリよく230で指定:)

svg {
	stroke-dasharray: 230 230;
	animation: loader 2s infinite;
}
@keyframes loader {
	from { stroke-dashoffset: 0; }
	to { stroke-dashoffset: -460; }
}

あとはこれを回せば出来上がり。新たに「くるくる回る動き」を@keyframes規則を定義して、さっきのanimationの値のあとに、カンマ区切りで書き足します。

svg {
	animation: loader 2s infinite, loading 2s infinite;
}
@keyframes loading {
	from { transform: none; }
	to { transform: rotate(360deg); }
}

うん?なんか思ったのと違いますね…:‹
回転が途中で止まるし、回転がゆっくりの時にいちばん短くなって欲しいのに、短い時がものすごく一瞬。伸び縮みするタイミングが悪いみたいです…X(
くるくる回る動きは、回転スピードが一定になるようにlinearを追記して、伸び縮みするタイミングは、破線の始点を半分手前(左方向)にズラしてみます。

svg {
	animation: loader 2s infinite, loading 2s infinite linear;
}
@keyframes loader {
	from { stroke-dashoffset: 230; }
	to { stroke-dashoffset: -230; }
}

うん、だいぶいい感じ! けどもうちょっとニュアンスを気持ちよくしたいなぁ…

微調整

くるくる回るスピードと、伸び縮みするスピードが同じだと単調なので、ちょっとだけズラしてみます。アニメーションの速さだけ変えるときは、animation-durationプロパティを使います。animationプロパティの値がふたつあるので、animation-durationの値もふたつずつ、カンマ区切りで指定します。ひとつめの値がloader(伸び縮みアニメ)の速さ、ふたつめの値がloading(くるくる回るアニメ)の速さになります。
ここでは試しに2パターン用意。

.s1 { animation-duration: 1.2s, 1.8s; }
.s2 { animation-duration: 1.8s, 1.2s; }

ふたつめの、伸び縮みのスピードよりも回るスピードが速い方がいいかなー:‹。あともうちょっとだけ伸び縮みのスピードを速めて、1.4s, 1.2sにすることにします。

伸び縮みの動き具合イージングもいろいろ試してみます。アニメーションのイージングだけを変えるときはanimation-timing-functionプロパティを使います。ここでは、「イージング関数チートシート」から、みっつ見繕ってコピペします。

.easeInCubic { animation-timing-function: cubic-bezier(0.32, 0, 0.67, 0), linear; }
.easeOutCubic { animation-timing-function: cubic-bezier(0.33, 1, 0.68, 1), linear; }
.easeInOutCubic { animation-timing-function: cubic-bezier(0.65, 0, 0.35, 1), linear; }

やっぱり、イン・アウトしている方が自然な感じがしますねー:D
もうちょっとヌルッとした感じを出したいので、「cubic-bezier.com」で、cubic-bezier()関数をちょこっと編集。
動きが柔らかい感じなので、線の先端も丸めたいです。線の先端の形状を指定するにはstroke-linecapプロパティを使って、丸めたいときにはroundを指定します。

svg {
	stroke-linecap: round;
	animation: loader 1.4s infinite cubic-bezier(.4,0,.3,1), loading 1.2s infinite linear;
}

で、出来上がりーXD


@keyframes規則animationプロパティcubic-bezier()関数について、詳しくは以下のページを参照のこと。