CSSで水滴を描く

CSSで水滴を描く方法のメモです。きっかけは、YouTubeで観た[Photoshop Tutorial: How to Make Water Drops](https://www.youtube.com/watch?v=YyothXgOR0Y)という動画。Photoshopのレイヤースタイルにシャドウの効果を追加していく方法がCSSでも真似できそうに思えたので再現してみました。

<p class="codepen" data-height="584.7484130859375" data-default-tab="result" data-slug-hash="PoEMjmY" data-user="kaz_hashimoto" style="height: 584.7484130859375px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
  <span>See the Pen <a href="https://codepen.io/kaz_hashimoto/pen/PoEMjmY">
  CSS waterdrops using mix-blend-mode</a> by Kazuhiro Hashimoto (<a href="https://codepen.io/kaz_hashimoto">@kaz_hashimoto</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>

※素材画像: Photo by [Evangeline Sarney](https://unsplash.com/@dhanya22) on [Unsplash](https://unsplash.com/photos/KyAacW3enww)

はじめに、アニメーションが付いてない方の４個の水滴の作り方の説明です。水滴を乗せるベースの画像をここではphoto.jpgとします。以下のコードで、`div.waterdrops`が水滴１個になります。

```html:html
<div class="photo">
  <img src="photo.jpg" alt="">
  <div class="waterdrops a"></div>
  <div class="waterdrops b"></div>
  <div class="waterdrops c"></div>
  <div class="waterdrops d"></div>
</div>
```

水滴の領域は正方形のボックスですが、レスポンシブ対応にするため、寸法を`width`にのみ依存するようにしてパーセント単位で指定します。コンテナーの`div.photo`には`position: relative`を設定しておきます。寸法などその他のプロパティの設定についてはCodePen参照。

```css:css
.photo {
  position: relative;
}

.waterdrops {
  width: 6.25%;  /* 50/800  (W) */
  position: absolute;
}
```

ここでは`width`を6.25%にしました。これは、ベースの画像を幅800pxで表示した時にちょうどよい大きさに見える水滴の寸法50pxから算出した値です。
これだけでは`div.waterdrops`が高さを持たないので、擬似要素を使ってパディング領域を作り正方形の領域をはめ込みます。

```css:css
.waterdrops::before {
  display: block;
  content: '';
  width: 100%; /* 包含ブロックの幅 (W) */
  height: 0;
  padding-top: 100%;  /* 包含ブロックの幅 (W) */
}
```

水滴の外形となる正円を作ります。

```css:css
.waterdrops {
  width: 6.25%;
  position: absolute;
  border-radius: 50%; /* 外形の円*/
}
```

水滴の質感をベース画像のレイヤーとのブレンドで作り出すため、背景を黒→白のグラデーションで塗ります。グラデーションの方向は左上から右下です。ブレンド時のメリハリをつけるため、黒の長さは25%まで延ばします。

```
background-image: linear-gradient(to right bottom, black, black 25%, white);
```

![drop1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469745/1caa842b-fdbe-54f4-6348-668061d6f5c8.png)
水滴の外側と内側にシャドウを設定します。内側のシャドウ(`inset`)の位置は、左上を厚くする（下図A）、右下を厚くする（同B)など考えられますが、仕上がりはAの方が水滴に立体感が出ていい感じだったのでここではAのパターンで設定します。ちなみに、参考にした動画[Photoshop Tutorial: How to Make Water Drops](https://www.youtube.com/watch?v=YyothXgOR0Y)では、レイヤー効果の「シャドウ（内側）」をBのように右下を厚くするように設定していました。

```
box-shadow: 10px 10px 14px red, inset 2px 2px 5px blue; /* A */

box-shadow: 10px 10px 14px red, inset -2px -2px 5px blue; /* B */
```

![box-shadow.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469745/49a32760-1e1d-8496-c7c8-6e29f2fcebf2.png)
シャドウの色をほぼ黒[^1]にして不透明度を下げます。
[^1]: このRGB値はHSB値 H=0°, S=100%, B=1%にほぼ相当します。Photoshopでレイヤー効果のスタイルパネルの「ドロップシャドウ」や「シャドウ（内側）」に対する描画色の初期値に使用されています。

```
box-shadow: 10px 10px 14px rgba(4,0,0,0.3),
    inset 2px 2px 5px rgba(4,0,0,0.3);
```

![drop3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469745/1b45eb27-60d3-0339-8b9d-379a33524c36.png)
水滴の左上に小さな光の点を付けます。

```css:css
.waterdrops::after {
  display: block;
  content: '';
  width: 12%; /* 6/50 */
  height: 12%;
  border-radius: 50%;
  position: absolute;
  top: 20%;
  left: 20%;
  background: rgba(255,255,255,0.9);
}
```

![drop4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469745/698cc7c6-fbde-8843-ced4-36f6ac370c33.png)
`div.waterdrops`に`mix-blend-mode: overlay`を設定して、ベース画像のレイヤーと水滴のレイヤーをブレンドします。最後に水滴の表示位置を調整して出来上がり。

```css:css
.waterdrops {
  width: 6.25%;
  position: absolute;
  border-radius: 50%;
  background-image: linear-gradient(to right bottom, black, black 25%, white);
  box-shadow: 10px 10px 14px rgba(4,0,0,0.3), inset 2px 2px 5px rgba(4,0,0,0.3);
  mix-blend-mode: overlay;
}

.waterdrops.a {
  top: 60%;
  left: 55%;
}
```

![drop5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/469745/883e0e83-916c-67ba-4760-d4ea3787d1b5.png)

## 水滴をアニメーションで動かす
JavaScriptを使って水滴に動きを加えてみましょう。アニメーションは[GSAP](https://greensock.com/gsap/)で作成します。

```html:html
<script src='https://cdnjs.cloudflare.com/ajax/libs/gsap/3.10.4/gsap.min.js'></script>
<script src='https://cdnjs.cloudflare.com/ajax/libs/gsap/3.10.4/EasePack.min.js'></script>
```

準備として、水滴のdiv要素を簡単に生成できるよう関数にしておきます。

```javascript:javascript
function createWaterdrop() {
  const el = document.createElement('div');
  el.classList.add('waterdrops');
  return el;
}
```

使い方はこんな感じ。生成した水滴のdivに位置をセットしてコンテナー`div.photo`の子要素に追加します。

```javascript:javascript
const photo = document.querySelector('.photo');

let d = createWaterdrop();
d.style.top = '5%';
d.style.left = '45%';
d.style.opacity = 0;
photo.appendChild(d);
```

ここでは、一番上のオレンジにかかる水滴の動きを実現するコードを説明します。このアニメーションは２つの動きで構成されます。

1. 水滴が縦に伸びながら上から下へ流れる動き
1. 水滴が縮んで斜めになって止まるまでの動き

それぞれを[gsap.to()](https://greensock.com/docs/v3/GSAP/gsap.to()) tweenに分けて[timeline](https://greensock.com/docs/v3/GSAP/Timeline)を構成すると、

```javascript:javascript
let tl = gsap.timeline();
tl.to(d, {scaleY: 1.5, scaleX: 0.7, opacity: 1,
        x: '10%', y: '150%', duration: 1.5})
.to(d, {scale: 1, skewX: -10, skewY: -10, y: '200%', duration: 2.5});
```

(GIFアニメーションです。Firefox向け直リンクは[こちら](https://res.cloudinary.com/dsfempoad/image/upload/v1651409489/qiita/drop-tween1_h0tmep.gif))
![drop-tween1.gif](https://res.cloudinary.com/dsfempoad/image/upload/v1651409489/qiita/drop-tween1_h0tmep.gif)
２つの動きの境目が滑らかにつながりません。１つ目のtweenが終了した所で一旦止まってから２つ目のtweenが開始するように見えて人工的な感じがしてしまいます。これは、easing関数がそれぞれのtweenで完結するためです。

|| tween1 | tween2 |
|:-:|:-:|:-:|
| easing | 速い→減速→停止  | 速い→減速→停止  |

「でろ〜ん」とした感じの動きにするために、１つのeasing曲線上を２つのtweenが連続して動くようにしたい。そこで、`gsap.to()`の`keyframes`プロパティを使います。２つのtweenに指定していたパラメーターのオブジェクトを配列にして`keyframes`に設定し、全体は１つの`gsap.to()` tweenにまとめます。easing関数は[SlowMo](https://greensock.com/docs/v3/Eases/SlowMo)プラグインの`slow()`関数を使ってみました。

| keyframes| [tween1, tween2] |
|:-:|:-:|
| easing | 速い→減速→等速→加速→停止 |

easing曲線は[こちら](https://greensock.com/docs/v3/Eases)で確認できます。

```javascript:javascript
gsap.to(d, {keyframes: [{scaleY: 1.5, scaleX: 0.7, opacity: 1,
        x: '10%', y: '150%', duration: 1.5}, {scale: 1, skewX: -10, skewY: -10, y: '200%', duration: 2.5}],
        ease: 'slow(0.7, 0.4, false)'
       });
```

(GIFアニメーションです。Firefox向け直リンクは[こちら](https://res.cloudinary.com/dsfempoad/image/upload/v1651409670/qiita/drop-tween2_h35iaa.gif))
![drop-tween2.gif](https://res.cloudinary.com/dsfempoad/image/upload/v1651409670/qiita/drop-tween2_h35iaa.gif)