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);
});
マウスイベントのclientXとclientYで、クリックされた場所の座標を取得します。
offsetTopとoffsetLeftは、親要素からの相対的な位置です。
この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はビューポート基準、offsetLeftはoffsetParent基準なので、ページがスクロールしていたり、コンテナの中の子要素をクリックした(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で動かす」考え方で作れます。