🦒

CSSアニメーションとtransform-origin

2021/04/20に公開

はじめに

CSSアニメーション以外でのtransform-originの使い方ありますか?
transform-originと戯れたので今回はこのローディングアニメーションの解説を行います。

完成品

ソースコード
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta name="robots" content="noindex">
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ee</title>
    <style>
        *,
        *::before,
        *::after {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            background: #333;
            display: grid;
            height: 100vh;
            margin: 0;
            place-items: center center;
        }

        .loading {
            width: 350px;
            height: 50px;
            display: flex;
            animation: hue-rotate linear 4s 0s infinite;
        }
        @keyframes hue-rotate {
            100% {filter: hue-rotate(360deg);}
        }

        .loading div {
            width: 250px;
            height: 50px;
            position: relative;
        }

        .loading div::before,
        .loading div::after {
            content: "";
            display: block;
            width: 50px;
            height: 50px;
            background: rgba(0, 231, 255, 1);
            border-radius: 50%;
            position: absolute;
            box-shadow:
                0 0 5px rgba(0, 231, 255, 1),
                0 0 10px rgba(0, 231, 255, 1),
                0 0 20px rgba(0, 231, 255, 1),
                0 0 40px rgba(0, 231, 255, 1),
                0 0 80px rgba(0, 231, 255, .5),
                0 0 160px rgba(0, 231, 255, .2);
        }

        .loading div::after {right: 0;}

        .loading div:nth-child(1) {animation: rotate1 ease-in-out 2s 0s infinite;}
        @keyframes rotate1 {
            0%,50% {transform-origin: 50% 50%;}
            25% {transform: rotate(180deg);}
            26%,50% {transform: rotate(180deg);}
            50.1%,100% {transform-origin: 175px 50%;}
            100% {transform: rotate(540deg);}
        }

        .loading div:nth-child(2) {
            margin-left: -150px;
            animation: rotate2 ease-in-out 2s 0s infinite;
        }
        @keyframes rotate2 {
            0%,50% {transform-origin: 50% 50%;}
            0%,25% {transform: rotate(0deg);}
            50% {transform: rotate(180deg);}
            50.1%,100% {transform-origin: 75px 50%;}
            100% {transform: rotate(540deg);}
        }
    </style>
</head>
<body>
    <div class="loading">
        <div></div>
        <div></div>
    </div>
</body>
</html>

解説

HTMLは以下の通りです。

<div class="loading">
  <div></div>
  <div></div>
</div>

まずはいつもどおり中央に寄せましょう。

body {
  background: #333;
  display: grid;
  height: 100vh;
  margin: 0;
  place-items: center center;
}

今回はflexではなく、gridで中央寄せを行います。特に意味はなく気分です。

  1. divの両端に疑似要素を作成
  2. ネガティブマージンを付けてdiv同士を重ねる
  3. transform-originを設定
  4. animationで回転

の3ステップで実装していきます。
まずは.loadingのサイズを考えていきましょう。
今回球のサイズは50px、同じく隙間も50pxとします。
球が4つと、隙間が3つなのでwidth350pxとなります。

.loading {
    width: 350px;
    height: 50px;
    display: flex;
}

同様の考え方でdivwidth250pxです。

.loading div {
    width: 250px;
    height: 50px;
    position: relative;
}

次に、疑似要素を作成します。

.loading div::before,
.loading div::after {
    content: "";
    display: block;
    width: 50px;
    height: 50px;
    background: rgba(0, 231, 255, 1);
    border-radius: 50%;
    position: absolute;
}

.loading div::after {right: 0;}

.loading div:nth-child(2) {margin-left: -150px;}

ここまで書くと、おそらくこのようになっていると思います。


ここに'box-shadow'を付けておしゃれにしてあげましょう。

.loading div::before,
.loading div::after {
    content: "";
    display: block;
    width: 50px;
    height: 50px;
    background: rgba(0, 231, 255, 1);
    border-radius: 50%;
    position: absolute;
+   box-shadow:
+               0 0 5px rgba(0, 231, 255, 1),
+               0 0 10px rgba(0, 231, 255, 1),
+               0 0 20px rgba(0, 231, 255, 1),
+               0 0 40px rgba(0, 231, 255, 1),
+               0 0 80px rgba(0, 231, 255, .5),
+               0 0 160px rgba(0, 231, 255, .2);
}


これでCSS部分は完成です。

アニメーション作成

アニメーションの作成に入る前に本題のtransform-originの話に移りましょう。
構文は以下の通りです。

/* 値1つの構文 /
transform-origin: 2px;
transform-origin: bottom;
 
/
x-offset | y-offset /
transform-origin: 3cm 2px;
/
x-offset-keyword | y-offset /
transform-origin: left 2px;
/
x-offset-keyword | y-offset-keyword /
transform-origin: right top;
/
y-offset-keyword | x-offset-keyword /
transform-origin: top right;
 
/
x-offset | y-offset | z-offset /
transform-origin: 2px 30% 10px;
/
x-offset-keyword | y-offset | z-offset /
transform-origin: left 5px -3px;
/
x-offset-keyword | y-offset-keyword | z-offset /
transform-origin: right bottom 2cm;
/
y-offset-keyword | x-offset-keyword | z-offset /
transform-origin: bottom right 2cm;
 
/
グローバル値 */
transform-origin: inherit;
transform-origin: initial;
transform-origin: unset;

主に使用するのは値2つの構文で、要素の左上を起点にx-offsety-offsetを指定します。
キーワード指定をすることもできるので、直感的に扱うことができます。

キーワード
left 0%
center 50%
right 100%
top 0%
bottom 100%

それではアニメーションの作成に移ります。

.loading div:nth-child(1) {animation: rotate1 ease-in-out 2s 0s infinite;}
@keyframes rotate1 {
    0%,50% {transform-origin: 50% 50%;}
    25% {transform: rotate(180deg);}
    26%,50% {transform: rotate(180deg);}
    50.1%,100% {transform-origin: 175px 50%;}
    100% {transform: rotate(540deg);}
}

ここでのポイントは1点です。50.1%,100% {transform-origin: 175px 50%;}
この部分で50%ではなく、50.1%にしている理由は、2つめのdivを消して実行するとわかり易いです。

これを50%,100% {transform-origin: 175px 50%;}に変更すると、

このようになり、transform-origin: 50% 50%;からtransform-origin: 175px 50%;に変化する際の移動が見えてしまいます。それでは見栄えが悪いため、.1%の間に移動させます。しかし、それでは移動していることがバレバレなのでもう一つのdivにも同じようなアニメーションを付けて、隠してあげましょう。

.loading div:nth-child(2) {
    margin-left: -150px;
+   animation: rotate2 ease-in-out 2s 0s infinite;
}
@keyframes rotate2 {
    0%,50% {transform-origin: 50% 50%;}
    0%,25% {transform: rotate(0deg);}
    50% {transform: rotate(180deg);}
    50.1%,100% {transform-origin: 75px 50%;}
    100% {transform: rotate(540deg);}
}


これで完成でもいいんですが、おしゃれに色を変えてみましょう。

.loading {
    width: 350px;
    height: 50px;
    display: flex;
+   animation: hue-rotate linear 4s 0s infinite;
}
@keyframes hue-rotate {
    100% {filter: hue-rotate(360deg);}
}

これで完成です。

応用

応用するとこんなものもできるので、是非挑戦してみてください。

おわり

分かりにくい点等あればコメント頂きたいです。

Discussion