You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

游戏开发中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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.23 02:15:38