HTMLとCSSだけでロード中のぐるぐるを自作する

今回はロード中などに表示されるぐるぐるするアレを作ろうと思います
ほら、あれですよ、F5押したらブラウザのタブのところに出てくるはずです

ところでこれの名前はなんというのだろう

ローディングアイコン」だったり「ローディングスピナー」だったり、様々な呼び方がありますよね
自分が聞いた中で一番長いやつは「サーキュラープログレスインジケーター」ですかね

調べてみましたが、これが正式な呼び方!というのは特になさそうです
なので、ここでは単に「スピナー」と呼ぶことにします

ぐるぐるしながら伸び縮みするやつを作る

今回作るスピナーの完成形はこちらです

昔のスピナーって単に C の形のやつが回ってるだけのものが主流だったと思うんですが、いつの間にか C が伸び縮みし始めましたよね
今回はこの伸び縮み感にこだわって再現していきたいと思います

基本方針としては以下のようになります

  1. SVGで C を描画する
  2. CSSのアニメーションで C を伸び縮みさせる
  3. CSSのアニメーションで C を回す

ちなみに、この記事を作るにあたって、世の中の色々なスピナーをよく観察して見ましたが、みんな似ているようで微妙に違っていました
なので、今回のこの作り方も唯一の正解というわけではないことをご承知おきください

SVGとは

SVGとは Scalable Vector Graphics の頭文字を取ったもので、画像ファイルの形式のひとつです
線や色を数字と計算式で描画しており、ベクター形式と呼ばれています
対して、写真の保存などによく使われる JPG はドットの集まりで画像を表現するもので、ラスター形式と呼ばれていますね
ベクター形式はラスター形式と違い、拡大縮小しても画質が変わらない性質があるので、ロゴ画像などに多く使われています
Adobe Illustrator などで作られることが多いですが、中身はただのxmlなので簡単な円や四角の画像なら手打ちで作れてしまいます
また、HTMLにインラインでそのまま記述することもできます
今回はこの方法を使って C を描画することにします

とりあえず描画してみる

適当なHTMLファイルを作成して、その中に以下のコードを記述します

<svg witdh="120" height="120" viewBox="0 0 120 120">
    <circle r="50" />
</svg>

widthとheightで大きさを指定します(単位を省略するとpxになります)
viewBoxでは画像を描画するキャンバスの座標を決めています
キャンバスを120×120のマス目に分割したイメージです
座標なので単位はありません
一番左上が(0, 0) 、右下が(120, 120)になります
widthとheightの値と一致させたほうがわかりやすいですが、違う値にすることもできます

svgタグの中にcircleタグを記述することで円を描画できます
r属性で半径を指定します
今回は50を指定したので、120×120のキャンパスの中に半径50の円が描かれるわけですね

するとこうなるはずです

見切れてしまいましたね

これは svg内の画像の位置を一番左上を原点とした座標の位置で指定しているからです
circleタグではcx属性とcy属性を指定することで中心の位置を変えることができます
(cx、cyを省略すると0が指定されたものと解釈されます)
なので、60を指定してやるとキャンバスの中心に来てくれるはずです

が、原点自体の位置をずらすこともできます
viewBoxを以下のようにしてみましょう

<svg witdh="120" height="120" viewBox="-60 -60 120 120">
    <circle r="50" />
</svg>

-60を指定しているのはキャンバスの位置をずらしているものだと思ってください
座標の原点の位置は変わらず、キャンバスの位置は変わっているので結果的に座標の原点がキャンバスの中心に来ます

うまくいきましたね

しかしながら、今回作りたいのは円ではなく輪っかです
なので、以下のようにしましょう

<svg witdh="120" height="120" viewBox="-60 -60 120 120">
    <circle r="50" fill="none" stroke="red" stroke-width="20" />
</svg>
  • fill:画像の色
  • stroke:輪郭の色
  • stroke-width:輪郭の太さ

を指定できます
これで、中身の色はなし、輪郭の色は赤色、輪郭の太さは20になります
キャンバスの大きさが120、円の半径が50、輪郭の太さが20なのでこれでキャンバスの中に輪っかがぴったり収まりましたね

C を作る

今度はこの輪っかを C の形にしていこうと思います
それには、svgの輪郭はstroke-dasharrayを指定することで点線にもできる、という仕様を利用します
以下のようにします

<svg witdh="120" height="120" viewBox="-60 -60 120 120">
    <circle r="50" fill="none" stroke="red" stroke-width="20" stroke-dasharray="20" />
</svg>

すると輪郭が長さ20間隔の点線になります
stroke-dasharray="20 40"としてあげれば隙間の方だけ間隔を長くすることもできます
輪郭の始点は時計で言うところの3時のところにあるので、右のところだけ始点と終点が繋がって見えたりしていますね

では、次にstroke-dasharray="157" としてみましょう(157は円周÷2です)
すると、どうなるか

半周分の輪郭が作れましたね

さらに、stroke-dashoffsetという値を指定してやります
これは輪郭の始点の位置をずらす属性です
次のようにしてみましょう

<svg witdh="120" height="120" viewBox="-60 -60 120 120">
    <circle r="50" fill="none" stroke="red" stroke-width="20" stroke-dasharray="157" stroke-dashoffset="-78" />
</svg>

輪郭の位置をずらすことができました
(右回りにずらすためにはstroke-dashoffset の値は負の値を指定する必要があります)

これを応用すればなんかいい感じの C が作れそうですね
ところで、svgの属性の一部はCSSのプロパティで記述することもできるので、以下のようにしてみましょう

<style>
    circle {
        fill: none;
        stroke: red;
        stroke-width: 20;
        stroke-dasharray: 236;
        stroke-dashoffset: -37;
    }
</style>
<svg witdh="120" height="120" viewBox="-60 -60 120 120">
    <circle r="50" />
</svg>

いい感じの C ができましたね!

アニメーションさせる

C はなんとかなったので、次はアニメーションをさせてみましょう
keyframesanimationを設定してあげればよいですね

  • keyframes:各キーフレームでのプロパティの指定
  • animation:再生時間や繰り返しの有無などの指定

考え方として以下の3状態をループする形を取りたいと思います

なんとなく月の満ち欠けっぽいのでkeyframesの名前はかっこよくwax-and-waneとでもしましょう
animation-timing-functionはease-in-outでもいいんですが、点対称じゃないのが気になるのでcubic-bezierで指定しています(Chrome Dev Tool などを使えばお好みのが作れます)

<style>
    circle {
        fill: none;
        stroke: red;
        stroke-width: 20;
        /* 使うkeyframesの名前、再生時間、補間の仕方、繰り返すかどうか、をそれぞれ指定している */
        animation: wax-and-wane 2s cubic-bezier(.5, 0, .5, 1) infinite;
    }

    /* 全体の再生時間のn%のときになってほしいCSSプロパティを指定する */
    @keyframes wax-and-wane {

        /* 状態1 */
        0% {
            stroke-dasharray: 236 78;
            stroke-dashoffset: -37;
        }

        /* 状態2 */
        50% {
            stroke-dasharray: 20 294;
            stroke-dashoffset: -304;
        }

        /* 状態3 */
        100% {
            stroke-dasharray: 236 78;
            stroke-dashoffset: -351;
        }
    }
</style>
<svg width="120" height="120" viewBox="-60 -60 120 120">
    <circle r="50" />
</svg>

ううむ、いい感じ

C を回す

あとはこれを回してあげるだけですね
keyframesをさらに追加しましょう
animationを複数設定したい時はカンマで区切ってあげます
ついでに、stroke-linecapにroundを指定してやると輪郭の端が丸くなってキュートです

<style>
    circle {
        fill: none;
        stroke: red;
        stroke-width: 20;
        stroke-linecap: round;
        /* 使うkeyframesの名前、再生時間、補間の仕方、繰り返すかどうか、をそれぞれ指定している */
        animation: wax-and-wane 2s cubic-bezier(.5, 0, .5, 1) infinite, rotate 1.5s linear infinite;
    }

    /* 全体の再生時間のn%のときになってほしいCSSプロパティを指定する */
    @keyframes wax-and-wane {

        /* 状態1 */
        0% {
            stroke-dasharray: 236 78;
            stroke-dashoffset: -37;
        }

        /* 状態2 */
        50% {
            stroke-dasharray: 20 294;
            stroke-dashoffset: -304;
        }

        /* 状態3 */
        100% {
            stroke-dasharray: 236 78;
            stroke-dashoffset: -351;
        }
    }

    @keyframes rotate {
        /* 0%の時はどっちみち0度なので省略してもよいです */
        0% {
            transform: rotate(0);
        }

        100% {
            transform: rotate(360deg);
        }
    }
</style>
<svg width="120" height="120" viewBox="-60 -60 120 120">
    <circle r="50" />
</svg>

というわけで、いい感じのスピナーが作れたんじゃないでしょうか

ReactやVue.jsを使っていればコンポーネントに切り分けて便利に使えると思います

まとめ

ReactやVue.jsを使っていても結局書くのはHTMLやCSSやJavaScriptだったりするので基礎は大事ですよね
普段ありものを使って済ますものでも、自分で作ってみると新たな気づきがあったりして楽しいので、たまにはいいんじゃないかなと思います

前へ

【React, Vue.js, Svelte】10分で説明する仮想DOMとリアクティビティ