ダブルタップで拡大される
連打するゲーム操作に、ブラウザのズームが割り込まないようにする。touch-action: manipulation はダブルタップ拡大を抑え、通常のピンチ拡大は残します。
推奨コードを表示
.game-button { touch-action: manipulation; }
/* 全体に user-scalable=no を指定しない。 */ダブルタップで画面が拡大する。ドラッグ中にスクロールする。復帰時にキャラが飛ぶ。
そうしたWebゲーム特有の問題を、再現・改善・実装の順に確認できるリファレンスです。
連打するゲーム操作に、ブラウザのズームが割り込まないようにする。touch-action: manipulation はダブルタップ拡大を抑え、通常のピンチ拡大は残します。
.game-button { touch-action: manipulation; }
/* 全体に user-scalable=no を指定しない。 */ゲーム面のスワイプはゲームに使い、メニュー内のスクロールは残す。親ページへスクロールが伝播することも防ぎます。
スクロールが端に達しても、親のページへ伝わりにくくなります。
.game-canvas { touch-action: none; }
.game-controls { touch-action: manipulation; }
.game-scroll { overflow: auto; overscroll-behavior: contain; }戦闘ボタンやCanvasでは不要な選択を抑えつつ、文章・入力欄は選択可能にする。ページ全体を禁止しない。
これは普通の説明文。長押しで選択・コピーできます。
文章は選択可能操作対象だけ選択を無効化しています。
.game-control {
user-select: none;
-webkit-user-select: none;
-webkit-touch-callout: none;
}
/* input, textarea, 説明文には適用しない */指がオブジェクトの外へ出ても追跡し、キャンセル時には操作を終了する。マウスとタッチをPointer Eventsで扱います。
駒の外まで指を動かしてから離しても、入力が終了します。
token.addEventListener('pointerdown', e => {
token.setPointerCapture(e.pointerId);
});
token.addEventListener('pointermove', moveWhenActive);
token.addEventListener('pointerup', stop);
token.addEventListener('pointercancel', stop);
token.addEventListener('lostpointercapture', stop);画面の高さを固定値にせず、動的な表示領域とセーフエリアに合わせる。縦画面・横画面どちらでも余白が必要です。
ブラウザのバーを表示・非表示にして値の変化を比較できます。
<meta name="viewport"
content="width=device-width, initial-scale=1, viewport-fit=cover">
.game-shell { min-height: 100dvh; }
.game-hud {
padding-bottom: env(safe-area-inset-bottom, 0px);
padding-inline:
env(safe-area-inset-left, 0px)
env(safe-area-inset-right, 0px);
}Web Audioは再生前にユーザー操作が必要なことがあります。スタート操作でAudioContextを有効化し、無音状態を見えるようにします。
クリック・タップしてから初めて音を鳴らします。ページ読み込み時には再生しません。
startButton.addEventListener('click', () => {
audioContext ??= new AudioContext();
audioContext.resume(); // ユーザー操作から実行
});非表示中は進行を止め、復帰後はプレイヤーが再開するまで止めておく。RAFの経過時間も上限を決めます。
開始後に別タブへ移動すると自動で一時停止します。戻っても勝手には再開しません。
document.addEventListener('visibilitychange', () => {
if (document.hidden) pauseGame();
});
function tick(now) {
const dt = Math.min(now - previousTime, 50);
updateGame(dt);
requestAnimationFrame(tick);
}CanvasのCSS表示サイズと内部解像度を合わせる。端末DPRを無制限に採用せず、ゲームの画風・負荷に合わせて上限を決めます。
ドット絵の場合はアンチエイリアスの有無も含め、実ゲーム画面の見た目で判断します。
const rect = canvas.getBoundingClientRect();
const dpr = Math.min(devicePixelRatio || 1, 2);
canvas.width = Math.round(rect.width * dpr);
canvas.height = Math.round(rect.height * dpr);
const ctx = canvas.getContext('2d');
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
ctx.imageSmoothingEnabled = false; // ドット絵の場合全画面はユーザー操作から要求し、未対応や拒否を扱う。スマホを回した際は、固定サイズではなく現在の幅・高さを読んで配置を更新します。
全画面の可否はブラウザによって異なります。画面方向の固定は要求しません。
try {
await gameElement.requestFullscreen(); // ユーザー操作直後
} catch {
showNormalLayout(); // 非対応・拒否でもプレイ可能に
}
window.addEventListener('resize', relayout);ゲーム画面のスクリーンショットではなく、共有専用の画像を生成。ネイティブ共有・PNGコピー・保存を機能に応じて提供します。
ズーム、テキスト選択、スクロールは通常のWebページでは重要な機能です。ゲーム操作面と説明・入力欄を分けることで、操作ミスを抑えながら使いやすさを維持します。