游戏开发中document.querySelectorAll('.line')返回空NodeList问题排查
赛车小游戏道路线条移动问题排查与解决
问题描述
开发赛车小游戏时,定义的movesLines函数用于重复移动道路线条以模拟车辆前进视觉效果,但调用document.querySelectorAll('.line')返回空NodeList,导致线条无法移动。完整项目代码如下:
HTML代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Game</title> <link rel="stylesheet" href="./style.css"> </head> <body> <div class="gameBoard"> <div class="score"></div> <div class="popup"> Click or Press any arrow to start!! </div> <div class="gameArea"></div> </div> <script src="./script.js"></script> </body> </html>
CSS代码
/* SPACING SYSTEM (px) 2 / 4 / 8 / 12 / 16 / 24 / 32 / 48 / 64 / 80 / 96 / 128 FONT SIZE SYSTEM (px) 10 / 12 / 14 / 16 / 18 / 20 / 24 / 30 / 36 / 44 / 52 / 62 / 74 / 86 / 98 */ * { margin:0; padding:0; box-sizing: border-box; } html{ font-size: 62.5%; } .gameBoard { display: flex; background-color: lightgreen; overflow-x: hidden; overflow-y: hidden; } .score { position: absolute; top: 1rem; left: 1rem; width: 10rem; height: 5rem; border: 2px solid green; } .popup { position: absolute; top: 50%; left: 50%; transform: translateX(-50%); color: black; font-weight: bold; padding: 2rem; border-radius: 11px; background-color: #777; z-index: 999; } .gameArea { position: relative; width: 40vw; height: 100vh; background-color: #333; margin-left: auto; margin-right: auto; } .car { position: absolute; bottom: 1rem; width: 8rem; height: 10rem; background-color: red; } .enemy { position: absolute; bottom: 1rem; width: 8rem; height: 10rem; background-color: blue; } .line { position: absolute; width: 1rem; height: 10rem; background-color: white; left: 50%; transform: translateX(-50%); } .hide { display: none; }
JavaScript代码
'use strict'; const score = document.querySelector('.score'); const popup = document.querySelector('.popup'); const gameArea = document.querySelector('.gameArea'); const gameBoard = document.querySelector('.gameBoard'); let player = { speed: 5, start: false }; let keys = { ArrowUp: false, ArrowDown: false, ArrowLeft: false, ArrowRight: false, }; gameBoard.addEventListener('click', start); document.addEventListener('keydown', keyDown); document.addEventListener('keyup', keyUp); function keyDown(e) { e.preventDefault(); keys[e.key] = true; // console.log(keys); } function keyUp(e) { e.preventDefault(); keys[e.key] = false; // console.log(keys); } function gamePlay() { console.log('game started'); let car = document.querySelector('.car'); let carD = car.getBoundingClientRect(); let road = gameArea.getBoundingClientRect(); if (player.start) { if (keys.ArrowDown && player.y < road.height - carD.height) player.y += player.speed; if (keys.ArrowUp && player.y > road.height * 0.3) player.y -= player.speed; if (keys.ArrowLeft && player.x > 0) player.x -= player.speed; if (keys.ArrowRight && player.x < road.width - carD.width) player.x += player.speed; car.style.top = player.y + 'px'; car.style.left = player.x + 'px'; window.requestAnimationFrame(gamePlay); } } function movesLines() { let lines = document.querySelectorAll('.line'); console.log(lines); lines.forEach(function (item) { item.y += player.speed; item.style.top = item.y + 'rem'; }); } function start() { if (player.start) return; movesLines(); popup.classList.add('hide'); player.start = true; window.requestAnimationFrame(gamePlay); for (let i = 0; i < 6; i++) { let roadLine = document.createElement('div'); roadLine.setAttribute('class', 'line'); roadLine.y = i * 15; roadLine.style.top = roadLine.y + 'rem'; gameArea.appendChild(roadLine); } let car = document.createElement('div'); car.setAttribute('class', 'car'); gameArea.appendChild(car); player.x = car.offsetLeft; player.y = car.offsetTop; // console.log('top position' + car.offsetTop); // console.log('Left position' + car.offsetLeft); }
原因分析
- 执行顺序错误:
start函数中先调用movesLines(),之后才通过循环创建并添加.line元素到DOM,导致调用querySelectorAll('.line')时,DOM中还没有任何对应元素,返回空NodeList。 - 未加入动画循环:即使修复顺序,
movesLines只执行一次,不会随游戏循环持续触发,线条无法保持移动效果。
解决方案
1. 调整代码执行顺序
在start函数中,先创建所有道路线条,再启动游戏循环,确保movesLines执行时DOM中已存在.line元素。
2. 将movesLines加入游戏动画循环
在gamePlay函数中每次调用requestAnimationFrame前执行movesLines,保证线条持续移动。
3. 添加线条重置逻辑
当线条超出屏幕底部时,重置到顶部,实现无限循环效果。
修改后的JavaScript关键代码如下:
function gamePlay() { console.log('game started'); let car = document.querySelector('.car'); let carD = car.getBoundingClientRect(); let road = gameArea.getBoundingClientRect(); if (player.start) { // 每次动画帧执行线条移动 movesLines(); if (keys.ArrowDown && player.y < road.height - carD.height) player.y += player.speed; if (keys.ArrowUp && player.y > road.height * 0.3) player.y -= player.speed; if (keys.ArrowLeft && player.x > 0) player.x -= player.speed; if (keys.ArrowRight && player.x < road.width - carD.width) player.x += player.speed; car.style.top = player.y + 'px'; car.style.left = player.x + 'px'; window.requestAnimationFrame(gamePlay); } } function movesLines() { let lines = document.querySelectorAll('.line'); lines.forEach(function (item) { item.y += player.speed; // 线条超出屏幕底部时重置到顶部 if(item.y > gameArea.offsetHeight / 10) { item.y = -10; } item.style.top = item.y + 'rem'; }); } function start() { if (player.start) return; popup.classList.add('hide'); player.start = true; // 先创建道路线条 for (let i = 0; i < 6; i++) { let roadLine = document.createElement('div'); roadLine.setAttribute('class', 'line'); roadLine.y = i * 15; roadLine.style.top = roadLine.y + 'rem'; gameArea.appendChild(roadLine); } let car = document.createElement('div'); car.setAttribute('class', 'car'); gameArea.appendChild(car); player.x = car.offsetLeft; player.y = car.offsetTop; // 启动游戏循环 window.requestAnimationFrame(gamePlay); }
内容的提问来源于stack exchange,提问作者Mayank Goyal
相关产品推荐
相关产品推荐

