「PCでは動くのに、スマホだけ click が反応しない」——この問題はいくつかの原因に分けられます。結論から言うと、正しい要素を使えば click はスマホでも問題なく動きます。原因を一つずつ確認していきましょう。
<button> や <a> にすること。click はスマホでも動き、昔の300ms遅延も今はほぼありません。touchstart を足すと二重発火するので不要。デバイス判定が必要なら、UA判定ではなく matchMedia を使います。まず確認:clickは今のスマホでも動く
かつてモバイルブラウザには「クリックが約300ms遅れる」問題がありましたが、<meta name="viewport" content="width=device-width"> を指定していれば現在はこの遅延はほぼ発生しません。そのため、わざわざ touchstart を使う必要はなく、click で十分です。
const button = document.getElementById("my-button");
button.addEventListener("click", () => {
console.log("クリックされました"); // スマホでもPCでも動く
});
原因1:クリックできない要素を使っている
<div> や <span> など本来クリック対象でない要素に click を付けると、特にiOSで反応しないことがあります。まずは正しい要素を使うのが解決の近道です。
- ボタンなら
<button>、リンクなら<a>を使う(キーボード操作・読み上げにも対応) - どうしても
<div>を使うなら、role="button"とtabindex="0"を付け、CSSでcursor: pointer;を指定する(iOSで委譲clickを発火させるため)
原因2:CSSでクリックが無効になっている
意図しないCSSがクリックを妨げていることもあります。よくある3つを確認しましょう。
pointer-events: none;が付いていると、クリックが完全に無効になる- 別の要素が上に重なっている(透明なオーバーレイ・
z-index)と、タップがそちらに吸われる - タップ領域が小さすぎると指で押しにくい。目安として44px四方以上を確保する
#my-button {
pointer-events: auto; /* none になっていないか確認 */
position: relative;
z-index: 1; /* 重なりで隠れていないか確認 */
min-width: 44px;
min-height: 44px; /* 押しやすいタップ領域 */
}
原因3:touchstartとclickの二重発火
touchstart と click の両方に同じ処理を書くと、スマホではtouchstart → click の順で2回実行されてしまいます。これは「動かない」とは逆の、二重に動く不具合です。基本は click だけにしましょう。// 両方に処理を書くと、スマホで2回実行される
el.addEventListener("touchstart", doSomething);
el.addEventListener("click", doSomething); // ← 重複
el.addEventListener("click", doSomething); // これだけでOK
どうしてもタッチを早く拾いたい特殊なケースだけ touchend を使い、その際は event.preventDefault() で後続の click を抑制します。ただし touchstart での preventDefault() はスクロールやズームまで止めてしまうので安易に使わないでください。
動くデモ:あなたの端末で何が発火する?
↓ ボタンをクリック/タップすると、発火したイベントが順に表示されます。PCでは click 系だけ、スマホでは touchstart も発火するのが分かります(だから両方に処理を書くと2回動くわけです):
スマホで touchstart と click の両方に同じ処理を書くと、このリストのとおり2つとも発火して2回実行されます。基本は click だけにすれば、PCでもスマホでも1回だけ動きます。
デバイス判定はUAではなくmatchMediaで
「スマホかどうかで処理を分けたい」とき、navigator.userAgent での判定(UA判定)は古い・不正確です(iPadがPCとして判定されるなど)。現在は入力方式そのものを調べる matchMedia を使います。
// タッチ主体のデバイスか(ホバー不可+粗いポインタ)
const isTouch = window.matchMedia("(hover: none) and (pointer: coarse)").matches;
if (isTouch) {
console.log("タッチデバイス");
} else {
console.log("マウスデバイス");
}
とはいえ、多くの場合は分岐せず click 一本で両対応できます。判定の詳しい方法はスマホ向けのクリックイベントを設定する方法、多数の要素を効率的に扱うならEvent Delegationでイベントを管理する方法も参考になります。
それでも遅延が気になるなら touch-action
ダブルタップでのズーム判定などにより、ごくわずかな遅延が気になる場合は、CSSの touch-action: manipulation; を指定すると改善します。(古い FastClick ライブラリはもう不要・非推奨です)。
button, a, .tappable {
touch-action: manipulation;
}
pointerdown / pointermove / pointerup を使うと、マウス・タッチ・ペンを1つのコードでまとめて扱えます(touchstart と mousedown を別々に書く必要がありません)。ただし単純なボタンのクリックなら click で十分です。よくある質問(FAQ)
<div> などであること、CSSの pointer-events: none や要素の重なり、タップ領域の小ささが原因です。<button> / <a> を使い、CSSを見直すと解決します。<div> 等の非インタラクティブ要素では発火しないことがあります。<button> / <a> を使うのが基本で、どうしても <div> なら cursor: pointer を付けると委譲clickが発火します。click だけで十分です。window.matchMedia("(hover: none) and (pointer: coarse)").matches でタッチデバイスを判定します。UA(navigator.userAgent)での判定は不正確なので避けてください。まとめ
スマホで click が動かない問題は、クリックできない要素を使っている・CSSで無効化されている・要素が重なっていることが主な原因です。まずは <button> / <a> を使い、CSSを見直しましょう。
click はスマホでも動き、300ms遅延も今はほぼありません。touchstart を足すと二重発火するので不要です。デバイス判定はUAではなく matchMedia を使いましょう。
