はじめに
ホットペッパー グルメで開発を担当しているSE(Standing Engineer)の小山瑞樹 *(こやまみずき)*です。
担当はWebアプリケーションのServer Sideの実装(Java)ですが、最近はWeb Front EndでA/Bテストの実装(JavaScript+CSS)をメインに開発しています。
さて、私の所属するチームでは普段はこの記事みたいに少しずつ美味しいものを堪能するコース料理のようなA/Bテストをたくさん回しているのですが、なんとなくホールケーキをドカ食いしてもいいんじゃないかという気がする瞬間がやってきて画面一面をジャックしてしまうA/Bテストを実施することもあります。
このときのアニメーションの実装がなかなか楽しかったので、作り方の整理の意味をこめてCSSで作るアニメーションの基本的なところを書いてみます。
作ってみるもの
せっかくCSSでアニメーションを作るのなら、なにかテーマを決めてみようと思います。
12月の一大イベントといえば・・・そうですね。冬至です。今年は21日みたいです。
なので、冬至にちなんだ【柚子湯】のアニメーションを作ってみます。
- 柚子を落として
- 浮かばせて
- 湯気
アニメーションで使うCSSのおさらい
@keyframes
要素をどういう風に動かしていくかを設定していきます。
「どのタイミングでなにを行うか」を細かく設定していけるので、使い方次第で複雑な動きも作れます。
例えば、不透明度を徐々に増やしていき要素をゆっくり表示する場合は次のように書けます。
@keyframes fadeIn {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
要素の動かし方
@keyframes
に書いていくことで、要素に変化を与えられます。
transform
要素の状態をベースにして、移動・変形・拡大/縮小・反転・回転ができます。
translateY(移動する値)
縦方向に要素を移動させます。正の値で↓へ、負の値で↑へ移動します。
opacity
要素全体の不透明度を0~1の間で指定します。
0(透明)→→→→1(不透明)です。
その要素にかかれている文字や画像も含めて透明にするので、0は完全に見えません。
@keyframes
で指定することで、簡単にfadeIn, fadeOutが実装できます。
アニメーションの設定
要素に設定するアニメーションのプロパティです。
複数指定する場合はカンマ区切りでa, b, c
のように書きます。
animation-name
要素にかけるアニメーションの名前です。
@keyframes
でつけた名前を設定してください。
animation-duration
アニメーションをどれだけの時間をかけて行うか(0%~100%までにかける時間)です。
animation-timing-function
アニメーションの進め方です。
0%→100%にどのように進んでいくかを設定できます。(最初がゆっくりで徐々にスピードアップする・・・など)
※ timing-functionの調整
ChromeのDeveloper Toolsで視覚的にいじることが可能ですので、動き方を調整する際は使ってみてください。
animation-delay
アニメーションを開始するまでの待ち時間です。
少し送らせてから実行したい場合には設定します。
animation-iteration-count
アニメーションを繰り返す回数です。
infiniteを指定すると永遠に繰り返します。
animation-direction
アニメーションの繰り返し方です。
たぶん、これだけ覚えていれば大丈夫でしょう。
-
normal
0%→100%を繰り返していきます。 (デフォルト) -
alternate
0→100%、100%→0%を繰り返していきます。
実際に作ってみる
(0) 柚子要素を作る
丸を書いてそれっぽいのを作ります。
色を調べたりしながら、こんな感じにしてみました。
少しだけ楽をするために、scssに頼りました。
$yuzu-size: 120px;
.yuzu {
position: fixed;
z-index: 9999;
top: 100vh;
display: block;
box-sizing: border-box;
background-color: hsl(60, 100%, 73%);
border-radius: 50%;
width: $yuzu-size;
height: $yuzu-size;
box-shadow: 30px 30px 16px hsla(0, 0%, 0%, 0.2);
&::before, &::after {
content: "";
border-radius: 50%;
box-sizing: border-box;
position: absolute;
}
&::before {
background-color: transparent;
width: $yuzu-size;
height: $yuzu-size;
$zure: 16px;
bottom: $zure;
right: $zure;
box-shadow: $zure $zure 12px hsla(56, 81%, 65%, 0.4);
}
&::after {
background-color: hsla(85, 60%, 56%, 0.4);
$size: 8px;
width: $size;
height: $size;
top: 20px;
left: 40%;
box-shadow: 1px 1px 1px hsla(85, 40%, 16%, 0.2);
}
}
-
top:100vh
としているのは、落下後の位置をベースにしているためです。完全に見えなくなるまで落とします -
z-index: 9999
は、そのページの最前面になるようにします
(1) 柚子を落とす
(0)で作った柚子を画面下部に落とします。
ベースの位置をtop:100vh
としているので、開始位置は-100vh・・・より少し下からでいいでしょう。
これは、もう単純にtranslateで↓に向けて移動させるだけです。
@keyframes yuzu-fall {
0% {
transform: translateY(-98vh);
}
100% {
transform: translateY(0vh);
}
}
パッと現れるのも味気ないので、すこしフェードインをかけてみることにします。
@keyframes fade-in {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
上記2つのアニメーションを.yuzu
に設定します。
$fall-duration: 700;
$delay: 300;
.yuzu {
animation-name: fade-in, yuzu-fall;
animation-duration: 200ms, #{$fall-duration}ms;
animation-timing-function: linear, cubic-bezier(.4,0,1,1);
animation-iteration-count: 1, 1;
animation-delay: #{$delay}ms, #{$delay}ms;
}
これで落ちます。ヒュンっと落ちます。
(2) 柚子を浮かべる
柚子湯を再現したいので、柚子を落としっぱなしにせずにぷかぷか浮かべたいと思います。
これも上下に延々動かせばいいので簡単ですが、落とすものと同じ.yuzu
に対してアニメーションをかけるので、transformは使えません。そのかわりtopを使います。(逆でもいいです。むしろ逆のほうが楽かもしれません。)
@keyframes yuzu-puka {
0% {
top: 100vh;
}
100% {
top: calc(100vh - #{$yuzu-size/10 * 4});
}
}
これを、(1)で書いた.yuzu
に追記します。
animation-iteration-count
はinfinite(ずっと繰り返す)。
animation-delay
は落ちてから少し待って開始します。
animation-direction
はalternate(順送り逆送りを交互に繰り返す)とします。
$fall-duration: 700;
$delay: 300;
.yuzu {
animation-name: fade-in, yuzu-fall, yuzu-puka;
animation-duration: 200ms, #{$fall-duration}ms, 4s;
animation-timing-function: linear, cubic-bezier(.4,0,1,1), cubic-bezier(0.55, 1.6, 0.57, -0.15);
animation-iteration-count: 1, 1, infinite;
animation-delay: #{$delay}ms, #{$delay}ms, #{$fall-duration + $delay + 300}ms;
animation-direction: alternate;
}
-
animation-duration
は4秒としていますがお好みでもう少しゆっくりでもいいです -
animation-timing-function
もお好みで調整してみるといいと思います
これで、落ちて→ぷかぷかまでできました。
(3) 湯気を出す
柚子をぷかぷか浮かばせるだけでは、まだ柚子湯ではありません。
ほっこり感を出すために、湯気を作っていきます。
(3)-1 湯気のベースを作る
湯気感を出すために、要素の実体は見せずにbox-shadowを駆使していきます。
ぼわっという感じがある程度簡単に出せるのと、box-shadowならばコンテンツの邪魔をしないからです。
<div class="yuge"></div>
.yuge {
top: 100vh;
position: fixed;
background-color: transparent;
border-radius: 50%;
}
そして、画面の下の方にたまる大きな湯気を作ります。
.yuge {
width: 40vw;
height: 10vh;
opacity: 0.6;
box-shadow: 20vw -30vh 0 hsl(0, 0%, 100%);
}
一旦、ぼかしを0にしてだいたいのポジションを決めてから・・・
ぼかしを設定します。
.yuge {
width: 40vw;
height: 10vh;
opacity: 0.6;
box-shadow: 20vw -30vh 120px hsl(0, 0%, 100%);
}
(3)-2 湯気を大量生産する
ひとつだけだとイマイチ湯気っぽくないので、これをたくさん増やします。
CSSを使ってひとつの要素で描ける●(まる)の数は・・・?
で教えていただいたとおり、1つの要素にbox-shadowはいくらでもつけられるので、この仕様を利用します。
同じ場所にたくさんつくっても意味がないのである程度ランダムに配置するsassの関数を作り・・・
@function yuge($max-top-vh: 100) {
@return #{random(50)}vw -#{random($max-top-vh)}vh #{160 + random(50)}px lighten(black, random(10) + 90%);
}
.yugeのbox-shadowに設定します。
box-shadow: yuge(60), yuge(60), yuge(60), yuge(60), yuge(60), yuge(60), yuge(60), yuge(60), yuge(60), yuge(60),
yuge(60), yuge(60), yuge(60), yuge(60), yuge(60), yuge(60), yuge(60), yuge(60), yuge(60), yuge(60),
yuge(60), yuge(60), yuge(60), yuge(60), yuge(60), yuge(60), yuge(60), yuge(60), yuge(60), yuge(60);
あとはこれを下からうえに向けてゆっくり持ち上げていくと・・・
@keyframes bottom-yuge-up {
0% {
opacity: 0;
transform: translateY(100vh);
}
99%, 100% {
opacity: 0.6;
transform: translateY(0vh);
}
}
.yuge {
animation-name: bottom-yuge-up;
animation-duration: 12s;
}
より湯気っぽくなります。
(3)-3 うごきのある湯気も作る
止まっているだけではほっこり感が足りません。
ほっこりさせるために、動きのある湯気も作りましょう。
静的湯気をそのままは使えないため、(3)-2までで作った湯気を「bottom-yuge」としてHTMLとCSSを作り変えます。
<div class="yuge bottom-yuge"></div>
.yuge {
position: fixed;
background-color: transparent;
border-radius: 50%;
}
.bottom-yuge {
top: 100vh;
width: 40vw;
height: 10vh;
opacity: 0.6;
box-shadow: yuge(60), yuge(60), yuge(60), yuge(60), yuge(60), yuge(60), yuge(60), yuge(60), yuge(60), yuge(60),
yuge(60), yuge(60), yuge(60), yuge(60), yuge(60), yuge(60), yuge(60), yuge(60), yuge(60), yuge(60),
yuge(60), yuge(60), yuge(60), yuge(60), yuge(60), yuge(60), yuge(60), yuge(60), yuge(60), yuge(60);
animation-name: bottom-yuge-up;
animation-duration: 12s;
}
そして、動く湯気の実体を作っていきます。
柚子の逆でゆーーーーーっくり下から上に動きます。
<ul class="hokkori-list">
<li class="yuge hokkori-yuge"></li>
</ul>
@keyframes hokkori-yuge {
0% {
opacity: 0;
transform: translateY(0vh);
}
50% {
opacity: 0.8;
transform: translateY(-50vh);
}
99%, 100% {
opacity: 0;
transform: translateY(-100vh);
}
}
.hokkori-yuge {
display: block;
opacity: 0;
animation-name: hokkori-yuge;
animation-iteration-count: infinite;
animation-timing-function: linear;
box-shadow: yuge(100);
$size: 120px;
width: #{$size}px;
height: #{$size * 1.3}px;
top: #{100 + random(50)}vh;
left: #{random(50)}vw;
$delay: 5000 + random(3000);
animation-delay: #{$delay}ms;
animation-duration: #{5000 + random(10000)}ms;
}
位置やアニメーションの発動タイミング・長さなどを変えられるようにして、とにかくランダムに配置していくと、湯気感がどんどん増していきます。
最後に
画面一面を柚子湯に仕立て上げることはできましたが、アニメーションだの半透明だのを駆使するとめちゃくちゃ重くなります。
スマートフォンで見ていると手がほかほかしてくる程に負荷がかかります。
やりすぎ注意・増やしすぎ注意です。
サンプルソースを公開しました
- Githubリポジトリ https://github.com/myamyu/yuzuyu
- サンプルページ https://myamyu.github.io/yuzuyu/