JS实现别踩白块小游戏:从需求到代码的全流程解析
“别踩白块”(又称“钢琴块”)是一款经典的休闲类小游戏,玩家需在方块下落过程中点击黑色方块,避开白色方块。通过JavaScript实现该游戏,可深入理解DOM操作、事件处理、动画原理和性能优化等前端核心技术。本文将从需求分析、技术选型、代码实现到最佳实践,完整讲解游戏开发过程,适合前端入门者学习DOM交互与动画逻辑。
目录#
游戏需求分析#
核心规则#
- 界面组成:由多行多列的方块组成,每行仅1个黑块,其余为白块。
- 交互逻辑:玩家点击黑块得分,点击白块或黑块“漏过”(超出容器底部)则游戏结束。
- 动态效果:方块自动从顶部下落,速度随得分递增。
- 目标:尽可能获得更高分数,考验反应速度。
技术选型#
- HTML:构建游戏界面结构(容器、得分、按钮、方块容器)。
- CSS:实现视觉样式(布局、颜色、响应式适配)。
- JavaScript:处理核心逻辑(方块生成、事件监听、动画、游戏状态管理)。
(注:本文使用原生JS,不依赖框架,聚焦基础DOM与动画原理。)
实现步骤#
1. HTML结构设计#
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>别踩白块</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="game-container">
<h1>别踩白块</h1>
<div class="score">得分:<span id="score">0</span></div>
<button id="start-btn">开始游戏</button>
<div id="block-container"></div>
</div>
<script src="script.js"></script>
</body>
</html>- 语义化结构:用
<div class="game-container">包裹游戏核心元素,<div id="block-container">承载下落的方块。 - 可维护性:得分、按钮、方块容器分离,便于后续逻辑绑定。
2. CSS样式实现#
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
background: #f5f5f5;
font-family: Arial, sans-serif;
}
.game-container {
width: 90%;
max-width: 400px;
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
padding: 20px;
text-align: center;
position: relative;
overflow: hidden;
}
#block-container {
width: 100%;
height: 400px;
position: relative;
overflow: hidden; /* 隐藏超出容器的方块 */
}
.block-row {
display: flex;
position: absolute;
top: 0;
left: 0;
width: 100%;
}
.block {
flex: 1;
height: 80px;
border: 1px solid #ccc;
background: #fff;
cursor: pointer;
}
.block.black {
background: #000;
}- 布局技巧:
- 方块行(
.block-row)用flex布局,使方块自动平分宽度。 - 方块容器(
#block-container)设为overflow: hidden,隐藏超出底部的方块。
- 方块行(
- 响应式:通过
max-width和百分比宽度适配不同设备,后续可结合媒体查询优化小屏幕体验。
3. JS核心逻辑#
(1)游戏初始化#
定义游戏配置(行数、列数、速度、得分等),并初始化界面:
const config = {
cols: 4, // 列数
blockHeight: 80, // 方块高度(px)
speed: 5, // 初始下落速度(px/帧)
score: 0,
isPlaying: false,
timer: null // 动画定时器
};
const blockContainer = document.getElementById('block-container');
const scoreElement = document.getElementById('score');
const startBtn = document.getElementById('start-btn');
// 初始化游戏
function initGame() {
config.score = 0;
config.isPlaying = true;
scoreElement.textContent = config.score;
blockContainer.innerHTML = ''; // 清空旧方块
// 生成初始行(从顶部堆叠)
for (let i = 0; i < 5; i++) { // 初始5行
createBlockRow(i);
}
startFalling(); // 启动下落动画
}(2)方块生成与渲染#
随机生成一行方块(含1个黑块),并添加到容器:
// 创建一行方块(rowIndex:行的初始垂直位置)
function createBlockRow(rowIndex) {
const row = document.createElement('div');
row.className = 'block-row';
// 初始位置:top = rowIndex * 方块高度(从顶部堆叠)
row.style.top = `${rowIndex * config.blockHeight}px`;
// 随机选择一个黑块的列索引
const blackCol = Math.floor(Math.random() * config.cols);
for (let col = 0; col < config.cols; col++) {
const block = document.createElement('div');
block.className = 'block';
if (col === blackCol) {
block.classList.add('black');
}
// 绑定点击事件
block.addEventListener('click', () => handleBlockClick(block, row));
row.appendChild(block);
}
blockContainer.appendChild(row);
}- 随机黑块:通过
Math.random()生成0~(cols-1)的随机数,标记该列为黑块。 - 事件绑定:为每个方块绑定点击事件,点击时调用
handleBlockClick处理逻辑。
(3)事件监听与交互#
点击黑块得分、移除方块;点击白块或游戏结束时终止游戏:
// 处理方块点击
function handleBlockClick(block, row) {
if (!config.isPlaying) return; // 游戏已结束则不响应
if (block.classList.contains('black')) {
// 点击黑块:得分+1,移除该行,生成新行
config.score++;
scoreElement.textContent = config.score;
row.remove(); // 移除当前行
// 生成新行(从顶部上方进入)
createBlockRow(-1); // rowIndex=-1 → 初始top=-80px
// 难度递增(每得10分,速度+1)
if (config.score % 10 === 0) {
config.speed += 1;
restartFalling(); // 重启动画以应用新速度
}
} else {
// 点击白块:游戏结束
gameOver();
}
}(4)自动下落与游戏结束#
用requestAnimationFrame实现平滑下落,检测方块是否“漏过”底部:
// 启动下落动画
function startFalling() {
let position = 0; // 总下落距离(px)
config.timer = requestAnimationFrame(function fall() {
if (!config.isPlaying) return; // 游戏结束则停止
position += config.speed;
// 所有行向下移动(通过transform实现,性能更优)
const rows = document.querySelectorAll('.block-row');
rows.forEach(row => {
// 行的当前垂直位置 = 初始top + 总下落距离
const currentTop = parseInt(row.style.top) + position;
// 检测是否超出容器底部
if (currentTop + config.blockHeight > blockContainer.clientHeight) {
// 检查该行是否有黑块
const hasBlack = Array.from(row.children).some(
block => block.classList.contains('black')
);
if (hasBlack) {
gameOver(); // 漏过黑块,游戏结束
} else {
row.remove(); // 漏过白块,移除该行
}
}
});
config.timer = requestAnimationFrame(fall);
});
}
// 重启动画(调整速度后调用)
function restartFalling() {
cancelAnimationFrame(config.timer); // 停止旧动画
startFalling(); // 启动新动画
}
// 游戏结束
function gameOver() {
config.isPlaying = false;
cancelAnimationFrame(config.timer); // 停止动画
alert(`游戏结束!得分:${config.score}`);
startBtn.disabled = false; // 重新启用开始按钮
}(5)开始按钮绑定#
startBtn.addEventListener('click', () => {
startBtn.disabled = true; // 防止重复点击
initGame(); // 初始化并启动游戏
});最佳实践#
1. 性能优化#
- 动画选择:用
requestAnimationFrame代替setInterval,与浏览器刷新率同步(通常60FPS),避免卡顿。 - DOM操作优化:
- 批量操作:生成一行方块后再添加到容器(如
createBlockRow中先构建行,再appendChild)。 - 避免重排:用
transform: translateY代替top属性(本文示例为简化用top,实际可改为transform,减少重排)。
- 批量操作:生成一行方块后再添加到容器(如
2. 代码模块化#
将游戏逻辑拆分为独立函数(initGame、createBlockRow、handleBlockClick等),降低耦合度:
- 新增功能(如音效、关卡)可通过扩展函数实现。
- 维护时只需修改对应函数,不影响其他逻辑。
3. 响应式与移动端适配#
- 触摸支持:为方块添加
touchstart事件,兼容移动端点击:block.addEventListener('click', () => handleBlockClick(block, row)); block.addEventListener('touchstart', () => handleBlockClick(block, row)); - 自适应布局:通过媒体查询调整小屏幕的列数和方块大小:
@media (max-width: 360px) { .block { height: 60px; /* 减小方块高度 */ } .game-container { max-width: 320px; } .block-row { /* 小屏幕列数改为3 */ /* 需配合JS动态修改cols参数 */ } }
常见问题及解决方案#
1. 方块点击无响应#
- 原因:事件监听未正确绑定,或方块被其他元素遮挡。
- 解决:检查
createBlockRow中是否为每个方块添加了addEventListener,并确保方块的z-index无冲突。
2. 游戏结束逻辑错误(漏过黑块未触发结束)#
- 原因:
hasBlack判断逻辑错误,或行的位置计算错误。 - 解决:确保遍历行内所有方块,正确判断是否含
black类;调试currentTop的计算逻辑,确保与容器高度比较正确。
3. 性能卡顿(动画不流畅)#
- 原因:
setInterval动画、频繁DOM操作。 - 解决:改用
requestAnimationFrame,并批量操作DOM(如生成一行后再添加到容器)。
总结#
通过实现“别踩白块”,我们掌握了:
- DOM操作:动态生成、删除元素,事件监听与响应。
- 动画原理:
requestAnimationFrame实现平滑下落,结合transform优化性能。 - 游戏逻辑:状态管理(得分、速度、游戏状态)、难度递增、边界检测。
该项目可扩展方向:添加音效、多关卡、本地存储记录最高分、自定义皮肤等。通过实践,你将更深入理解前端交互与动画的核心原理。
参考文献#
- MDN Web Docs:
requestAnimationFrame、DOM操作。 - 《JavaScript高级程序设计》(第4版):事件、动画与性能优化章节。
- 前端性能优化:《高性能JavaScript》。