最近フロントエンドを触ることが多くなってきたので、 CSS でアニメーションを描画する方法について調べてみました。

CSS でアニメーションを描画

一度だけ実行されるアニメーション

まず、クリックするごとに形が変わる要素を作ってみました。

クリックイベントに関しては JavaScript を使ってるものの、アニメーション自体は CSS のみ。以下、ソース。

まず HTML はこれだけ。

<div id="css-test-object" class="default"></div>

続いて CSS。

div#css-test-object {
    width: 100px;
    height: 100px;
    margin: 20px 0px 20px calc(50% - 50px);
    box-shadow: 0 24px 38px 3px rgba(0,0,0,0.14), 0 9px 46px 8px rgba(0,0,0,0.12), 0 11px 15px -7px rgba(0,0,0,0.2);
}

div#css-test-object.default {
    animation-duration: 500ms;
    animation-fill-mode: forwards;
    animation-name: toDefault;
}

div#css-test-object.circle {
    animation-duration: 500ms;
    animation-fill-mode: forwards;
    animation-name: toCircle;
}

div#css-test-object.square {
    animation-duration: 500ms;
    animation-fill-mode: both;
    animation-name: toSquare;
}

@keyframes toDefault {
    from {
        background-color: #01579b;
        border-radius: 25%;
    }

    to {
        background-color: #ffffff;
        border-radius: 0%;
    }
}

@keyframes toCircle {
    from {
        background-color: #ffffff;
        border-radius: 0%;
    }

    to {
        background-color: #f57f17;
        border-radius: 50%;
    }
}

@keyframes toSquare {
    from {
        background-color: #f57f17;
        border-radius: 50%;
    }

    to {
        background-color: #01579b;
        border-radius: 25%;
    }
}

#css-test-object に対して 3 種類の class を準備して、それぞれのクラスがロードされた時に実行されるアニメーションを @keyframes で定義してます。例えば .circle の場合、

div#css-test-object.circle {
    animation-duration: 500ms;
    animation-fill-mode: forwards;
    animation-name: toCircle;
}

@keyframes toCircle {
    from {
        background-color: #ffffff;
        border-radius: 0%;
    }

    to {
        background-color: #f57f17;
        border-radius: 50%;
    }
}

まず、 div#css-test-object.circle に対するスタイルの指定について。

  • animation-duration
    アニメーションの開始から終了までにかかる時間を指定しています。今回の場合、 500 ms つまり 0.5 秒かけて形を変化させています。

  • animation-fill-mode
    アニメーションの実行前後に、指定したスタイルを適用するかを指定しています。 none, forwards, backwards, both から指定します。デフォルトは none で、アニメーション実行時以外はそのスタイルが適用されません。forwards だと、最後のフレーム (つまり @keyframesto) で指定したスタイルが保持されます。 その他については下記参照。
    animation-fill-mode | MDN

  • animation-name
    実行するアニメーションのキーフレームを示す @keyframes 名前を指定しています。

続いて、アニメーションの中身 toCircle について。

  • from
    開始時のスタイルを指定しています。

  • to
    終了時のスタイルを指定しています。

今回の場合 toCircle アニメーションでは、背景色が #ffffff から #f57f17 に、要素の角丸が 0% から 50% に変化します。

ちなみにクリックイベントについては下記のような JavaScirpt で実装。

const mainObj = document.getElementById('css-test-object');
mainObj.addEventListener('click', (event) => {
    changeObject(event);
});

function changeObject(event) {
    if (event.target.classList.contains('circle')) {
        event.target.classList.remove('circle');
        event.target.classList.add('square');
    } else if (event.target.classList.contains('square')) {
        event.target.classList.remove('square');
        event.target.classList.add('default');
    } else {
        event.target.classList.remove('default');
        event.target.classList.add('circle');
    }
}

繰り返し実行されるアニメーション

ドットを動かしてみる。

以下の HTML に対して、

<div class="dot-area">
    <div class="dot round"></div>
</div>

以下の CSS を適用してます。

    div.dot-area {
        position: relative;
        width: 100px;
        height: 100px;
        margin: 20px 0px 20px calc(50% - 50px);
    }
    div.dot {
        position: absolute;
        width: 10px;
        height: 10px;
        background-color: #000000;
        border-radius: 50%;
        left: 50px;
        top: 50px;
        box-shadow: 0 24px 38px 3px rgba(0,0,0,0.14), 0 9px 46px 8px rgba(0,0,0,0.12), 0 11px 15px -7px rgba(0,0,class0.2);
    }
    div.dot.round {
        animation-duration: 3000ms;
        animation-fill-mode: forwards;
        animation-iteration-count: infinite;
        animation-name: round;
    }

    div.dot.round-smooth {
        animation-duration: 3000ms;
        animation-fill-mode: forwards;
        animation-iteration-count: infinite;
        animation-name: roundSmooth;
    }

    @keyframes round {
        0% {
            left: 100.0px;
            top: 50.0px;
        }
        10.0% {
            left: 65.451px;
            top: 2.447px;
        }
        20.0% {
            left: 9.549px;
            top: 20.611px;
        }
        30.0% {
            left: 9.549px;
            top: 79.389px;
        }
        40.0% {
            left: 65.451px;
            top: 97.553px;
        }
        50.0% {
            left: 100.0px;
            top: 50.0px;
        }
        60.0% {
            left: 65.451px;
            top: 2.447px;
        }
        70.0% {
            left: 9.549px;
            top: 20.611px;
        }
        80.0% {
            left: 9.549px;
            top: 79.389px;
        }
        90.0% {
            left: 65.451px;
            top: 97.553px;
        }
        100.0% {
            left: 100.0px;
            top: 50.0px;
        }
    }

アニメーションを繰り返し実行するためには animation-iteration-countinfinite を指定します。ここを具体的な数値にすれば、その回数だけ繰り返されます。

keyframe について先程と違うのは、 fromto の代わりに、割合 (%) でスタイルを指定している点です。
アニメーションの開始時を 0% 、終了時を 100% として、途中のスタイルを適宜指定します。

本当は単位円を描画したかったんですが、上記の記述では動きがカクカクしています。なので、より細かくスタイルを指定してみます。

@keyframes roundSmooth {
        0% {
            left: 100.0px;
            top: 50.0px;
        }
        1.0% {
            left: 99.606px;
            top: 43.733px;
        }
        2.0% {
            left: 98.429px;
            top: 37.566px;
        }
        3.0% {
            left: 96.489px;
            top: 31.594px;
        }
        4.0% {
            left: 93.815px;
            top: 25.912px;
        }

        /* 略 */

        98.0% {
            left: 98.429px;
            top: 62.434px;
        }
        99.0% {
            left: 99.606px;
            top: 56.267px;
        }
        100.0% {
            left: 100.0px;
            top: 50.0px;
        }
    }

こうすると、なめらかになります。

ちなみに、上のスタイルは下記のスクリプトで作ってます。三角関数知っててよかったー(棒)

import math

ACCURACY = 100
BASE_LEFT = 50
BASE_TOP = 50
RADIUS = 50
PER = 360 / ACCURACY

percent = 0
rad = 0
style = '@keyframes roundSmooth {\n'
while (percent <= 100):
    left = round(BASE_LEFT + math.cos(math.radians(rad)) * RADIUS, 3)
    top = round(BASE_TOP - math.sin(math.radians(rad)) * RADIUS, 3)
    style += '''    {}% {{
        left: {}px;
        top: {}px;
    }}\n'''.format(percent, left, top,)

    rad += PER
    percent = round((rad / 360) * RADIUS, 0)

style += '}'
keyframe_css = './keyframe.css'
with open(keyframe_css, mode='w') as f:
    f.write(style)

ACCURACY で精度、 RADIUS で単位円の半径 (px)、 BASE_LEFTBASE_TOP で基準となる位置を指定します。

以上、CSS でアニメーションを描画してみた話でした。
とりあえず基本的な部分はわかったので、より複雑なアニメーションにもチャレンジできそうです。

comments powered by Disqus