魔法少女サイト

これは、魔法少女になれなかった男の記録である。

ニートに学ぶCSS Animation演出講座 3時間目

あいさつ

f:id:yuki540com:20180503144545p:plain

ハロー、yuki540だよ!ピロリン

↑ このテンションしんどいのでやめます

改めまして、yuki540です。

前回の記事読んでくれましたか?読んでない方は、ぜひ読んでみてください。

今回はキャラクターイラストを使ったアニメーション表現を紹介できたらなと思います。

で、今回作るのものはこちら。

Twitterで「3時間目はこれにする!」って言ってたものがコロコロ変わってしまってすみません...)

WebGLっぽい立体表現を使った演出ですね。

perspective, transform-styleともに対応状況が良くなってきたので積極的に使っていい頃合いかなと思います。

f:id:yuki540com:20180318103631p:plain

(ただ今回作るもののような演出は、処理が重めなのであくまで「こういう演出もあるんだな〜」程度にみてください)

作ろう

まず、最終的に出来上がるものはこちらです。

はい。じゃあ一緒に作っていきましょう。

まず、時計から

まず、最初に↓の時計(?)から作っていきましょう。

f:id:yuki540com:20180318104743p:plain

マークアップはこんな感じになります。

<main class="stage">
  <div class="stage__inner">
    <section class="clock">
      <div class="clock__frame">
        <div></div><div></div><div></div><div></div>
        <div></div><div></div><div></div><div></div>
        <div></div><div></div><div></div><div></div>
        <div></div><div></div><div></div><div></div>
        <div></div><div></div><div></div><div></div>
        <div></div><div></div><div></div><div></div>
      </div>
      <div class="clock__hands">
        <div></div><div></div>
      </div>
      <div class="clock__image"></div>
    </section>
  </div>
  <div class="stage__filter"></div>
</main>

先に.stageにスタイルを適用してみましょう。

といってもサイズを画面全体にするだけです。単純ですね。

あと、.stage__filterbox-shadowで内側に影をつけて、回想シーンのような雰囲気を表現しています。

* {
  margin: 0;
  padding: 0;
}
.stage {
  position: absolute;
  top: 0; left: 0;
  width: 100%; height: 100%;
  overflow: hidden;
}
.stage__inner,
.stage__filter {
  position: absolute;
  top: 0; left: 0;
  width: 100%; height: 100%;
}
.stage__filter { box-shadow: 0 0 300px #444 inset; }

次に時計の縁(?)の部分にスタイルを適用してみましょう。

500px x 500pxの時計を画面の中心に配置しています。

そして、縁の部分の.clock__frame divを24分割して、15deg間隔を空けながら回転させています。(24個それぞれにスタイルをあてるのはsassなりを使うべきですね...)

f:id:yuki540com:20180318112425p:plain

/**
 * clock
 */
.clock {
  position: absolute;
  top: calc(50% - 250px); left: calc(50% - 250px);
  width: 500px; height: 500px;
  background-color: rgba(100, 100, 100, 0.1);
  border-radius: 50%;
}

/*** frame ***/
.clock__frame {
  position: absolute;
  top: 20px; left: 20px;
  width: calc(100% - 40px); height: calc(100% - 40px);
}
.clock__frame:before,
.clock__frame:after {
  content: ""; display: block;
  position: absolute;
  top: 0; left: 0;
  width: 100%; height: 100%;
  border: solid 1px #444;
  border-radius: 50%;
  box-sizing: border-box;
}
.clock__frame:after {
  top: 10px; left: 10px;
  width: calc(100% - 20px); height: calc(100% - 20px);
}
.clock__frame div {
  position: absolute;
  top: 0; left: 0;
  width: 100%; height: 100%;
}
.clock__frame div:after {
  content: ""; display: block;
  position: absolute;
  top: 0; left: calc(50% - 2.5px);
  width: 5px; height: 10px;
  background-color: #444;
}
/* 24分割して回転 --------------------------------------- */
.clock__frame div:nth-child(1)  { transform: rotate(0deg); }
.clock__frame div:nth-child(2)  { transform: rotate(15deg); }
.clock__frame div:nth-child(3)  { transform: rotate(30deg); }
.clock__frame div:nth-child(4)  { transform: rotate(45deg); }
.clock__frame div:nth-child(5)  { transform: rotate(60deg); }
.clock__frame div:nth-child(6)  { transform: rotate(75deg); }
.clock__frame div:nth-child(7)  { transform: rotate(90deg); }
.clock__frame div:nth-child(8)  { transform: rotate(105deg); }
.clock__frame div:nth-child(9)  { transform: rotate(120deg); }
.clock__frame div:nth-child(10) { transform: rotate(135deg); }
.clock__frame div:nth-child(11) { transform: rotate(150deg); }
.clock__frame div:nth-child(12) { transform: rotate(165deg); }
.clock__frame div:nth-child(13) { transform: rotate(180deg); }
.clock__frame div:nth-child(14) { transform: rotate(195deg); }
.clock__frame div:nth-child(15) { transform: rotate(210deg); }
.clock__frame div:nth-child(16) { transform: rotate(225deg); }
.clock__frame div:nth-child(17) { transform: rotate(240deg); }
.clock__frame div:nth-child(18) { transform: rotate(255deg); }
.clock__frame div:nth-child(19) { transform: rotate(270deg); }
.clock__frame div:nth-child(20) { transform: rotate(285deg); }
.clock__frame div:nth-child(21) { transform: rotate(300deg); }
.clock__frame div:nth-child(22) { transform: rotate(315deg); }
.clock__frame div:nth-child(23) { transform: rotate(330deg); }
.clock__frame div:nth-child(24) { transform: rotate(345deg); }

次に長針と短針と時計の中央に配置する画像にスタイルをあてていきましょう。

.clock__hands div:nth-child(1)(長針)が高さ50%で、.clock__hands div:nth-child(2)(短針)が高さ40%にしています。

.clock__imageは、時計の中央に配置する画像ですね。100% - 90px(410px)という大きさにしています。

f:id:yuki540com:20180318113820p:plain

画像は、お好みのものを用意してください。

/*** hands ***/
.clock__hands {
  position: absolute;
  top: 35px; left: 35px;
  width: calc(100% - 70px); height: calc(100% - 70px);
}
.clock__hands div {
  position: absolute;
  top: 0; left: 0;
  width: 100%; height: 100%;
}
.clock__hands div:after {
  content: ""; display: block;
  position: absolute;
  top: 0; left: calc(50% - 5px);
  width: 10px;
  background-color: #444;
  border-radius: 5px;
}
.clock__hands div:nth-child(1):after { height: 50%; }
.clock__hands div:nth-child(2):after { height: 40%; top: 10%; }

/*** image ***/
.clock__image {
  position: absolute;
  top: 45px; left: 45px;
  width: calc(100% - 90px); height: calc(100% - 90px);
  background-image: url(/* お好きな画像を指定 */);
  background-size: auto 100%;
  background-position: center;
  background-repeat: no-repeat;
}

このままだと、止まったままなので時計っぽくありませんね。(特に画像を重ねているので)

なので、時計の針を動かしていきましょう。

.clock__hands:nth-child(2)(短針)が360deg.clock__hands:nth-child(1)(長針)が360 * 12 = 4320deg回転するようにします。(これよりいい方法がありますが脳死でいきます)

時計なので、イージングにはlinear(一定のスピードでアニメーション)を使っています。

/*********************************************************************************
  animation
*********************************************************************************/
.clock__hands div:nth-child(1) { animation: rotate4320 30s linear 0s infinite; }
.clock__hands div:nth-child(2) { animation: rotate360 30s linear 0s infinite; }

/*********************************************************************************
  keyframes
*********************************************************************************/
@keyframes rotate360 {
  from { transform: rotate(0deg); }   
  to   { transform: rotate(360deg); }   
}
@keyframes rotate4320 {
  from { transform: rotate(0deg); }
  to   { transform: rotate(4320deg); }
}

どうでしょうか?

↓のような時計ができましたか?できましたね。はい。次。

次に行く前に一旦、.clockdisplay:noneしておいてください。

回想シーンパネルのようなもの

f:id:yuki540com:20180318121027p:plain

次は↑の円状の回想シーンのパネルのようなものを作っていきましょう。

まず、↓のマークアップを追加してください。

<main class="stage">
  <div class="stage__inner">
    <section class="clock"><!-- 省略 --></section>

    <!-- 追加: ここから -->
    <section class="memories memories_layer_1">
      <div class="memories__image memories__image_type_1"></div>
      <div class="memories__frame"></div>
    </section>
    <section class="memories memories_layer_2">
     <div class="memories__image memories__image_type_2"></div>
     <div class="memories__frame"></div>
    </section>
    <section class="memories memories_layer_3">
      <div class="memories__image memories__image_type_3"></div>
      <div class="memories__frame"></div>
    </section>
    <section class="memories memories_layer_4">
      <div class="memories__image memories__image_type_4"></div>
      <div class="memories__frame"></div>
    </section>
    <section class="memories memories_layer_5">
      <div class="memories__image memories__image_type_5"></div>
      <div class="memories__frame"></div>
    </section>
    <!-- 追加: ここまで -->
      
  </div>
  <div class="stage__filter"></div>
</main>

先に縁の部分を作っていきましょう。

.memoriesの大きさは.clockと同様に500px x 500pxです。

ここでは一旦、.memories__image_type_2 ~ .memories__image_type_5display:noneで非表示にします(重なってしまって、これからデザインが確認しづらいため)

縁部分の内側(.memories__frame:before)が通常の線で、外側(.memories__frame:after)が波のような滑らかな線にしていきます。

f:id:yuki540com:20180318122301p:plain

滑らかな線は、border-topborder-bottomのような2点だけを指定するできます。

/**
 * memories
 */
.memories {
  position: absolute;
  top: calc(50% - 250px); left: calc(50% - 250px);
  width: 500px; height: 500px;
}

/*** 一旦、.memories__image_type_1だけを表示 ***/
.memories__image_type_2,
.memories__image_type_3,
.memories__image_type_4,
.memories__image_type_5 { display: none; }

/*** frame ***/
.memories__frame {
  position: absolute;
  top: 0; left: 0;
  width: 100%; height: 100%;
}
.memories__frame:before,
.memories__frame:after {
  content: ""; display: block;
  position: absolute;
  border-radius: 50%;
  box-sizing: border-box;
}
.memories__frame:before {
  top: 25px; left: 25px;
  width: calc(100% - 50px); height: calc(100% - 50px);
  border: solid 1px #444;
}
.memories__frame:after {
  top: 0; left: 0;
  width: 100%; height: 100%;
  border-top: solid 10px #444;
  border-bottom: solid 10px #444;
}

次にパネルの画像にスタイルをあてていきます。

ちょっと透明にしているのは、あとでレイヤー間を通り過ぎる感覚をより際立たせるためです。

あと、内側にbox-shadowで影をつけて回想シーン感をまた出します。

f:id:yuki540com:20180318123526p:plain

画像はお好みのものを。

/*** image ***/
.memories__image {
  position: absolute;
  top: 50px; left: 50px;
  width: calc(100% - 100px); height: calc(100% - 100px);
  border-radius: 50%;
  background-size: cover;
  background-position: center;
  opacity: 0.8;
}
.memories__image:after {
  content: ""; display: block;
  position: absolute;
  width: 100%; height: 100%;
  border-radius: 50%;
  box-shadow: 0 0 40px #333 inset;
}
.memories__image_type_1 { background-image: url(/* お好きな画像を指定 */); }
.memories__image_type_2 { background-image: url(/* お好きな画像を指定 */); }
.memories__image_type_3 { background-image: url(/* お好きな画像を指定 */); }
.memories__image_type_4 { background-image: url(/* お好きな画像を指定 */); }
.memories__image_type_5 { background-image: url(/* お好きな画像を指定 */); }

このままだとつまらないので、滑らかな線(memories__frame:after)を回転させましょう。

キーフレームはrotate360を使い回しましょう。

.memories__frame:after { animation: rotate360 3.5s linear 0s infinite; }

ここまできたら、.clock.memories__image_type_2 ~ .memories__image_type_5display:noneを消してください。

立体空間をすり抜ける

最後にお待ちかねの立体空間をすり抜けるようなアニメーションですが、それを実現するためにperspectivetransform-styleを使います。

perspectiveは、"ユーザの視点" から "コンテンツ(要素)"の距離を指定することができます。

なので、対象の要素が傾いていた場合、perspectiveの値が小さければ小さいほど傾きが大きく、perspectiveの値が大きければ大きいほど傾きが小さくなります。

詳しくはこちら

f:id:yuki540com:20180318130014p:plain

transform-styleは、親要素の傾きに対して、子要素は3D空間の位置を保持するかを指定できます。

デフォルトではflatですが、preserve-3dにするとパララックス効果(視差効果)を出すことができます。

で、まず視点の基準として.stageperspectiveを指定し、各3D空間に配置する要素の親要素である.stage__innertransform-styleを指定します。

.stage { perspective: 20px; }
.stage__inner { transform-style: preserve-3d; }

次に.clock.memories_layer_1 ~ .memories_layer_5を3D空間の位置に配置しましょう。

translateZ100px間隔を空けながら、3D空間に配置していきます。

f:id:yuki540com:20180318132836p:plain

translateZを指定すると、perspectiveの値により、本来より大きくなったり小さくなったりするので、ある地点で本来の大きさになるようにscaleで調整します。

.clock { transform: translateZ(50px) scale(calc(1 - (50 / 100))); }
.memories_layer_1 { transform: translateZ(150px) scale(calc(1 - (150 / 200))); }
.memories_layer_2 { transform: translateZ(250px) scale(calc(1 - (250 / 300))); }
.memories_layer_3 { transform: translateZ(350px) scale(calc(1 - (350 / 400))); }
.memories_layer_4 { transform: translateZ(450px) scale(calc(1 - (450 / 500))); }
.memories_layer_5 { transform: translateZ(550px) scale(calc(1 - (550 / 600))); }

あとは"ユーザの視点"を行ったり来たりするためにcameraキーフレームを使います。

行き来するのは、cameraキーフレームを0% ~ 100%100% ~ 0%にするだけなので、キーフレームをカンマで区切り、reverseオプションで100% ~ 0%にすることができます。

あとswayキーフレームで斜めに揺らして、より立体感と浮遊感を表現しています。

f:id:yuki540com:20180318133700p:plain

/*********************************************************************************
  animation
*********************************************************************************/
.stage {
  animation:
    camera 8s ease-in-out 0s forwards,
    camera 4s ease 8s reverse forwards;
}
.stage__inner { animation: sway 0.7s ease-in-out 0s alternate infinite; }

/*********************************************************************************
  keyframes
*********************************************************************************/
@keyframes camera {
  from { perspective: 20px; }
  to   { perspective: 600px; }
}
@keyframes sway {
  from { transform: translate(3px, 3px); }
  to   { transform: translate(-3px, -3px); }
}

これで完成したはずです。

記述ミスや記述漏れがあるかもしれませんので、完成したコードはこちらにありますのでご確認ください。

さいごに

今回はperspectiveを使った演出を解説しましたが、いかがでしたでしょうか?

立体的に見せるだけで表現の幅がかなり広がりましたね。

また、今回の回想シーンのような表現にも注目して欲しいなと思います。対象の作品(今回はリゼロ)の雰囲気をいかに出せるかがキャラクターイラストを使った演出のキモだと僕は思います。

ですが、実際のページで使うには、まだパフォーマンスが良くないのでWebGLでの演出なんかの参考になれば幸いです。

次回も何か面白いアニメーションを用意するので、よければ見てください。

じゃーねー

f:id:yuki540com:20180318134559p:plain