SVGで円弧を描くアニメーションを実装する

たまに見かける、アイコンや画像の周りを円弧がぐるっと1回転するアニメーション。

これをどうにか実装できないかと考えて、なんとか形にすることができたので書き留めます。

条件としては下記。

  • SVGで円弧を描く
    • アニメーションはcss3のkeyframeアニメーションを使用
  • レスポンシブ対応
  • JavaScriptでアニメーション開始タイミング(スクロール量)を制御
    • クラス付与するとアニメーションが開始

実は今までSVGを触ったことがなかったため、今回初めてで手探りとなりました。

コード

できたコードが下記。なお、サンプルのためBootstrap4とFontAwesome利用です。

HTML

<div class="fairycircle fairycircle_address mb-5">
    <i class="fas fa-fw fa-address-card fa-5x fairycircle_icon" aria-hidden="true"></i>
    <div class="fairycircle_svg">
        <svg class="fairycircle_svgImg" width="400" height="400" viewBox="0 0 400 400">
            <circle class="fairycircle_svgCircle" cx="200" cy="200" r="195" />
        </svg>
    </div>
</div>

今回は直径400px、線の太さ10px(cssで指定)の円弧をベースにします。

上記コードでは、svgタグ内のwidth, height400を指定。viewBox(描画領域のサイズ)は幅・高さ共に0400なので0 0 400 400

円弧を描くためsvgの子要素にcircleタグ。cx, cy属性は直系の半分で200

実際の半径を指定するrですが、線の太さを加味します。今回は10pxで、中心が半径に当たるので線の太さの半分の値を直系の半分から差し引いて195です。

Scss

$main-color: #39e16b;
$main-color2: darken($main-color, 15%);
$main-color3: lighten($main-color, 15%);

.fairycircle {
    max-width: 400px; //直径
    margin: auto;
    position: relative;
    &_icon {
        position: absolute; //absoluteで重ねる
        left: 50%; //中央寄せ
        top: 50%;
        transform: translate(-50%, -50%);
        display: block;
        &.fa { //アイコンの色
            &-life-ring {
                color: $main-color;
            }
            &-box-open {
                color: $main-color2;
            }
            &-address-card {
                color: $main-color3;
            }
        }
    }
    .fairycircle_svg { //svgの親要素にwidth: 100%を指定
        position: relative;
        width: 100%;
        padding-top: 100%;
        &Img { //svgにabsolute指定
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            transform: rotate(-90deg); //3時の方向からアニメーションが開始されるので、反時計回りに90度回転させて真上からアニメーションさせる
        }
        &Circle { //svgの円弧
            fill: transparent;
            stroke-width: 10; //線の太さ
            stroke: transparent; //クラス付与されていないときは透明にしておく
            &.circleEffect {
                animation: circle 3s 1 forwards; //アニメーション。クラス付与したタイミングでアニメーションを開始する
            }
        }
        @keyframes circle {
            0% { stroke-dasharray: 0 1256; } /* ceil(400 * 3.14) */
            99.9%,to { stroke-dasharray: 1256 1256; }
        }
    }
    &.fairycircle_support { //線の色を透明から変化
        .circleEffect {
            stroke: $main-color;
        }
    }
    &.fairycircle_box {
        .circleEffect {
            stroke: $main-color2;
        }
    }
    &.fairycircle_address {
        .circleEffect {
            stroke: $main-color3;
        }
    }
}

上記の通り、keyframeアニメーションで円弧を描きます。肝は

  • レスポンシブ対応
  • アイコンとSVGをposition: absolute;で重ねる
  • デフォルトの線の色は透明にしておく
  • JavaScriptでクラス付与したタイミングで以下の2つを実施
    • 線の色を適宜変更
    • keyframeアニメーション実行開始

JavaScript(jQuery)

$(function() {
    svgAnimeTrigger()
}
//svgアニメーショントリガー
const svgAnimeTrigger = function() {
    const $svgWrapper = $(".fairycircle")
    const vh = $(window).height()

    $(window).on("scroll", function() {
        let currentPos = $(window).scrollTop()
        $svgWrapper.each(function() {
            let position = $(this).offset().top
            let posiVal = Math.floor(position)
            if (currentPos > posiVal - vh) { //スクロール量が一定量に達したら
                $(this).find(".fairycircle_svgCircle").addClass("circleEffect") //クラス付与
            }
        })
    })
}

JavaScript(jQuery)でやることは「スクロール量を計測し、各SVGに対する閾値を越えたらクラス付与する」ことだけです。

これで、以下のように要素が見えたところまでSVGがスクロールすると円弧を描くアニメーションが実行される、という形です。

SVGが円弧を描くアニメーションを実施している様子
SVGが円弧を描くアニメーションを実施している様子

思ったよりもサクッとできて良かった。

参考

この記事を書いた人

アバター

アルム=バンド

フルスタックエンジニアっぽい何か。LAMPやNodeからWP、gulpを使ってejs,Scss,JSのコーディングまで一通り。たまにRasPiで遊んだり、趣味で開発したり。