JavaScript

【JavaScript】波紋(リップル)アニメーションの作成方法

JavaScriptでクリックしたところから波が発生する波紋(リップル)アニメーションの作成方法を紹介します。

この記事の目次
  1. 波紋(リップル)アニメーションのデモ
  2. JavaScriptコードの解説
  3. CSS側:波紋を広げるキーフレーム
  4. スクロール位置や子要素の影響を受けない座標計算

JavaScriptでクリックしたところから波が発生する波紋(リップル)アニメーションの作成方法を紹介します。

JavaScriptのリップルは、クリック位置に小さなspanを追加し、CSSの@keyframesで円を拡大しながら透明にし、アニメーションが終わったら要素を削除する、という3段階で作れます。ライブラリは不要で、JavaScriptが担当するのは「座標の計算」と「要素の追加・削除」だけです。

波紋(リップル)アニメーションのデモ

デモが表示されない場合はCodePenで直接開くこともできます。

スポンサーリンク

JavaScriptコードの解説


  const container = document.querySelector(".container");

  container.addEventListener("click", (e) => {
    // クリックの場所
    const x = e.clientX;
    const y = e.clientY;
  
    // 親要素の端
    const containerTop = e.target.offsetTop;
    const containerLeft = e.target.offsetLeft;
  
    // 端までの距離
    const inY = y - containerTop;
    const inX = x - containerLeft;
  
    // リップル生成
    const ripple = document.createElement("span");
    ripple.classList.add("ripple");
    ripple.style.top = `${inY}px`;
    ripple.style.left = `${inX}px`;
  
    container.appendChild(ripple);
  
    setTimeout(() => ripple.remove(), 700);
  });
  
  

マウスイベントのclientXclientYで、クリックされた場所の座標を取得します。
offsetTopoffsetLeftは、親要素からの相対的な位置です。
この2つの差を取ると、コンテナの左上を基準にしたクリック位置が求まります。
あとはcreateElementで要素を作り、CSSでスタイルを当てたクラスを付与してappendChildで挿入すれば、クリックした場所から広がる波紋になります。
最後に、要素を残すとDOMが増え続けてしまうのでremove()で削除します。

CSS側:波紋を広げるキーフレーム

波紋の見た目はCSSが担当します。.rippleは中心をクリック位置に合わせるためtransform: translate(-50%, -50%)で自身の半分だけ戻し、scaleを大きくしながらopacityを0にします。JavaScript側のsetTimeoutの700msは、このanimationの時間と揃えます。

.container {
  position: relative;
  overflow: hidden;
}

.ripple {
  position: absolute;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.6);
  transform: translate(-50%, -50%) scale(0);
  animation: ripple 0.7s ease-out forwards;
  pointer-events: none;
}

@keyframes ripple {
  to {
    transform: translate(-50%, -50%) scale(20);
    opacity: 0;
  }
}

pointer-events: noneを付けると、広がった波紋の上をクリックしても次の波紋が正しく発生します。

スクロール位置や子要素の影響を受けない座標計算

clientXはビューポート基準、offsetLeftoffsetParent基準なので、ページがスクロールしていたり、コンテナの中の子要素をクリックした(e.targetが子要素になる)ときに位置がずれることがあります。現在はgetBoundingClientRect()でコンテナの位置を取ると、どちらの影響も受けません。

container.addEventListener("click", (e) => {
  const rect = container.getBoundingClientRect();
  const inX = e.clientX - rect.left;
  const inY = e.clientY - rect.top;

  const ripple = document.createElement("span");
  ripple.classList.add("ripple");
  ripple.style.left = `${inX}px`;
  ripple.style.top = `${inY}px`;
  container.appendChild(ripple);

  ripple.addEventListener("animationend", () => ripple.remove());
});

削除のタイミングもsetTimeoutではなくanimationendイベントにすると、CSS側の時間を変えてもJavaScriptを直す必要がありません。

appendChildの使い方は、JavaScriptのDOM要素の操作方法を参照してみてください。

クリック以外のきっかけで要素を動かしたい場合は、スクロールに合わせてふわっと表示するscrollRevealをライブラリなしで実装する方法や、ページ全体の切り替えを演出するページ遷移アニメーションの作り方も同じ「クラスを付けてCSSで動かす」考え方で作れます。

スポンサーリンク