跳到主要内容纯 CSS 贪吃蛇游戏:无 JavaScript 实现完整逻辑 | 极客日志HTML / CSS大前端算法
纯 CSS 贪吃蛇游戏:无 JavaScript 实现完整逻辑
综述由AI生成纯 CSS 实现贪吃蛇游戏逻辑,利用 CSS Grid 布局构建棋盘,通过复选框和单选按钮模拟状态管理,结合 CSS 动画与选择器实现蛇的移动、食物生成及碰撞检测。文章涵盖网格系统、蛇身创建、方向控制、分数系统及游戏结束判断等核心模块,展示了在不使用 JavaScript 的情况下完成游戏交互的技术极限挑战,适合前端开发者探索 CSS 高级特性。
灭霸24 浏览 纯 CSS 贪吃蛇游戏:无 JavaScript 实现完整逻辑
引言
在 Web 开发领域,CSS 通常被视为负责样式的语言,而 JavaScript 则负责交互逻辑。本文将挑战这一传统观念,使用纯 CSS 实现完整的贪吃蛇游戏逻辑。这不仅是前端技术的极限挑战,也是对 CSS 选择器、动画和状态管理能力的深度探索。
文章将详细讲解如何仅用 HTML 和 CSS 创建一个功能完整的贪吃蛇游戏,涵盖游戏的核心逻辑:蛇的移动、食物生成、碰撞检测、分数计算和游戏结束判断。
1. 贪吃蛇游戏原理分析
贪吃蛇游戏的核心机制包括:
- 蛇在网格上移动
- 玩家控制蛇的移动方向
- 蛇吃到食物后长度增加
- 蛇碰到边界或自身时游戏结束
- 随游戏进行速度逐渐加快
在无 JavaScript 的情况下,我们需要用 CSS 模拟这些逻辑。关键挑战在于:
- 状态管理:CSS 本质上是无状态的
- 随机性:CSS 无法生成随机数
- 连续移动:需要模拟时间依赖的行为
- 用户交互:捕获键盘事件并改变游戏状态
2. CSS 状态管理策略
我们将使用以下 CSS 技术来模拟状态:
2.1 复选框 (checkbox) hack
利用 :checked 伪类模拟布尔状态
<input type="checkbox" hidden>
<label for="game-state">开始游戏</label>
<div></div>
#game-state:checked ~ .game-area {
}
2.2 单选按钮 (radio) 组
模拟互斥状态,如游戏方向
<input type="radio" name="direction" hidden>
<input type="radio" = >
name
"direction"
hidden
<input type="radio" name="direction" hidden checked>
2.3 CSS 变量与计数器
:root {
--snake-length: 3;
--game-speed: 1s;
--score: 0;
}
2.4 CSS 动画与关键帧
@keyframes snake-move {
0% { transform: translateX(0); }
100% { transform: translateX(100px); }
}
2.5 兄弟选择器和子元素计数
.snake-cell:nth-child(3) {
background: green;
}
3. 游戏棋盘与网格系统
<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>
<div></div>
<div>
<div></div>
<div></div>
<div></div>
</div>
<div><div></div></div>
</div>
<div>
<label for="game-start">开始/暂停</label>
<label for="dir-up">上</label>
<label for="dir-down">下</label>
<label for="dir-left">左</label>
<label for="dir-right">右</label>
</div>
<div>
<div>得分:<span>0</span></div>
<div>等级:<span>1</span></div>
<div>游戏结束!</div>
</div>
</div>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--grid-size: 20;
--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: 800px;
width: 100%;
background: rgba(0, 0, 0, 0.7);
border-radius: 20px;
padding: 30px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.5);
border: 2px solid #4CAF50;
}
.game-title {
text-align: center;
margin-bottom: 20px;
font-size: 2.5rem;
color: #4CAF50;
text-shadow: 0 0 10px rgba(76, 175, 80, 0.7);
}
.game-subtitle {
text-align: center;
margin-bottom: 30px;
color: #aaa;
font-size: 1.1rem;
}
.game-board {
position: relative;
width: calc(var(--cell-size) * var(--grid-size));
height: calc(var(--cell-size) * var(--grid-size));
margin: 0 auto 30px;
background-color: var(--bg-color);
border: 3px solid var(--grid-color);
border-radius: 5px;
overflow: hidden;
}
.grid-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: grid;
grid-template-columns: repeat(var(--grid-size), 1fr);
grid-template-rows: repeat(var(--grid-size), 1fr);
z-index: 1;
}
.grid-container::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: linear-gradient(var(--grid-color) 1px, transparent 1px), linear-gradient(90deg, var(--grid-color) 1px, transparent 1px);
background-size: var(--cell-size) var(--cell-size);
opacity: 0.3;
}
.game-status {
display: flex;
justify-content: space-between;
margin-bottom: 25px;
font-size: 1.2rem;
background: rgba(0, 0, 0, 0.5);
padding: 15px;
border-radius: 10px;
border: 1px solid #333;
}
.score, .level {
font-weight: bold;
}
.score-value {
color: #4CAF50;
}
.level-value {
color: #2196F3;
}
.game-over {
color: #F44336;
font-weight: bold;
opacity: 0;
transition: opacity 0.3s;
}
.controls {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 15px;
margin-top: 20px;
}
.control-btn {
padding: 12px 25px;
background: linear-gradient(to bottom, #4CAF50, #2E7D32);
color: white;
border: none;
border-radius: 8px;
font-size: 1.1rem;
cursor: pointer;
transition: all 0.2s;
text-align: center;
min-width: 120px;
box-shadow: 0 4px 0 #1B5E20;
user-select: none;
}
.control-btn:active {
transform: translateY(4px);
box-shadow: 0 0 0 #1B5E20;
}
.start-btn {
background: linear-gradient(to bottom, #2196F3, #0D47A1);
box-shadow: 0 4px 0 #0D47A1;
}
.dir-btn {
min-width: 80px;
padding: 12px 15px;
}
@media (max-width: 600px) {
:root {
--cell-size: 15px;
}
.game-container {
padding: 15px;
}
.game-title {
font-size: 2rem;
}
.controls {
flex-direction: column;
align-items: center;
}
.control-btn {
width: 100%;
max-width: 200px;
}
}
4. 蛇身的创建与移动
这是最复杂的部分,我们需要用纯 CSS 创建蛇身并实现移动效果。我们将使用 CSS Grid 定位每个蛇身部分,并通过动画改变其位置。
首先,我们需要在 HTML 中创建蛇身元素。由于 CSS 无法动态创建元素,我们需要预先创建足够多的蛇身部分:
<div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
</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);
}
.snake-cell.body-part {
--row: 10;
--col: 10;
top: calc((var(--row) - 1) * var(--cell-size));
left: calc((var(--col) - 1) * var(--cell-size));
opacity: 0;
}
.snake-cell.body-part:nth-child(-n + var(--snake-length)) {
opacity: 1;
}
@keyframes snakeMoveRight {
0% { left: calc((var(--col) - 1) * var(--cell-size)); }
100% { left: calc(var(--col) * var(--cell-size)); }
}
@keyframes snakeMoveLeft {
0% { left: calc((var(--col) - 1) * var(--cell-size)); }
100% { left: calc((var(--col) - 2) * var(--cell-size)); }
}
@keyframes snakeMoveDown {
0% { top: calc((var(--row) - 1) * var(--cell-size)); }
100% { top: calc(var(--row) * var(--cell-size)); }
}
@keyframes snakeMoveUp {
0% { top: calc((var(--row) - 1) * var(--cell-size)); }
100% { top: calc((var(--row) - 2) * var(--cell-size)); }
}
#dir-right:checked ~ .game-board .snake-cell {
animation: snakeMoveRight var(--game-speed) linear infinite;
}
#dir-left:checked ~ .game-board .snake-cell {
animation: snakeMoveLeft var(--game-speed) linear infinite;
}
#dir-down:checked ~ .game-board .snake-cell {
animation: snakeMoveDown var(--game-speed) linear infinite;
}
#dir-up:checked ~ .game-board .snake-cell {
animation: snakeMoveUp var(--game-speed) linear infinite;
}
#game-start:not(:checked) ~ .game-board .snake-cell {
animation-play-state: paused;
}
5. 食物生成与随机性模拟
在纯 CSS 中实现随机性是最具挑战的部分。我们将使用多个隐藏的单选按钮和动画来模拟伪随机性:
<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 {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(0.8); opacity: 0.8; }
}
.food-cell {
animation: foodBlink 1s infinite;
}
.food-generator {
counter-reset: food-counter;
animation: changeFood 5s infinite;
}
@keyframes changeFood {
0%, 20% { counter-increment: food-counter 1; }
25%, 45% { counter-increment: food-counter 7; }
50%, 70% { counter-increment: food-counter 13; }
75%, 95% { counter-increment: food-counter 19; }
100% { counter-increment: food-counter 23; }
}
.food-cell:nth-child(1):after {
content: counter(food-counter);
}
6. 方向控制实现
方向控制通过单选按钮组实现,使用 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:checked + .dir-btn {
background: linear-gradient(to bottom, #FF9800, #EF6C00);
box-shadow: 0 4px 0 #E65100;
}
7. 碰撞检测机制
碰撞检测是游戏逻辑的核心。我们将使用 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, 67, 54, 0.7);
border-radius: 50%;
opacity: 0;
animation: pulse 0.5s;
}
.snake-cell.head:has(~ .snake-cell.body-part:nth-child(-n + var(--snake-length))[style*="left:"]) {
}
.food-cell:active + .snake-cell.head {
animation: eatFood 0.3s;
}
@keyframes eatFood {
0% { transform: scale(1); }
50% { transform: scale(1.3); }
100% { transform: scale(1); }
}
.food-cell:active {
counter-increment: score-counter 10;
}
.score-value::after {
content: counter(score-counter);
}
8. 分数系统与游戏状态
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;
}
#game-start:checked ~ .game-board:has(.snake-cell.head[style*="left: calc(-1 * var(--cell-size))"]) ~ .game-over,
#game-start:checked ~ .game-board:has(.snake-cell.head[style*="left: calc(20 * var(--cell-size))"]) ~ .game-over,
#game-start:checked ~ .game-board:has(.snake-cell.head[style*="top: calc(-1 * var(--cell-size))"]) ~ .game-over,
#game-start:checked ~ .game-board:has(.snake-cell.head[style*="top: calc(20 * var(--cell-size))"]) ~ .game-over {
opacity: 1;
animation: gameOverFadeIn 1s;
}
@keyframes gameOverFadeIn {
0% { opacity: 0; transform: translate(-50%, -50%) scale(0.5); }
100% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
}
.game-pause {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 2rem;
color: #FFC107;
z-index: 10;
opacity: 0;
pointer-events: none;
}
#game-start:not(:checked) ~ .game-board .game-pause {
opacity: 1;
}
9. 完整代码实现
由于篇幅限制,这里提供完整的 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>
</style>
</head>
<body>
<div>
<h1>纯 CSS 贪吃蛇游戏</h1>
<p>不使用 JavaScript 实现完整游戏逻辑</p>
<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>
<div>得分:<span>0</span></div>
<div>等级:<span>1</span></div>
<div>游戏结束!</div>
</div>
<div>
<div></div>
<div>
<div></div>
<div></div>
<div></div>
</div>
<div>
<div></div>
</div>
<div>游戏暂停</div>
</div>
<div>
<label for="game-start">开始/暂停</label>
<label for="dir-up">上</label>
<label for="dir-down">下</label>
<label for="dir-left">左</label>
<label for="dir-right">右</label>
<button onclick="window.location.reload()">重新开始</button>
</div>
<div>
<h3>游戏说明:</h3>
<ul>
<li>使用方向按钮控制蛇的移动</li>
<li>吃到红色食物可以增加长度和得分</li>
<li>撞到墙壁或自己的身体游戏结束</li>
<li>每得 100 分升一级,速度加快</li>
<li>游戏完全使用 CSS 实现,无 JavaScript</li>
</ul>
<div>
<h4>CSS 技术亮点:</h4>
<p>本游戏使用纯 CSS 实现,利用了以下 CSS 特性:</p>
<ul>
<li>CSS Grid 布局创建游戏网格</li>
<li>CSS 变量存储游戏状态</li>
<li>CSS 动画实现蛇的连续移动</li>
<li>复选框和单选按钮 hack 实现状态切换</li>
<li>CSS 计数器实现分数系统</li>
<li>复杂选择器实现碰撞检测</li>
</ul>
</div>
</div>
</div>
</body>
</html>
10. 优化与扩展思路
虽然我们已经实现了基本的贪吃蛇游戏,但仍有一些改进空间:
10.1 性能优化
- 减少 DOM 元素数量,使用 CSS 伪元素替代部分蛇身
- 优化动画性能,使用 transform 代替 top/left
- 减少选择器复杂度,提高渲染效率
10.2 功能扩展
- 添加障碍物元素
- 实现不同种类的食物(不同分值)
- 添加关卡系统,每关有不同的地图布局
- 实现保存最高分功能(需少量 JavaScript)
10.3 纯 CSS 极限挑战
- 完全消除对 JavaScript 的依赖,包括重新开始功能
- 实现更精确的碰撞检测
- 添加音效(使用 CSS 触发音频播放)
10.4 响应式改进
- 适配不同屏幕尺寸
- 添加触摸手势控制
- 优化移动设备上的交互体验
结论
通过本项目的实现,我们展示了 CSS 作为样式语言的强大潜力。虽然纯 CSS 实现完整游戏逻辑存在诸多限制,但通过巧妙的 hack 和技术组合,我们成功创建了一个功能基本完整的贪吃蛇游戏。
这个项目不仅是对 CSS 技术深度的探索,也展示了前端开发中创造性解决问题的重要性。在实际生产环境中,我们仍然推荐使用 JavaScript 处理复杂逻辑,但了解 CSS 的极限能力有助于我们编写更高效、更优雅的代码。
- 提升对 CSS 高级特性的理解
- 在不支持 JavaScript 的环境下提供基本交互
- 作为技术挑战和学习工具
- 减少对 JavaScript 的依赖,提高页面加载速度
相关免费在线工具
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- Gemini 图片去水印
基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
- HTML转Markdown
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online