リップルエフェクト解体新書

#リップルエフェクトとは
実際に見た方が早いです。これです。

<p class="codepen" data-height="300" data-theme-id="dark" data-default-tab="html,result" data-user="numlocker-japan" data-slug-hash="WNboNev" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="Ripple Effect Sample">
  <span>See the Pen <a href="https://codepen.io/numlocker-japan/pen/WNboNev">
  Ripple Effect Sample</a> by NumLocker (<a href="https://codepen.io/numlocker-japan">@numlocker-japan</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

こんな感じで、クリックするとそのクリック位置に合わせて波紋が出るエフェクトのことをリップルエフェクトと言います。
マテリアルデザインにも使われているアニメーションです。

#実装方法
では、さっそく実装方法を見ていきます。

##クリックされた座標の取得
リップルエフェクトは先ほども述べたとおり、マテリアルデザインに使われており、「ユーザーのクリックに応じた」動作が求められます。つまり、クリックされた位置にちょうどエフェクトが描画される必要があります。
クリックされた位置(座標)をJavaScriptで取得する方法は、2種類あります。
**event.pageX / event.pageY**を使う方法と、**event.offsetX / event.offsetY**を使う方法です。
例えば、特定のdiv要素がクリックされた際にそのpageXの値をconsoleに表示するならこのようになります。

```sample.js

document.querySelector('div').addEventListener('click', (event) => {
    console.log(event.pageX);
})

```

下に、html要素およびその配下のdiv要素をクリックした時に、event.pageX / event.pageYおよびevent.offsetX / event.offsetYで取得したクリックイベントの座標がアラートで表示されるページを作ったので、それぞれの違いを、ぜひ一度確かめてみてください。(淡い青色の部分にhtml要素が広がっており、濃い青色の部分がその配下のdiv要素です。)

<p class="codepen" data-height="500" data-theme-id="dark" data-default-tab="html,result" data-user="numlocker-japan" data-slug-hash="NWPbWjN" style="height: 500px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="Where is the event?">
  <span>See the Pen <a href="https://codepen.io/numlocker-japan/pen/NWPbWjN">
  Where is the event?</a> by NumLocker (<a href="https://codepen.io/numlocker-japan">@numlocker-japan</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

以上のことから、event.pageX / event.pageYで返される値は、画面に対する**絶対座標**であること、event.offsetX / event.offsetYで返される値は、クリックされた要素の右上の点からの**相対座標**であり、**下の要素のoffsetX / offsetYの値も、一番上にある要素の値が返される**ということが分かります。(div要素をクリックした際に、html要素のクリックイベントのoffsetがdiv要素のものと一致していたことから分かる。)

##座標の値を用いて円の描画を行う
では、クリックイベントの座標の値に合わせて、クリックされた位置に円を描画してみます。
分かりやすいように半透明にしています。いろんな場所でクリックしてみてください。

<p class="codepen" data-height="500" data-theme-id="dark" data-default-tab="css,result" data-user="numlocker-japan" data-slug-hash="VwYmYNp" style="height: 500px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="Circle Sample">
  <span>See the Pen <a href="https://codepen.io/numlocker-japan/pen/VwYmYNp">
  Circle Sample</a> by NumLocker (<a href="https://codepen.io/numlocker-japan">@numlocker-japan</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

では、重要な部分のコードを説明していきます。

```Circle_Sample.css

button{
    /*とりあえずそれっぽいボタンにします*/
    width: 180px;
    height: 60px;
    font-size: 18px;
    color: white;
    background-color: blue;
    border: 2px solid #fff;
    border-radius: 12px;

    /*ここからが大事*/
    outline:none;
    position: relative;
    z-index: 1;
}

.ripple{
    /*とりあえずそれっぽいエフェクトにします*/
    width: 50px;
    height: 50px;
    background-color: #f005;
    border-radius: 50%;

    /*ここからが大事*/
    position: absolute;
    z-index: 5;
    transform: translateX(-50%) translateY(-50%);
    outline: none;
    pointer-events: none;
}

```

```Circle_Sample.js

document.querySelector('button').addEventListener('click', (event)=>{
    event.currentTarget.insertAdjacentHTML('afterbegin', '<span class="ripple" style="left:' + event.offsetX + 'px;top:' + event.offsetY + 'px;"></span>')
})

```

(rippleクラスは、赤い円に付与されているクラスです)
JavaScriptでは、クリックに合わせて、その対象の要素(ここではbutton)の内側にrippleクラスを持ったspan要素を挿入しています。button要素は、``event.currentTarget`` として取得しています。

ボタンに指定した ``outline: none;`` は、指定しないと、こんな感じで、青い枠線が表示されてしまいます。

|![outline.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/305299/db975498-debf-d2a7-09dc-f0f172c01a80.png)
|:-:|

次に、ボタン側に ``position: relative;`` , エフェクト側に ``position: absolute;`` を指定しています。これは、JavaScript側で、クリックイベントの**offset**の値をエフェクトの位置指定に使用していることに起因します。

ボタン側に ``position: relative;``, エフェクト側に ``position: absolute;`` が指定されていることで、rippleクラスの ``top``, ``left`` の基準が、bodyではなく、buttonになります。これにより、rippleクラスの ``left`` にクリックイベントの ``offsetX``, rippleクラスの ``top`` にクリックイベントの ``offsetY`` を代入することで、要素の位置がクリック位置と一致するわけです。

ただし、これだけではクリック位置とrippleクラスの指定されたspan要素の**左上**が一致します。これは、CSSの位置の基準が要素の左上であるからです。そのため、要素の幅の半分だけ左に、要素の高さの半分だけ上に、位置をずらすことで、クリック位置に円の**中心**が一致して表示されるようになります。そして、そのためのスタイルが、``transform: translateX(-50%) translateY(-50%);`` です。

ところが、ここで問題になってくるのが、``z-index`` です。というのも、**positionやtransformの値を設定することで、z-indexがうまくきかなくなってしまう**のです。ここでは念のためz-indexを明示的に指定していますが、ここで紹介したコードをそのまま他のページに埋め込んだりすると、上手く表示されない可能性があります。

最後に、rippleクラスには、``pointer-events: none;`` が指定されています。
これは、rippleクラスのついた要素、つまり赤い円に対し、**クリックイベントを無効化する**プロパティです。
これを行わないと、すでに存在していた円の上をクリックした際、その円を基準として座標が返されてしまいます。(**下の要素に対するoffsetの値であっても、一番上にある要素の値が返される**ことを思い出してください。)

##エフェクトの完成
ではいよいよ、先ほどの赤い円が表示されるボタンをさらにグレードアップして、リップエフェクトを作ってみます、先ほどのボタンに足りないのは次の要素です。

* 赤い円のボタン外の部分を非表示にする
* 時間がたつにつれて、円が大きくなりながら消えていくアニメーション

これだけで、冒頭で紹介したようなボタンが完成します。
1つめは簡単です。buttonに ``overflow: hidden;`` を指定するだけです。
では2つめはどうでしょうか。これは、CSSアニメーションを使って実装する方法が一般的です。(詳しい説明はよそ様にお願いします→[CSSアニメーション 入門](https://qiita.com/soarflat/items/4a302e0cafa21477707f) )
では、作ってみます。

```Circle_Animation.css

@keyframes rippleEffect {
    0% {
        opacity: 1;
        width: 0px;
        height: 0px;
    }

    40% {
         opacity: .3;
    }

    100% {
        opacity: 0;
        width: 100px;
        height: 100px;
    }
}

```

始まった直後(0%の時点)は、``opacity: 1;`` としているので、不透明ですが、次第に透明になっていき、終了時(100%の時点)で、 ``opacity: 0;`` すなわち完全に透明になっています。さらに、それにしたがって大きさが0pxから100pxになっています。

では、このアニメーションをrippleクラスに適用します。

```css

.ripple{
    animation-name: rippleEffect;
    animation-duration: .5s;
    animation-fill-mode:none;
}

```

0.5sで実行し、その後はCSSアニメーション実行の影響を受けていない状態に戻ります。よって、

```css

.ripple{
    width: 0px;
    height: 0px
}

```

も合わせて指定しておくことで、0.5sたてば、大きさが0pxになってくれます。



ここまで追加したCSSをまとめます。

```Circle_Sample_added.css

button{
    overflow: hidden;
}

.ripple{
    width: 0px;
    height: 0px

    animation-name: rippleEffect;
    animation-duration: .5s;
    animation-fill-mode:none;
}

@keyframes rippleEffect {
    0% {
        opacity: 1;
        width: 0px;
        height: 0px;
    }

    40% {
         opacity: .3;
    }

    100% {
        opacity: 0;
        width: 100px;
        height: 100px;
    }
}

```

では、赤い円を表示していたボタンにこれらを追加します。

<p class="codepen" data-height="500" data-theme-id="dark" data-default-tab="css,result" data-user="numlocker-japan" data-slug-hash="mdyOebv" style="height: 500px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="Ripple Effect Final">
  <span>See the Pen <a href="https://codepen.io/numlocker-japan/pen/mdyOebv">
  Ripple Effect Final</a> by NumLocker (<a href="https://codepen.io/numlocker-japan">@numlocker-japan</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

いかがでしょうか？
こうして読み解いていくと、決して難しいものではないと思っていただけたのではないでしょうか？

#応用編

##四角形エフェクト
リップルエフェクトとは呼ばれないと思いますが、今回の応用として四角形のエフェクトも作ってみました。リップルエフェクトとの違いは、はじめの位置がクリック位置と一致するだけでなく、終了位置がborderと一致している点です。時間がかかる処理の前などに入れると見栄えがよさそう...

<p class="codepen" data-height="500" data-theme-id="dark" data-default-tab="css,result" data-user="numlocker-japan" data-slug-hash="bGNBpEm" style="height: 500px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;" data-pen-title="bGNBpEm">
  <span>See the Pen <a href="https://codepen.io/numlocker-japan/pen/bGNBpEm">
  bGNBpEm</a> by NumLocker (<a href="https://codepen.io/numlocker-japan">@numlocker-japan</a>)
  on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

###処理の概要
リップルエフェクトと同じ手法で、四角形を描画します。transformによって、CSSアニメーションが終わるまでに、四角形を大きくしながら、中心位置までずらしています。また、クリック位置をCSS変数に代入してCSSアニメーションに組み込んでいるのですが、(他にもっと賢い方法があれば教えてください。*_ _))ﾍﾟｺﾘ )
また、クリックから時間差でbuttonのborderとcolorをオレンジ色に変更します。これには ``setTimeout()`` を用いました。また、色変更がゆっくり行われるのは、buttonに ``transition: .3s;`` が指定されているからです。

#まとめ
いかがでしたか？はじめは非常に巧みで複雑なエフェクトに見えますが、マスターしてしまえば簡単にクールなボタンが作れます。
アイデア次第でどんなエフェクトにも転用することができることでしょう。ぜひ、誰もが押したくなるボタンを作りましょう！