リップル波紋ボタン
クリックした位置から波紋が広がる。Material Designでおなじみの「押した手応え」。
プレビュー
操作クリック/タップで動きます。
仕様書
- 向く場面
- 送信など押した手応えを返したい主要ボタン
- 指で隠れるモバイルのタップ位置を伝えたい
- 押してから結果まで一瞬の間がある操作
- 避ける場面
- 一覧の行など大量に並ぶ要素
- 押した直後に画面全体が遷移するボタン
- 連打される操作(span が積み残る)
- 構成要素
- ボタン
- ラベル
- 波紋
- 持っている状態
- 通常
- 押下中
- キーボード
Tab でボタンへ・Enter/Space で実行(button 標準)。キー発火時は波紋がボタン中央から出る
- 支援技術
波紋は aria-hidden の装飾 span。reduced-motion では波紋を出さず :active の色変化で手応えを返す。focus-visible の輪郭は波紋に隠れない
- 動き
波紋を scale 0→4・opacity→0 で 0.6s linear。押した位置の手応え。reduced-motion では無し
デザインの要点
- 波紋は押した座標から出す。中央固定だと「押した場所が認識された」実感が薄く、指で隠れるモバイルで効かない
- 円の直径はボタンの長辺、拡大は 4 倍。どの端を押しても波紋が反対側の端まで届く最小の組み合わせ
- 動かすのは transform と opacity だけ。width/height を動かすとレイアウトが毎フレーム走る
- キーボードで押されたとき(clientX=0)は中央から出す。座標をそのまま使うと左上の角から波紋が出る
使いどころ
押した瞬間に「効いた」と伝えたいボタン全般に向く。とくにフォームの送信ボタンやモバイルの主要アクションのように、押してから結果が出るまでに一瞬の間があるUIで効果が大きい。波紋はタップ位置から広がるので、指で隠れて見えないモバイルでも「自分が触った場所が認識された」という手応えを返せる。逆に、テーブルの行やリスト項目のように一画面に何十個も並ぶ要素へ付けると、スクロール中の誤タップごとに波紋が走って画面が騒がしくなるため向かない。また、押した直後に画面全体が切り替わる遷移ボタンでは、波紋が広がりきる前に消えて演出が中途半端になる。その場合は波紋ではなく、遷移先のローディング表示に投資した方が体験がよくなる。
仕組み
仕組みは「クリック位置に円を1つ置いて、transform: scale() で拡大しながら opacity を0に落とす」だけ。ボタンに position: relative と overflow: hidden を与え、生成した span を絶対配置することで、円がボタンの角丸からはみ出さずに切り取られる。円の中心をクリック位置に合わせるには、getBoundingClientRect() でボタンの位置を取り、event.clientX から差を引いて座標を出す。半径はボタンの対角線の長さを基準にすると、どの位置を押しても端まで波紋が届く。アニメーション対象を width/height ではなく transform に限定するのが要点で、transform と opacity はコンポジタだけで処理できるためレイアウトの再計算が起きない。終了後は animationend か setTimeout で span を削除し、DOMに要素が積み残らないようにする。
実装の注意
最も多い失敗は overflow: hidden の付け忘れで、波紋が四角く飛び出してボタンの形が崩れる。次に多いのが span の削除漏れ。連打されるとDOMに数百個の空要素が残り、長時間開いたままの画面で少しずつ重くなる。連打対策としては、既存の波紋を消してから新しい波紋を出すか、同時に存在できる数を1〜2個に制限する。波紋の色はボタンの背景色に対して十分なコントラストが必要で、濃い背景に濃い波紋を置くと動いていることがわからない。白の透明度20〜40%あたりが無難だが、明るい背景のボタンでは黒系の半透明にする。iframe のプレビューでは親ページのスクロール位置が影響しないが、実ページで position: fixed の要素の中に置く場合は座標計算の基準がずれることがあるので、必ず getBoundingClientRect() の値を使い、offsetLeft のような累積値に頼らない。
アクセシビリティ
波紋は装飾なので、支援技術に読ませる必要はない。生成する span には aria-hidden="true" を付け、ボタンのラベルが波紋の要素で汚れないようにしている。重要なのは prefers-reduced-motion への対応で、動きに酔いやすいユーザーの設定を尊重して @media (prefers-reduced-motion: reduce) では波紋のアニメーションを止めて span を表示しない。このとき「押した手応え」がゼロになると困るので、代わりに背景色をわずかに濃くする :active スタイルを同じメディアクエリの中で用意してある。また、キーボードで操作するユーザーはクリック座標を持たない(Enter や Space で発火した click は clientX/clientY が 0 になる)ため、e.detail === 0 か座標が (0,0) のときはボタン中心から波紋を出す分岐を入れている。これが無いと波紋が左上の角から出る。:focus-visible のアウトラインは outline-offset で外側に出し、overflow: hidden の波紋に切り取られないようにしてある。
調整して使う
触る値は3つ。波紋の持続時間(0.6秒が既定。長くすると余韻が出るが連打に弱くなるので0.4〜0.8秒の範囲)、波紋の色と透明度(明るい背景のボタンなら rgba(0,0,0,.15)、濃い背景なら rgba(255,255,255,.35) から調整)、そして最終的な拡大率(ボタンの対角線を基準に2〜2.5倍あれば端まで届く)。落ち着いた印象にしたいときは、拡大しきる前に opacity を0へ向かわせるのではなく、拡大の終盤で一気に消す ease-out 寄りのイージングにすると上品になる。ブランドカラーを波紋に使うと押した瞬間だけ色が差し込む表現になり、テキストボタンのような装飾の少ないボタンでも存在感を出せる。
コード(コピーして使えます)
<button class="ripple-btn">クリックしてみて</button>