纯 CSS 贪吃蛇游戏:无 JavaScript 实现完整逻辑
纯 CSS 实现贪吃蛇游戏逻辑,利用 CSS Grid 布局构建棋盘,通过复选框和单选按钮模拟状态管理,结合 CSS 动画与选择器实现蛇的移动、食物生成及碰撞检测。文章涵盖网格系统、蛇身创建、方向控制、分数系统及游戏结束判断等核心模块,展示了在不使用 JavaScript 的情况下完成游戏交互的技术极限挑战,适合前端开发者探索 CSS 高级特性。

纯 CSS 实现贪吃蛇游戏逻辑,利用 CSS Grid 布局构建棋盘,通过复选框和单选按钮模拟状态管理,结合 CSS 动画与选择器实现蛇的移动、食物生成及碰撞检测。文章涵盖网格系统、蛇身创建、方向控制、分数系统及游戏结束判断等核心模块,展示了在不使用 JavaScript 的情况下完成游戏交互的技术极限挑战,适合前端开发者探索 CSS 高级特性。

在 Web 开发领域,CSS 通常被视为负责样式的语言,而 JavaScript 则负责交互逻辑。本文将挑战这一传统观念,使用纯 CSS 实现完整的贪吃蛇游戏逻辑。这不仅是前端技术的极限挑战,也是对 CSS 选择器、动画和状态管理能力的深度探索。
文章将详细讲解如何仅用 HTML 和 CSS 创建一个功能完整的贪吃蛇游戏,涵盖游戏的核心逻辑:蛇的移动、食物生成、碰撞检测、分数计算和游戏结束判断。
贪吃蛇游戏的核心机制包括:
在无 JavaScript 的情况下,我们需要用 CSS 模拟这些逻辑。关键挑战在于:
我们将使用以下 CSS 技术来模拟状态:
利用 :checked 伪类模拟布尔状态
<input type="checkbox" hidden>
<label for="game-state">开始游戏</label>
<div><!-- 游戏内容 --></div>
#game-state:checked ~ .game-area {
/* 游戏进行中的样式 */
}
模拟互斥状态,如游戏方向
<input type="radio" name="direction" hidden>
<input type="radio" name="direction" hidden>
<input type="radio" name="direction" hidden checked>
存储和计算游戏状态
:root {
--snake-length: 3;
--game-speed: 1s;
--score: 0;
}
模拟连续移动和状态变化
@keyframes snake-move {
0% { transform: translateX(0); }
100% { transform: translateX(100px); }
}
实现复杂的选择逻辑
/* 选择第 n 个子元素 */
.snake-cell:nth-child(3) {
background: green;
}
首先创建游戏棋盘,使用 CSS Grid 布局:
<div>
<input type="checkbox" hidden>
<input type="radio" name="direction" hidden>
<input type="radio" name="direction" hidden>
<input type="radio" name="direction" hidden>
<input type="radio" name="direction" hidden checked>
<div>
<!-- 20x20 网格,共 400 个单元格 -->
<div><!-- 通过 CSS 生成 400 个单元格 --></div>
<!-- 蛇身元素 -->
<div>
<div></div>
<div></div>
<div></div>
</div>
<!-- 食物元素 -->
<>
开始/暂停
上
下
左
右
得分:0
等级:1
游戏结束!
现在,让我们用 CSS 创建网格和基本样式:
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
/* 游戏变量 */
--grid-size: 20; /* 20x20 网格 */
--cell-size: 20px;
--snake-color: #4CAF50;
--snake-head-color: #2E7D32;
--food-color: #F44336;
--bg-color: #000;
--grid-color: #333;
/* 游戏状态变量 */
--snake-length: 3;
--current-direction: right;
--game-speed: 0.5s;
--score: 0;
--level: 1;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 100%);
color: #fff;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
.game-container {
max-width: ;
: ;
: (, , , );
: ;
: ;
: (, , , );
: solid ;
}
{
: center;
: ;
: ;
: ;
: (, , , );
}
{
: center;
: ;
: ;
: ;
}
{
: relative;
: ((--cell-size) * (--grid-size));
: ((--cell-size) * (--grid-size));
: auto ;
: (--bg-color);
: solid (--grid-color);
: ;
: hidden;
}
{
: absolute;
: ;
: ;
: ;
: ;
: grid;
: ((--grid-size), fr);
: ((--grid-size), fr);
: ;
}
{
: ;
: absolute;
: ;
: ;
: ;
: ;
: ((--grid-color) , transparent ), (, (--grid-color) , transparent );
: (--cell-size) (--cell-size);
: ;
}
{
: flex;
: space-between;
: ;
: ;
: (, , , );
: ;
: ;
: solid ;
}
, {
: bold;
}
{
: ;
}
{
: ;
}
{
: ;
: bold;
: ;
: opacity ;
}
{
: flex;
: wrap;
: center;
: ;
: ;
}
{
: ;
: (to bottom, , );
: white;
: none;
: ;
: ;
: pointer;
: all ;
: center;
: ;
: ;
: none;
}
{
: ();
: ;
}
{
: (to bottom, , );
: ;
}
{
: ;
: ;
}
(: ) {
{
: ;
}
{
: ;
}
{
: ;
}
{
: column;
: center;
}
{
: ;
: ;
}
}
这是最复杂的部分,我们需要用纯 CSS 创建蛇身并实现移动效果。我们将使用 CSS Grid 定位每个蛇身部分,并通过动画改变其位置。
首先,我们需要在 HTML 中创建蛇身元素。由于 CSS 无法动态创建元素,我们需要预先创建足够多的蛇身部分:
<!-- 在 .game-board 内部,.grid-container 之后 -->
<div>
<!-- 蛇头 -->
<div></div>
<!-- 预先创建最大可能长度的蛇身部分 -->
<div></div>
<div></div>
<!-- 更多身体部分... -->
<!-- 总共创建 100 个身体部分,足够游戏使用 -->
<div></div>
<div></div>
<div></div>
<!-- 一直到 100 -->
</div>
现在,我们需要用 CSS 来控制哪些身体部分是可见的(根据当前蛇的长度),以及它们的位置:
/* 蛇容器 */
.snake-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 2;
}
/* 蛇身单元格 */
.snake-cell {
position: absolute;
width: var(--cell-size);
height: var(--cell-size);
border-radius: 3px;
transition: all var(--game-speed) linear;
z-index: 2;
}
/* 蛇头 */
.snake-cell.head {
background-color: var(--snake-head-color);
box-shadow: 0 0 10px var(--snake-head-color);
z-index: 3;
border-radius: 5px;
}
/* 蛇身 */
.snake-cell.body {
background-color: var(--snake-color);
}
/* 通过 CSS 变量控制蛇身位置 */
.snake-cell.body-part {
--row: 10;
--col: 10;
: (((--row) - ) * (--cell-size));
: (((--col) - ) * (--cell-size));
: ;
}
(-n + ()) {
: ;
}
snakeMoveRight {
{ : (((--col) - ) * (--cell-size)); }
{ : ((--col) * (--cell-size)); }
}
snakeMoveLeft {
{ : (((--col) - ) * (--cell-size)); }
{ : (((--col) - ) * (--cell-size)); }
}
snakeMoveDown {
{ : (((--row) - ) * (--cell-size)); }
{ : ((--row) * (--cell-size)); }
}
snakeMoveUp {
{ : (((--row) - ) * (--cell-size)); }
{ : (((--row) - ) * (--cell-size)); }
}
~ {
: snakeMoveRight (--game-speed) linear infinite;
}
~ {
: snakeMoveLeft (--game-speed) linear infinite;
}
~ {
: snakeMoveDown (--game-speed) linear infinite;
}
~ {
: snakeMoveUp (--game-speed) linear infinite;
}
() ~ {
: paused;
}
在纯 CSS 中实现随机性是最具挑战的部分。我们将使用多个隐藏的单选按钮和动画来模拟伪随机性:
<!-- 在 .game-board 内部添加食物容器 -->
<div>
<!-- 创建多个食物可能位置 -->
<input type="radio" name="food-pos" hidden>
<input type="radio" name="food-pos" hidden>
<!-- 更多食物位置选项... -->
<!-- 食物显示元素 -->
<label for="food-1-1"></label>
<label for="food-1-2"></label>
<!-- 更多食物单元格... -->
</div>
/* 食物容器 */
.food-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 2;
}
/* 食物单元格 */
.food-cell {
position: absolute;
width: var(--cell-size);
height: var(--cell-size);
background-color: var(--food-color);
border-radius: 50%;
box-shadow: 0 0 10px var(--food-color);
top: calc((var(--food-row) - 1) * var(--cell-size));
left: calc((var(--food-col) - 1) * var(--cell-size));
opacity: 0;
z-index: 2;
cursor: default;
}
/* 当对应位置被选中时显示食物 */
.food-pos:checked + .food-cell {
opacity: 1;
}
/* 食物闪烁动画 */
@keyframes foodBlink {
, { : (); : ; }
{ : (); : ; }
}
{
: foodBlink infinite;
}
{
: food-counter;
: changeFood infinite;
}
changeFood {
, { : food-counter ; }
, { : food-counter ; }
, { : food-counter ; }
, { : food-counter ; }
{ : food-counter ; }
}
() {
: (food-counter);
}
方向控制通过单选按钮组实现,使用 CSS 根据选中状态改变蛇的移动方向:
/* 方向控制逻辑 */
.dir-control {
position: absolute;
opacity: 0;
pointer-events: none;
}
/* 蛇根据方向改变位置 */
#dir-right:checked ~ .game-board .snake-cell.head {
--direction: right;
}
#dir-left:checked ~ .game-board .snake-cell.head {
--direction: left;
}
#dir-up:checked ~ .game-board .snake-cell.head {
--direction: up;
}
#dir-down:checked ~ .game-board .snake-cell.head {
--direction: down;
}
/* 防止立即反向移动(贪吃蛇不能直接反向) */
#dir-right:checked ~ #dir-left,
#dir-left:checked ~ #dir-right,
#dir-up:checked ~ #dir-down,
#dir-down:checked ~ #dir-up {
pointer-events: none;
}
/* 方向按钮激活状态 */
.dir-btn {
position: relative;
}
.dir-control + {
: (to bottom, , );
: ;
}
碰撞检测是游戏逻辑的核心。我们将使用 CSS 选择器和动画来模拟碰撞检测:
/* 边界碰撞检测 */
@keyframes checkBoundary {
0% { --head-col: 10; --head-row: 10; }
/* 向右移动时检测右边界 */
25% { --head-col: 20; --head-row: 10; }
/* 向左移动时检测左边界 */
50% { --head-col: 0; --head-row: 10; }
/* 向下移动时检测下边界 */
75% { --head-col: 10; --head-row: 20; }
/* 向上移动时检测上边界 */
100% { --head-col: 10; --head-row: 0; }
}
/* 当蛇头到达边界时触发游戏结束 */
.snake-cell.head {
--head-col: 10;
--head-row: 10;
animation: checkBoundary 1s infinite paused;
}
/* 检测到边界碰撞时显示游戏结束 */
.snake-cell.head:after {
content: '';
position: absolute;
width: 100%;
height: 100%;
background: rgba(244, , , );
: ;
: ;
: pulse ;
}
(~ (-n + ())) {
}
+ {
: eatFood ;
}
eatFood {
{ : (); }
{ : (); }
{ : (); }
}
{
: score-counter ;
}
{
: (score-counter);
}
使用 CSS 计数器实现分数和等级系统:
/* 初始化计数器 */
body {
counter-reset: score-counter level-counter;
}
/* 分数更新逻辑 */
#food-1-1:checked ~ .game-status .score-value::after {
counter-increment: score-counter 10;
content: counter(score-counter);
}
/* 等级更新逻辑 */
:root {
--level: calc(counter(score-counter) / 100 + 1);
}
.level-value::after {
content: var(--level);
}
/* 游戏结束状态 */
.game-over {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 3rem;
color: #F44336;
text-shadow: 0 0 10px rgba(244, 67, 54, 0.7);
z-index: 10;
opacity: 0;
pointer-events: none;
}
~ () ~ ,
~ () ~ ,
~ () ~ ,
~ () ~ {
: ;
: gameOverFadeIn ;
}
gameOverFadeIn {
{ : ; : (-, -) (); }
{ : ; : (-, -) (); }
}
{
: absolute;
: ;
: ;
: (-, -);
: ;
: ;
: ;
: ;
: none;
}
() ~ {
: ;
}
由于篇幅限制,这里提供完整的 HTML 结构和核心 CSS 代码框架:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>纯 CSS 贪吃蛇游戏</title>
<link rel="stylesheet" href="style.css">
<style>
/* 这里包含所有上述 CSS 代码 */
</style>
</head>
<body>
<div>
<h1>纯 CSS 贪吃蛇游戏</h1>
<p>不使用 JavaScript 实现完整游戏逻辑</p>
<!-- 游戏控制输入 -->
<input type="checkbox" hidden>
<!-- 方向控制 -->
<input type="radio" name="direction" hidden>
得分:0
等级:1
游戏结束!
游戏暂停
开始/暂停
上
下
左
右
重新开始
游戏说明:
使用方向按钮控制蛇的移动
吃到红色食物可以增加长度和得分
撞到墙壁或自己的身体游戏结束
每得 100 分升一级,速度加快
游戏完全使用 CSS 实现,无 JavaScript
CSS 技术亮点:
本游戏使用纯 CSS 实现,利用了以下 CSS 特性:
CSS Grid 布局创建游戏网格
CSS 变量存储游戏状态
CSS 动画实现蛇的连续移动
复选框和单选按钮 hack 实现状态切换
CSS 计数器实现分数系统
复杂选择器实现碰撞检测
虽然我们已经实现了基本的贪吃蛇游戏,但仍有一些改进空间:
通过本项目的实现,我们展示了 CSS 作为样式语言的强大潜力。虽然纯 CSS 实现完整游戏逻辑存在诸多限制,但通过巧妙的 hack 和技术组合,我们成功创建了一个功能基本完整的贪吃蛇游戏。
这个项目不仅是对 CSS 技术深度的探索,也展示了前端开发中创造性解决问题的重要性。在实际生产环境中,我们仍然推荐使用 JavaScript 处理复杂逻辑,但了解 CSS 的极限能力有助于我们编写更高效、更优雅的代码。
纯 CSS 实现游戏的主要价值在于:

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online