【JavaScript】スマホでクリックイベントが動作しない場合の原因と解決方法

「PCでは動くのに、スマホだけ click が反応しない」——この問題はいくつかの原因に分けられます。結論から言うと、正しい要素を使えば click はスマホでも問題なく動きます。原因を一つずつ確認していきましょう。

この記事の結論:まずクリック対象を <button><a> にすること。click はスマホでも動き、昔の300ms遅延も今はほぼありませんtouchstart を足すと二重発火するので不要。デバイス判定が必要なら、UA判定ではなく matchMedia を使います。
スポンサーリンク

まず確認:clickは今のスマホでも動く

かつてモバイルブラウザには「クリックが約300ms遅れる」問題がありましたが、<meta name="viewport" content="width=device-width"> を指定していれば現在はこの遅延はほぼ発生しません。そのため、わざわざ touchstart を使う必要はなく、click で十分です。

JavaScript: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四方以上を確保する
CSS:クリックを妨げる設定を確認
#my-button {
  pointer-events: auto; /* none になっていないか確認 */
  position: relative;
  z-index: 1;           /* 重なりで隠れていないか確認 */
  min-width: 44px;
  min-height: 44px;     /* 押しやすいタップ領域 */
}

原因3:touchstartとclickの二重発火

やりがちなアンチパターン:「スマホ対応のために」touchstartclick の両方に同じ処理を書くと、スマホではtouchstart → click の順で2回実行されてしまいます。これは「動かない」とは逆の、二重に動く不具合です。基本は click だけにしましょう。
JavaScript:NG(二重発火する)
// 両方に処理を書くと、スマホで2回実行される
el.addEventListener("touchstart", doSomething);
el.addEventListener("click", doSomething); // ← 重複
JavaScript:OK(clickだけで十分)
el.addEventListener("click", doSomething); // これだけでOK

どうしてもタッチを早く拾いたい特殊なケースだけ touchend を使い、その際は event.preventDefault() で後続の click を抑制します。ただし touchstart での preventDefault()スクロールやズームまで止めてしまうので安易に使わないでください。

動くデモ:あなたの端末で何が発火する?

↓ ボタンをクリック/タップすると、発火したイベントが順に表示されます。PCでは click 系だけ、スマホでは touchstart も発火するのが分かります(だから両方に処理を書くと2回動くわけです):


    スマホで touchstartclick の両方に同じ処理を書くと、このリストのとおり2つとも発火して2回実行されます。基本は click だけにすれば、PCでもスマホでも1回だけ動きます。

    デバイス判定はUAではなくmatchMediaで

    「スマホかどうかで処理を分けたい」とき、navigator.userAgent での判定(UA判定)は古い・不正確です(iPadがPCとして判定されるなど)。現在は入力方式そのものを調べる matchMedia を使います。

    JavaScript: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 ライブラリはもう不要・非推奨です)。

    CSS:touch-actionで遅延を抑える
    button, a, .tappable {
      touch-action: manipulation;
    }
    マウス・タッチ・ペンを統一するなら Pointer Events:ドラッグやスワイプなどclick では足りない高度な操作を扱うときは、pointerdown / pointermove / pointerup を使うと、マウス・タッチ・ペンを1つのコードでまとめて扱えます(touchstartmousedown を別々に書く必要がありません)。ただし単純なボタンのクリックなら click で十分です。

    よくある質問(FAQ)

    Qスマホでclickイベントが反応しない原因は?
    A多くはクリック対象が <div> などであること、CSSの pointer-events: none や要素の重なり、タップ領域の小ささが原因です。<button> / <a> を使い、CSSを見直すと解決します。
    QiOSのSafariでclickが発火しないときは?
    A<div> 等の非インタラクティブ要素では発火しないことがあります。<button> / <a> を使うのが基本で、どうしても <div> なら cursor: pointer を付けると委譲clickが発火します。
    Qtouchstartとclickの両方を設定すべき?
    Aいいえ。両方に同じ処理を書くと、スマホで2回実行(二重発火)されます。基本は click だけで十分です。
    QモバイルとPCで処理を分けるには?
    Awindow.matchMedia("(hover: none) and (pointer: coarse)").matches でタッチデバイスを判定します。UA(navigator.userAgent)での判定は不正確なので避けてください。

    まとめ

    スマホで click が動かない問題は、クリックできない要素を使っている・CSSで無効化されている・要素が重なっていることが主な原因です。まずは <button> / <a> を使い、CSSを見直しましょう。

    click はスマホでも動き、300ms遅延も今はほぼありませんtouchstart を足すと二重発火するので不要です。デバイス判定はUAではなく matchMedia を使いましょう。