
CSSだけでも波アニメーションはできますが、今回はSVGデータを使って実装してみました。
基本的なアニメーション
まずはwidthのサイズを300pxにした場合のアニメーションを紹介します。
CSS
.wave-sample {
overflow:hidden;
margin:0 auto;
width:300px;
height:100vh;
background-color:#4690dc;
}
.wave-sample svg path {
fill:#fff;
animation:wave linear 4s infinite;
}
@keyframes wave {
from {
transform:translate(0);
}
to {
transform:translate(-66.66%);
}
}
HTML
<div class="wave-sample"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 300" width="900" height="300"> <path d="M 0,0 v 100,0 q 150,50 300,0 t 300,0 q 150,50 300,0 v 0,-100 Z" /> </svg> </div>
以上でアニメーションできます。
幅(width)を100%にしてみる
上述のCSSを100%にすると、途中で波が切れてしまいます。
原因は、.wave-sample をwidth:100% にしても、内部のSVGが width=”900″のまま固定されていることです。
さらに<path>自体をtransformで動かしているため、SVGのviewBox=”0 0 900 300″の外へ移動した部分がクリップされるためです。
このタイプの波アニメーションなら、pathではなくSVG全体を横に動かすようにすると扱いやすいです。3つ分の波を横に並べたSVGを「コンテナ幅の300%」にして、1波分=33.333%ずつ移動させます。
CSS
.wave-sample {
width: 100%;
height: 100vh;
margin: 0 auto;
overflow: hidden;
position: relative;
background-color: #4690dc;
}
.wave-sample .wave {
display: block;
width: 300%;
height: 300px;
animation: wave 4s linear infinite;
}
.wave-sample .wave path {
fill: #fff;
}
@keyframes wave {
from {
transform: translateX(0);
}
to {
transform: translateX(-33.333333%);
}
}
HTML
<div class="wave-sample"> <svg class="wave" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 300" preserveAspectRatio="none" > <path d="M 0,0 v 100 q 150,50 300,0 t 300,0 t 300,0 v -100 Z" /> </svg> </div>
SVGの中には、
0px 300px 600px 900px
|----------|----------|----------|
波1 波2 波3
という感じで、300単位の波が3つ入っています。
これをSVG自体、
width: 300%;
にすることで、
ブラウザ幅 ↓ ┌─────┐ │ 波1 │ └─────┘ SVG全体 ┌──────────┬──────────┬──────────┐ │ 波1 │ 波2 │ 波3 │ └──────────┴──────────┴──────────┘
という状態にします。
そして1波分だけ、
transform: translateX(-33.333333%);
移動させます。
これなら画面幅が変動しても途中で切れにくくなります。
また
preserveAspectRatio="none"
も重要なポイントです。
指定しておくことで、SVGをブラウザ幅に合わせて横方向へ伸縮できます。
これがないと、
viewBox="0 0 900 300"
のアスペクト比をSVGが維持しようとするため、「画面いっぱいに波を広げたい」という今回の用途では少し扱いづらくなります。
さらにスムーズなアニメーションにする
幅100%でも動くようにしましたが、このままだと途中で切れるような「カクカク」した動きになります。
原因はアニメーションの終了地点と開始地点で波形が完全に一致していないためです。
今回のSVGでは、見た目上は300ごとに波がありますが、実際には q → t で「下向き+上向き」が1セットになっているため、波形の1周期は300ではなく600です。そのため -33.333%=300相当でループすると、位相が逆の位置に戻ってしまい「カクッ」と見えます。
このケースは、同じ波形を2セット用意して、ちょうど1セット分だけ移動させる方法が良さそうです。
CSS
.wave-sample {
width: 100%;
height: 100vh;
margin: 0 auto;
overflow: hidden;
position: relative;
background-color: #4690dc;
}
.wave-sample .wave {
display: block;
width: 200%;
height: 300px;
animation: wave 10s linear infinite;
will-change: transform;
}
.wave-sample .wave path {
fill: #fff;
}
@keyframes wave {
from {
transform: translate3d(0, 0, 0);
}
to {
transform: translate3d(-50%, 0, 0);
}
}
HTML
<div class="wave-sample"> <svg class="wave" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1200 300" preserveAspectRatio="none" > <path d=" M 0,0 V 100 q 150,50 300,0 t 300,0 q 150,50 300,0 t 300,0 V 0 Z "/> </svg> </div>
ポイントはここです。
width: 200%;
として、SVGの中にまったく同じ波を2つ並べています。
イメージとしては、
SVG全体 ┌──────────────┬──────────────┐ │ 波セットA │ 波セットB │ │ q → t │ q → t │ └──────────────┴──────────────┘ ↑ 画面にはこの半分だけ表示
これを、
開始 ↓ [A][B] ↓ 左へ移動 [B][ ] ↑ 終了地点
と動かします。
AとBが完全に同じ形なので、
transform: translateX(-50%);
まで移動した瞬間に
transform: translateX(0);
へ戻っても、
表示されている波形がまったく同じです。
そのためループ地点が分からなくなります。
また
will-change: transform;
を入れて動きを少し滑らかにしています。
まとめ
今回はSVG自体のサイズはそのままでをループさせることでアニメーションを実現しました。
その他
transform: scale(*)
という方法もあり、どちらかというとSVGならこの調整の方がレスポンシブとの親和性も高く扱いやすいです。
次回はその辺も試してきたいと思います。