
简介这份资源是一套面向网页开发初学者的HTML贪吃蛇游戏实战项目适合刚接触前端、希望通过小游戏理解HTML、CSS与JavaScript协同工作的学习者。项目围绕游戏区域搭建、分数显示、键盘事件监听、蛇身位置更新、碰撞检测与食物随机生成等核心逻辑展开帮助读者把静态页面与动态交互串联起来。压缩包共3个文件包含1个html主页面、1张jpg背景图和1张png素材图整体约86KB体积轻巧便于直接运行和二次修改。目前已有1453人学习下载说明该案例在入门练习中具有一定参考价值。读者可从中获得完整的游戏页面结构、基础样式布局思路以及JavaScript驱动游戏循环的实现方式并在此基础上尝试加入音效、难度分级或CSS3动画等扩展功能适合作为前端入门阶段巩固DOM操作与逻辑思维的练手素材。1. 用 HTML 写贪吃蛇一个文件就能跑起来的经典练手项目很多人第一次接触前端都是从「用 HTML 做贪吃蛇」开始的。它不像框架项目那样一上来就配环境、装依赖而是把 HTML、CSS、JavaScript 三件套塞进一个文件里双击就能玩。这份资源就是这样一个完整可运行的贪吃蛇小游戏核心逻辑全部用原生 JavaScript 实现没有引入任何第三方库适合刚学完 HTML 基础标签、想找个东西练手的开发者也适合想拿它当教学案例、二次改成其他网格类游戏的人。它解决的不是业务问题而是「怎么把键盘事件、定时器、碰撞检测、状态管理串成一个能玩的东西」这个问题。下面我按拆项目的思路把它从结构到代码再到改造讲一遍。2. 项目结构与技术选型为什么一个 HTML 文件就够了2.1 目录结构与文件职责这份资源的结构非常扁平常见做法就是一个主文件加一个说明文档snake-game/ ├── index.html # 页面结构 样式 游戏逻辑全部内联 └── README.md # 玩法说明和改造提示index.html里通常分三块style负责画布和分数面板的样式canvas负责渲染游戏画面script负责全部逻辑。之所以不拆成game.js、style.css是因为这个项目的定位就是「零配置、双击即玩」拆文件反而增加了新手的心智负担。等你二次开发到一定规模再拆也不迟。2.2 技术选型Canvas 还是 DOM 网格贪吃蛇的渲染有两条路用 DOM 的div拼网格或者用canvas画。这份资源用的是 Canvas原因很直接——蛇身移动时如果频繁增删 DOM 节点浏览器重排开销大蛇一长就卡Canvas 每帧整体重绘逻辑更干净。核心 API 就几个API作用canvas.getContext(2d)拿到 2D 绘图上下文ctx.fillRect(x, y, w, h)画蛇身和食物ctx.clearRect()每帧清屏setInterval/requestAnimationFrame驱动游戏循环keydown事件接收方向键输入提示用setInterval控制速度最简单但帧率不稳定进阶做法是requestAnimationFrame配合时间戳累加后面改造章节会讲。2.3 游戏循环与状态设计整个游戏的状态可以用几个变量描述清楚蛇身坐标数组、当前方向、食物坐标、分数、游戏是否结束。常见做法是把它们放在脚本顶部统一声明方便调试时一眼看到全貌const canvas document.getElementById(game); const ctx canvas.getContext(2d); const gridSize 20; // 每个格子 20px const tileCount canvas.width / gridSize; // 横向格子数 let snake [{ x: 10, y: 10 }]; // 蛇身头部在数组第一个 let direction { x: 0, y: 0 }; // 当前移动方向 let food { x: 15, y: 10 }; // 食物坐标 let score 0; let gameOver false;这里gridSize和tileCount决定了棋盘大小改这两个值就能改难度。蛇身用数组存头部unshift新坐标、尾部pop掉旧坐标就实现了「移动」的视觉效果这是贪吃蛇最核心的一个技巧。3. 核心逻辑拆解从键盘输入到碰撞检测的完整链路3.1 键盘事件与方向控制方向控制最容易翻车的地方是「反向自杀」——蛇正在向右走你按左键它立刻掉头撞上自己。解决办法是记录当前方向禁止输入与当前方向相反的键document.addEventListener(keydown, (e) { switch (e.key) { case ArrowUp: if (direction.y 0) direction { x: 0, y: -1 }; break; case ArrowDown: if (direction.y 0) direction { x: 0, y: 1 }; break; case ArrowLeft: if (direction.x 0) direction { x: -1, y: 0 }; break; case ArrowRight: if (direction.x 0) direction { x: 1, y: 0 }; break; } });direction.y 0这个判断的意思是只有当前不在垂直移动时才允许改成垂直方向。这样向右走时按左键会被忽略避免瞬间掉头。参数x、y的取值只有 -1、0、1分别代表左/右、上/下。3.2 游戏主循环与移动逻辑每一帧要做四件事算新头部坐标、判断撞墙撞自己、判断是否吃到食物、重绘画面。顺序不能乱先判死再判吃否则会出现「死了还在加分」的玄学 bugfunction gameLoop() { if (gameOver) return; // 1. 计算新头部位置 const head { x: snake[0].x direction.x, y: snake[0].y direction.y }; // 2. 撞墙检测 if (head.x 0 || head.x tileCount || head.y 0 || head.y tileCount) { gameOver true; return; } // 3. 撞自己检测 for (let i 0; i snake.length; i) { if (snake[i].x head.x snake[i].y head.y) { gameOver true; return; } } // 4. 吃到食物头部入队不删尾部蛇变长 if (head.x food.x head.y food.y) { score 10; snake.unshift(head); placeFood(); // 重新随机生成食物 } else { snake.unshift(head); snake.pop(); // 没吃到尾部删掉长度不变 } draw(); }snake.unshift(head)把新头插到数组最前snake.pop()删掉最后一节整体看起来就是蛇在往前爬。吃到食物时不pop蛇就长了一节。placeFood()负责在棋盘内随机一个不与蛇身重叠的坐标。3.3 渲染与食物生成渲染函数每帧先清屏再画食物最后画蛇。食物用不同颜色区分蛇头可以单独加深一点视觉上更清楚function draw() { ctx.clearRect(0, 0, canvas.width, canvas.height); // 画食物 ctx.fillStyle #e74c3c; ctx.fillRect(food.x * gridSize, food.y * gridSize, gridSize - 2, gridSize - 2); // 画蛇 snake.forEach((seg, i) { ctx.fillStyle i 0 ? #2ecc71 : #27ae60; ctx.fillRect(seg.x * gridSize, seg.y * gridSize, gridSize - 2, gridSize - 2); }); // 更新分数显示 document.getElementById(score).textContent 得分 score; } function placeFood() { let newFood; do { newFood { x: Math.floor(Math.random() * tileCount), y: Math.floor(Math.random() * tileCount) }; } while (snake.some(s s.x newFood.x s.y newFood.y)); food newFood; }gridSize - 2是给每个格子留 2px 间隙看起来像一块块独立的方块比实心一片好看。placeFood里的do...while保证食物不会生成在蛇身上否则会出现「食物被蛇盖住吃不到」的情况。3.4 启动与重开最后把定时器和重开按钮接上整个游戏就闭环了let timer setInterval(gameLoop, 150); // 150ms 一帧数字越小越快 document.getElementById(restart).addEventListener(click, () { snake [{ x: 10, y: 10 }]; direction { x: 0, y: 0 }; score 0; gameOver false; placeFood(); draw(); });setInterval的第二个参数 150 就是速度改成 100 会明显变快改成 250 就慢悠悠。重开时把所有状态重置回初始值注意direction要归零否则蛇会带着上一局的方向直接冲出去。4. 避坑与常见问题排查4.1 按方向键页面跟着滚动现象玩游戏时按上下键整个网页跟着上下滚。原因方向键默认会触发页面滚动。解决在keydown回调里对方向键调用e.preventDefault()或者在 CSS 里给body加overflow: hidden。4.2 蛇移动时一顿一顿的现象蛇走起来不流畅像卡帧。原因setInterval的间隔和渲染节奏不匹配或者间隔设得太大。解决把间隔调到 100150ms进阶做法是改用requestAnimationFrame加时间累加器让移动和刷新解耦。4.3 食物生成在蛇身上现象偶尔食物刷出来就看不见或者刚吃到又消失。原因placeFood没有排除蛇身坐标。解决用do...while循环反复随机直到坐标不在snake数组里为止上面 3.3 的代码已经处理了。4.4 连续快速按键导致反向撞死现象手速快的时候蛇会突然掉头撞自己。原因一帧内收到多个方向键direction被连续覆盖。解决除了 3.1 的反向判断还可以加一个「每帧只接受一次方向变更」的标志位或者用队列缓存输入。4.5 游戏结束后定时器还在跑现象撞墙后画面还在动分数还在涨。原因gameLoop里gameOver判断没拦住或者定时器没清。解决在gameOver true之后clearInterval(timer)重开时再重新setInterval。5. 二次开发从贪吃蛇改成你自己的网格游戏这份资源最大的价值不是贪吃蛇本身而是它那套「网格 循环 状态数组」的骨架。你把这套骨架抽出来改几个地方就能变成推箱子、吃豆人、扫雷。我一般会先动这三个点第一把snake数组换成你要操作的对象。比如推箱子snake变成箱子坐标数组direction变成玩家移动方向碰撞检测从「撞自己」改成「撞墙和撞箱子」。第二把gameLoop的驱动方式换掉。贪吃蛇是定时自动走推箱子是按键才走所以把setInterval去掉改成在keydown里直接调move()再draw()。第三把渲染层抽象出来。现在draw()里写死了画蛇和食物你可以改成drawEntity(x, y, color)通用函数不同游戏传不同参数。改造完记得验证三件事边界坐标有没有越界、状态重置有没有漏项、定时器有没有重复创建。我踩过的坑是重开时忘了clearInterval结果每重开一次就多一个定时器蛇越跑越快查了半天才发现是定时器叠加。从那以后我每次写带循环的游戏都强制在启动前先清一次定时器再重新创建。希望这份拆解帮到你拿到文件先跑起来再照着改一版属于自己的网格游戏。本文还有配套的精品资源点击获取