📝

SVG + CSSでローディングを作りました

2022/09/05に公開

アプリケーションのローディング画面が、fontawesomeに依存していたので
SVGとCSSでオリジナルのローディングアイコンを作りました。

成果物

1) アイコンをデザイナーさんに依頼。

元々使っていたのがfa-spinarだったので、代替するにもシンプルなアイコンかな?と思い

こういうのや

こういう、画像を利用しなくても、簡単なHTML・CSSで再現できるかな?と期待想像していたのですが、
拝見したのはこちら。

アプリケーションのテイストにマッチしたものをがっつりデザイン頂きました。

このデザインを画像なしのコードで頑張ろうとすると
HTMLタグを用意して、CSSでラインを丸め込んで、JSで円周に配置して・・・・とか、
パスをcanvas2Dで描いて・・・・とか
色んなやり方があると思いますが、今回はインラインSVGを利用することにしました。

2) アニメーションのイメージを確認

デザインは静的な状態で受け取ったので、次に動きを確認します。

ロード中は、時計回りにグラデーションのパスの色が変わっていくイメージとのことでしたので、
それを念頭にCSSアニメーションを考えます。
CSSで扱いやすくするために、SVG DOMを見ていきましょう。

3) SVGを綺麗にする

画像受け取り時の状態はこちら

<svg viewBox="0 0 30 30" xmlns="http://www.w3.org/2000/svg">
  <rect fill="#95aec8" height="9" rx="1.5" transform="matrix(-.70710678 .70710678 -.70710678 -.70710678 54.138 22.425)" width="3" x="20.925" y="17.925"/>
  <rect fill="#406b9d" height="9" rx="1.5" transform="matrix(-.70710678 .70710678 -.70710678 -.70710678 18.289 7.575)" width="3" x="6.075" y="3.075"/>
  <rect fill="#bfcfdd" height="9" rx="1.5" transform="matrix(.70710678 .70710678 -.70710678 .70710678 11.925 -13.638)" width="3" x="20.925" y="3.075"/>
  <rect fill="#6a8cb2" height="9" rx="1.5" transform="matrix(.70710678 .70710678 -.70710678 .70710678 18.075 1.211)" width="3" x="6.075" y="17.925"/>
  <rect fill="#aabfd3" height="9" rx="1.5" transform="matrix(0 1 -1 0 40.5 -10.5)" width="3" x="24" y="10.5"/>
  <rect fill="#557ca8" height="9" rx="1.5" transform="matrix(0 1 -1 0 19.5 10.5)" width="3" x="3" y="10.5"/>
  <rect fill="#d4e0e8" height="9" rx="1.5" width="3" x="13.5"/>
  <rect fill="#7f9dbd" height="9" rx="1.5" width="3" x="13.5" y="21"/>
</svg>

8個の<rect>にそれぞれサイズやfill、transform matrixが付いていました。

https://twitter.com/p__h__sa/status/1560181531016335360?s=20&t=6Cgs7h8gd2eMl6ol-5PHHg

時計回りにアニメーションさせていくのですが、よく見るとrectの並びが順不同になっています。これでは扱いづらいので、まずはアニメーションをかける順にDOMを綺麗に並べて、<rect>にclassを振っていきましょう。

ついでに、fill、width、hightはCSSで指定することにして、
インラインで不要な<svg>タグのxmlns属性も消してしまいます。

さらにさらに、パスの配置のtransformもピンと来ないなぁと思い、matrixについて調べました。

matrix()は6つの値を持つ二次元変形行列
matrix( scaleX(), skewY(), skewX(), scaleY(), translateX(), translateY() )

参考

CSS transformのmatrix()を一瞬で 完 全 理 解 できるやつ作ったよ。
matrixを完全理解できるアプリ
線形代数の知識ゼロから始めて行列式「だけ」理解する – アジマティクス

大変わかりやすかったですありがとうございます🎉🎉

3DCGやWebGLで遊ぶ人なので、なんとなーく線型代数も分かるけど
果たしてこのSVGで使う必要があるのか?と。
パスを円形になるように傾けるだけだから、transform rotateで事足りるのではないか?
ということでSVG座標と<rect>のtransformも整理しました。

最終的にはこうなりました。

<div class="loading">
   <svg viewBox="0 0 30 30">
     <rect rx="1.5" x="13.5" class="loading-path _no1"/>
     <rect rx="1.5" transform="rotate(45 22.425 7.576)" x="20.925" y="3.075" class="loading-path _no2"/>
     <rect rx="1.5" transform="rotate(90 25.5 15)" x="24" y="10.5" class="loading-path _no3"/>
     <rect rx="1.5" transform="rotate(-45)" x="-1.5" y="28" class="loading-path _no4"/>
     <rect rx="1.5" x="13.5" y="21" class="loading-path _no5"/>
     <rect rx="1.5" transform="rotate(45)" x="19.5" y="6" class="loading-path _no6"/>
     <rect rx="1.5" transform="rotate(90 4.5 15)" x="3" y="10.5" class="loading-path _no7"/>
     <rect rx="1.5" transform="rotate(135 7.576 7.575)" x="6.075" y="3.075" class="loading-path _no8"/>
   </svg>
</div>

rotateにすることで、<rect>が座標に対して45度か90度かで配置されてるんだな・・・とわかります。
まぁmatrixよりは把握しやすくなったと思うのでこれでいきましょう。

4) CSSを作る

transform rotateをCSS化しても良かったんだけど、CSSの楽さを考えて、<rect>のtransformはインラインに残しておくことにしました。
CSSはwidth, height, fillとアニメーションしか書きません。

.loading {
  width: 65px;
  height: 65px;
  margin: auto;

  &-path {
    animation: fadeColor infinite .8s reverse;
    width: 3px;
    height: 9px;
    fill: #d4e0e8;

    @for $i from 1 to 9 {
      &._no#{$i} {
        animation-delay: $i * .1s;
      }
    }
  }
}

@keyframes fadeColor {
  to {
    fill: #406B9D;
  }
}

rx属性も全て1.5で共通なので、CSSに持っていってもいいけれど
safariだけrxプロパティが効かなかったので、インラインに残しておきました。

参考

rx = "<length>"
矩形の角を丸めるために用いられる楕円の X 軸半径の長さ。
基本図形

※ちなみにwidth, heightはSVG内で指定した方が良い場合もあります。
https://qiita.com/manabuyasuda/items/01a76204f97cd73ffc4e#バグフィックスieとandroid

5) インラインSVGのアクセシビリティを考えてみる

SVGが読まれなかった時の代替画像を入れるかーと思いましたが、IE廃止の今は考えなくて構わない & ローディング画面は長くとも3秒程度しか出ないので省略。

roleや代替テキストのWAI-ARIAはあった方がいいので、
svgに role="img" aria-label="loading" を付けました。

title属性を付けるというのもありますが、aria-labelで大丈夫という記事があったので
そちらを参考にしました。
https://zenn.dev/catnose99/scraps/8dd52a640e440ce1e265

脱fontawesomeおめでとう🎉🎉

Discussion