マガジン

スマホで触ると画面がスクロールしてしまうゲームの直し方

遊びをつくる

執筆にClaudeを使用しています。

キーボード、ゲームパッド、タッチ画面を同じ机に置いた操作方法の比較。
記事のテーマをもとに制作したAI生成イメージ。実際のゲーム画面ではありません。

スマホでブラウザゲームを開いたとき、画面を指でタップしたりスワイプしたりするとゲームではなくページ全体がスクロールしてしまう——このトラブルは、PCで動作確認を済ませたゲームをスマホで試したときに最も起きやすい問題のひとつです。ゲームとしての操作を受け取るべき場面で、ブラウザが「ページを動かす操作」と解釈してしまうのが原因です。 直し方はいくつかのアプローチに分かれますが、どれを使うかは「どの要素でどんな操作を受け取りたいか」によって変わります。この記事では、原因の仕組みを短く整理したうえで、状況別に対処法と判断基準を示します。

この記事の目次
  1. なぜスクロールが起きるのか
  2. CSSだけで解決できるケース
  3. JavaScriptで `preventDefault()` を正しく呼ぶ
  4. `<meta>` タグによるズーム制御との違い
  5. ゲーム全体をスクロール不可にしたいとき
  6. 確認と検証の手順
  7. 次の行動
  8. 参考資料

なぜスクロールが起きるのか

スマートフォンのブラウザは、タッチ操作に対してデフォルトで「スクロール・ピンチズーム」などの画面操作を割り当てています。JavaScriptの touchstart / touchmove イベントは発火しますが、ブラウザはその処理と並行して自分自身のスクロール動作も実行しようとします。

ゲームのコードが touchmove を受け取っていても、preventDefault() を呼ばない限りブラウザはスクロールを止めません。さらに近年のブラウザはパフォーマンス上の理由から、タッチイベントリスナーをデフォルトで「パッシブ(passive)」として扱うようになっています。パッシブなリスナーの中では preventDefault() が無効になるため、「書いたのに効かない」という状況が起きます。これがこの問題の中核にある仕組みです。

CSSだけで解決できるケース

ゲームのプレイ領域全体が一つの要素(<canvas> や特定の <div>)に収まっている場合、CSSの touch-action プロパティが最もシンプルな解決策です。

css
canvas {
  touch-action: none;
}

touch-action: none を指定した要素は、ブラウザのデフォルトタッチ操作(スクロール・ズーム)をすべて無効にします。JavaScriptのコードを変えずに済むため、既存のゲームへの影響が小さいのが利点です。

判断基準として、次の条件がそろうときはCSSだけで対応できます。

  • ゲームの入力を受け取る要素が明確に一つに絞れる
  • その要素の外側(余白・ヘッダーなど)はスクロールさせて構わない
  • ページ全体のスクロールを完全に止める必要がない

逆に、ゲームが <body> 全体に広がっていたり、複数の要素にタッチ処理が分散していたりする場合は、後述のJavaScriptによる対処が必要になります。

JavaScriptで `preventDefault()` を正しく呼ぶ

CSS対応だけでは不十分なとき、またはすでに touchmove イベントで処理を書いているときは、イベントリスナーを「ノンパッシブ」として登録し直す必要があります。

js
element.addEventListener('touchmove', function(e) {
  e.preventDefault();
  // ゲームの処理
}, { passive: false });

{ passive: false } を第三引数に渡すことで、ブラウザに「このリスナーは preventDefault() を呼ぶ可能性がある」と伝えます。これを書かないと、ブラウザはリスナーをパッシブとみなし、preventDefault() を呼んでもスクロールが止まりません。

touchstart でも同様です。ゲーム開始時の入力を受け取るイベントに { passive: false } が抜けていると、最初のタッチでページが動いてしまいます。

よくある失敗パターン

  • addEventListener('touchmove', handler) と書いていて第三引数がない→ブラウザがパッシブと判断し preventDefault() が無視される
  • e.preventDefault() はあるが touchstart にも touchmove にも両方必要な場面で片方しか書いていない
  • canvas ではなく document や window にリスナーを付けていて、touch-action: none の範囲外のタッチが拾えていない

`<meta>` タグによるズーム制御との違い

<meta name="viewport" content="user-scalable=no"> を使うとピンチズームを止めることはできますが、スクロールは止まりません。「ズームだけ問題」と「スクロールも問題」は別の現象なので、viewportタグだけで対応しようとするとスクロールは残ります。また、ズーム禁止はアクセシビリティ上の懸念があり、ブラウザ側で無視されるケースも増えています。この方法に頼るより、touch-action とイベントリスナーの組み合わせで対処する方が確実です。

ゲーム全体をスクロール不可にしたいとき

ゲームがページ全体を占有する設計(いわゆるフルスクリーンゲーム)の場合、<body> 自体のスクロールを止めるのが最も確実です。

css
html, body {
  overflow: hidden;
  height: 100%;
  touch-action: none;
}

これに加えて、ゲームのメインキャンバスや操作領域にも touch-action: none を重ねて指定しておくと、ブラウザ間の挙動の差を減らせます。

ただし、この方法はページ内に「スクロールさせたいUI(例:スコアランキングの一覧)」があるときには使えません。その場合は、スクロールさせたい領域だけに overflow-y: auto を指定し、ゲームキャンバスとUIを明確に分離する構造にする必要があります。

確認と検証の手順

修正後は必ず実機で確認します。ブラウザのデベロッパーツールにあるデバイスシミュレーターはタッチイベントの挙動が実機と異なることがあるため、スクロール問題の確認には不向きです。

  1. スマートフォンの実機(iOS Safari・Android Chromeの両方)でゲームを開く
  2. ゲームの操作エリアを指でゆっくりスワイプし、ページが動かないことを確認する
  3. 二本指でピンチし、ズームが起きないことを確認する
  4. ゲームエリア外(余白・ヘッダーなど)は意図どおりにスクロールするか確認する

iOSとAndroidはタッチイベントの細かい挙動が異なるため、どちらか一方だけでなく両方で確認する習慣をつけておくと、後から別の端末で問題が出るリスクを減らせます。

次の行動

まずゲームのプレイ領域に touch-action: none をCSSで追加し、実機で動作を確認してみてください。それでもスクロールが残る場合は、touchmove と touchstart のイベントリスナーに { passive: false } が付いているかを確認し、e.preventDefault() を呼んでいるかを見直してください。修正したゲームはHTMLファイルやZIPにまとめてアソバース(https://aso-verse.com)に投稿し、スマホからの動作を公開環境で確かめることができます。

参考資料

  • MDN Web Docs「EventTarget.addEventListener()」
  • MDN Web Docs「touch-action」
  • MDN Web Docs「Touch events」
  • Web Fundamentals「Passive event listeners」(Google Developers)