CSS タップ エフェクト 9選 — 一行でコピペOK
css タップ エフェクトとは、指でボタンやカード、リンクに触れた瞬間だけ色・大きさ・影が変わり、入力が確かに届いたとスマホの画面だけで伝える演出です。この記事では9種類を選び、それぞれ6〜25行のコードとして実装しました。
自動再生 · タイルを押すとその項目へ · すべて1つのzip
- 01 アウトラインのリングパルス
- 02 押し込み縮小+影の減少
- 03 hoverなし端末専用の:active
- 04 長押しで現れる遅延リアクション
- 05 リスト行のタップハイライト
- 06 カードタップのスケール+明るさ
- 07 タップ確認ジッター(エラー揺れではない)
- 08 チェックボックスのタップフィードバック
- 09 インラインリンクのタップフィードバック
並び順は人気度ではなく、指先の一回のタップが実際に通っていく順番です。押すとまずリングが膨らみ(01)、押し込まれた感触が続き(02)、触れる機器によって反応が分かれ(03)、長く押すと待ち時間が挟まり(04)、リストとカードを選び(05・06)、いいねを短く確認し(07)、チェックボックスで状態を切り替え(08)、最後はリンクを押して次の画面へ進みます(09)。各コードは動作の要点だけを切り出してあり、残りのマークアップと変形値はzipにそのまま入っています。
01アウトラインのリングパルス
ボタンの内側を塗りつぶす波紋ではなく、外側でbox-shadowのリングが一度だけ膨らんで消えます。タップが終わってもリングは最後まで再生される必要があるため、:activeではなくpointerdownでクラスを付け替えるJSを添えています。アイコンボタンや、すでに塗りつぶしリップルがある画面で二つ目の合図が要る場所に向いています。
.btn::after {
content: "";
position: absolute;
inset: -2px;
border-radius: inherit;
box-shadow: 0 0 0 0 rgba($color, .55);
opacity: 0;
}
.btn.is-ringing::after {
animation: ring $duration $easing forwards;
}
@keyframes ring {
0% { box-shadow: 0 0 0 0 rgba($color, .55); opacity: 1; }
100% { box-shadow: 0 0 0 26px rgba($color, 0); opacity: 0; }
}
02押し込み縮小
押している間scale(.96)まで縮み、box-shadowも同時に浅くなります。離すとバウンスなしで即座に元の大きさへ戻り、touch-action: manipulationでモバイルブラウザの300msタップ遅延とダブルタップ拡大も消します。送信・決済ボタンなど、押した確信が重要なCTAに向いています。
.btn {
transform: scale(1);
transition: transform $duration $easing, box-shadow $duration $easing;
touch-action: manipulation;
}
.btn:active {
transform: scale(.96);
box-shadow: $shadow-press;
}
03hoverなし端末専用の:active
@media (hover: none) and (pointer: coarse)のブロックの中にだけ:activeの背景色を置きます。マウスのある機器はこのブロック自体を読まないため、スマホでタップした後に灰色が張り付く定番のバグがここでは起きません。
@media (hover: none) and (pointer: coarse) {
.btn:active {
background: $stage-ink;
color: $subject-cream;
}
}
04長押しで現れる遅延リアクション
押した瞬間には何も起きず、transition-delayで600ms遅れてからカードが大きくなり影が濃くなります。背景が満ちていく進捗バーではなく、遅延ひとつで短いタップと長押しを見分けます。touch-action: noneでスクロール操作との奪い合いも避けています。
.card {
transition: transform $duration $easing $hold,
box-shadow $duration $easing $hold,
border-color $duration $easing $hold;
touch-action: none;
}
.card:active {
transform: scale(1.06);
box-shadow: $shadow-raised;
border-color: $color;
}
05リスト行のタップハイライト
-webkit-tap-highlight-colorを透明にした上で、行をタップすると独自の背景色が一瞬光り0.2秒で消えます。行の高さは44px以上を確保して指の幅に合わせてあり、設定メニューやチャットルーム一覧のように行単位でタップするリストに向いています。
.list li {
min-height: 44px;
transition: background $duration $easing;
}
.list li:active {
background: rgba($color, .18);
transition: background 0s;
}
06カードタップのスケール+明るさ
カードをタップしている間scale(.98)とfilter: brightness(.94)が同時にかかり、カード全体が押し込まれたように見えます。touch-action: manipulationで商品カードを素早く二度押したときの拡大ズームも防ぎます。
.card {
transform: scale(1);
filter: brightness(1);
transition: transform $duration $easing, filter $duration $easing;
touch-action: manipulation;
}
.card:active {
transform: scale(.98);
filter: brightness(.94);
}
07タップ確認ジッター
タップした直後の140msだけ±2px幅で小さく震え、同時にハートのアイコンが薄い色から濃い色へ塗り替わります。左右に大きく三回揺れるエラー表示とは幅も目的も違う、成功だけを知らせる短い震えです。
.btn:active { animation: jitter $duration $easing; }
.btn:active .btn__icon { color: $error; }
@keyframes jitter {
0% { transform: scale(1) translate(0, 0); }
25% { transform: scale(1.18) translate(-2px, 1px); }
50% { transform: scale(1.1) translate(2px, -1px); }
75% { transform: scale(1.16) translate(-1px, 1px); }
100% { transform: scale(1) translate(0, 0); }
}
08チェックボックスのタップフィードバック
カスタムチェックボックスをタップすると、チェックの線がstroke-dashoffsetで描かれると同時に箱全体がscale(1.08)まで一度だけ弾みます。本物のinput[type=checkbox]は隠し、隣接兄弟セレクタで箱と線をまとめて動かします。
.cb__input:checked ~ .cb__box {
border-color: $color;
background: $color;
transform: scale(1.08);
}
.cb__input:checked ~ .cb__box .cb__check {
stroke-dashoffset: 0;
}
.cb__check {
stroke-dasharray: 24;
stroke-dashoffset: 24;
transition: stroke-dashoffset $duration $easing;
}
09インラインリンクのタップフィードバック
本文中のリンクをタップすると文字の背後に色のブロックが現れ、0.14秒で消えます。既定のタップハイライトを透明にした場所にも、押した合図をこの背景色だけで残します。
.p__link {
background: transparent;
transition: background $duration $easing;
}
.p__link:active {
background: rgba($color, .32);
transition: background 0s;
}
どこで壊れるか — 落とし穴
09のインラインリンクを最初に作ったときは、backgroundのアルファ値を0%から60%までlinearで均等に2秒かけて動かしていました。レンダリングしたあと測定ファイルを開くと、累積面積は「動いた」と出たのに強度の値が閾値ぎりぎりで低く、映像としては動いていても数値上は静止画に近い判定になりました。設定のピクセル閾値は255段階中12で、linearはフレーム数ぶん変化を均等に割るため、総量は変えずに範囲を0〜35%まで狭めて1フレームあたりの変化幅を広げてから、ようやく強度が閾値を超えました。この狭めた範囲を反映した圧縮ファイルの合言葉はe8dzq4mpで、それより前は09が測定上「動かないリンク」のままでした。
アクセシビリティ
9つともprefers-reduced-motion: reduceでは動きを止めますが、押した・チェックされた・ハイライトされたという最後の状態は残るので反応そのものは消えません。キーボード操作のために02・04・06・08には:focus-visibleの専用アウトラインを付けています。
.card:focus-visible {
outline: 3px solid $color;
outline-offset: 3px;
}
残りの五つ(01・03・05・07・09)はボタン・リスト・リンクの既定のフォーカスリングをそのまま使い、上書きしていません。タップする面の大きさもコードで確保していて、05のリスト行はmin-height: 44px、08のチェックボックスは見た目の箱が26pxでもlabel自体にmin-height: 44pxを与えて実際のタップ領域を広げています。ただし09のように文章の中に混じったリンクはWCAG 2.5.8の例外(文中・ブロックテキスト内のリンク)にあたるため、44pxは強制していません。@media (hover: none) and (pointer: coarse)はcaniuseのメディア機能対応表で確認できるとおり、Chrome・Safari・Firefoxとも早くから対応しています。
タップ以外のCSSアニメーションはカテゴリー一覧にまとめてあり、このサイトの作り方は紹介ページに書いています。
FAQ
iOS Safariでも01のリングはタップした瞬間に現れますか?
:activeだけで作っていたら遅れて現れることがあります。MDNの:activeの解説には、iOS Safariは<body>など上位の要素にtouchstartリスナーが一つでもないと:activeのスタイルを遅延なく適用しない、と書かれています。01は最初から:activeではなくpointerdownイベントでクラスを直接付け替えているため、この遅延を避けています。
03のタッチ専用スタイルは、マウスも挿せるタブレットではどうなりますか?
(hover: none) and (pointer: coarse)は「主な入力手段」を基準に判定されます。マウスを挿しても端末自身がタッチを主入力として報告していればタッチ用スタイルのままで、逆にマウスが主入力に切り替わるとこのブロックは静かに効かなくなります。どちらの状態でも見た目が壊れることはありません。
9つを同じフォームに一緒に使ってもいいですか?
使えます。02・06・08はボタン・カード・チェックボックスという別々の要素に効いているので、同じ画面に混ざっても何を押したか迷いません。ただし04のように遅延がある反応と02のように即座に反応するものを同じ要素に重ねると、どちらの反応を待てばいいか分からなくなるため、要素ひとつにつき一つだけに絞ってください。