iOS端touchstart触发HTML Canvas卡顿问题求修复方案
修复iOS上Canvas游戏点击卡顿问题
问题描述
基于requestAnimationFrame实现的HTML Canvas小游戏,在iPhone的iOS系统中点击Canvas时会出现轻微卡顿、抖动或冻结现象,PC和安卓设备运行正常。已尝试在touchstart事件中调用preventDefault()与event.stopPropagation(),但问题未解决。
相关代码
HTML
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Game</title> <style> #gameCanvas { border: 1px solid #000; } </style> </head> <body> <div class="game-container"> <canvas id="gameCanvas"></canvas> </div> <script type="module" src="/src/main.js"></script> </body> </html>
JavaScript
const gameCanvas = document.getElementById('gameCanvas'); let context = gameCanvas.getContext('2d'); let requestId = null; let lastTime = 0; let pickupTimer = 0; let obstacleTimer = 0; const chopper = { x: 50, y: 150, width: 50, height: 20, dy: 0, }; let pickups = new Set(); let obstacles = new Set(); const pickupWidth = 10; const pickupHeight = 10; const obstacleWidth = 30; const obstacleHeight = 30; let score = 0; let gameRunning = true; let gameSpeed = -0.5; let isFlapping = false; function spawnPickup(delta) { pickupTimer += delta; if (pickupTimer >= 0.5) { // 0.5秒间隔 const x = context.canvas.width; const y = Math.random() * (context.canvas.height - pickupHeight); pickups.add({ x, y, width: pickupWidth, height: pickupHeight, value: 10, dx: gameSpeed }); pickupTimer = 0; } } function spawnObstacle(delta) { obstacleTimer += delta; if(obstacleTimer >= 0.6) { // 0.6秒间隔 const x = context.canvas.width; const y = Math.random() * (context.canvas.height - obstacleHeight); obstacles.add({ x, y, width: obstacleWidth, height: obstacleHeight, dx: gameSpeed}); obstacleTimer = 0; } } function drawPickup(pickup) { context.fillStyle = 'gold'; context.fillRect(pickup.x, pickup.y, pickup.width, pickup.height); } function drawObstacle(obstacle) { context.fillStyle = 'red'; context.fillRect(obstacle.x, obstacle.y, obstacle.width, obstacle.height); } function drawChopper(deltaTime) { if (isFlapping) { chopper.dy = -300; // 调整为毫秒级速度 isFlapping = false; } chopper.dy += 900 * deltaTime; // 重力调整为秒级加速度 chopper.y += chopper.dy * deltaTime; if (chopper.y + chopper.height > context.canvas.height || chopper.y < 0) { endGame(); } context.fillStyle = 'green'; context.fillRect(chopper.x, chopper.y, chopper.width, chopper.height); } function checkCollision(rect1, rect2) { return rect1.x < rect2.x + rect2.width && rect1.x + rect1.width > rect2.x && rect1.y < rect2.y + rect2.height && rect1.y + rect1.height > rect2.y; } function handlePickupOverlap(deltaTime) { pickups.forEach(pickup => { pickup.x += pickup.dx * 1000 * deltaTime; // 转成毫秒级位移 drawPickup(pickup); if (checkCollision(chopper, pickup)) { score += pickup.value; pickups.delete(pickup); } else if (pickup.x + pickup.width < 0) { pickups.delete(pickup); } }); } function handleObstacleOverlap(deltaTime) { obstacles.forEach(obstacle => { obstacle.x += obstacle.dx * 1000 * deltaTime; // 转成毫秒级位移 drawObstacle(obstacle); // if (checkCollision(chopper, obstacle)) { // endGame(); // } else if (obstacle.x + obstacle.width < 0) { // obstacles.delete(obstacle); // } }); } function updateGame(timestamp = 0) { const deltaTime = (timestamp - lastTime) / 1000; // 将毫秒转为秒 lastTime = timestamp; if (gameRunning) { context.clearRect(0, 0, context.canvas.width, context.canvas.height); drawChopper(deltaTime); spawnPickup(deltaTime) spawnObstacle(deltaTime) handlePickupOverlap(deltaTime); handleObstacleOverlap(deltaTime); requestId = requestAnimationFrame(updateGame); } } function endGame() { // gameRunning = false; // 测试用重置 chopper.y = 140 chopper.dy = 0; } function setupCanvas() { context.canvas.width = 300; context.canvas.height = 600; lastTime = performance.now(); // 初始化lastTime为当前时间 requestId = requestAnimationFrame(updateGame); // 将触摸事件绑定到Canvas元素 gameCanvas.addEventListener('touchstart', (event) => { event.preventDefault(); event.stopPropagation(); isFlapping = true; }); // 阻止touchend的默认行为 gameCanvas.addEventListener('touchend', (event) => { event.preventDefault(); event.stopPropagation(); }); } setupCanvas();
修复方案
1. 修正deltaTime单位错误
原代码中deltaTime直接使用毫秒值进行计算,但逻辑里默认按秒处理,导致数值偏差过大,iOS的时间精度差异会放大这个问题。将deltaTime转为秒单位,同时调整所有速度、加速度参数为对应量级:
const deltaTime = (timestamp - lastTime) / 1000;- 调整直升机的速度、重力,以及障碍物/道具的移动速度为毫秒级或对应秒级参数
2. 优化触摸事件绑定
- 将触摸事件绑定到Canvas元素而非window,减少事件冒泡的层级和干扰
- 添加
touchend事件的默认行为阻止,避免iOS浏览器触发后续的滚动、缩放等操作
3. 启用Canvas硬件加速
在CSS中给Canvas添加以下属性,强制iOS启用硬件加速,提升渲染性能:
#gameCanvas { transform: translateZ(0); backface-visibility: hidden; will-change: transform; }
4. 初始化lastTime值
在setupCanvas中用performance.now()初始化lastTime,避免第一帧的deltaTime异常过大导致的跳变。
内容的提问来源于stack exchange,提问作者watupbrah
相关产品推荐
相关产品推荐

