50天50个小项目 (Vue3 + Tailwindcss V4) ✨ | InsectCatchGame(捕捉昆虫游戏)
该项目通过三个界面(开始页、昆虫选择页、游戏页)实现互动式游戏体验,核心功能包括:1)响应式状态管理(ref/computed);2)动态昆虫生成与位置随机化;3)点击捕捉动画效果;4)计时计分系统。技术亮点涉及组合式API、生命周期钩子、动态样式绑定等Vue核心特性,同时采用CSS过渡实现平滑动画。
📅 我们继续 50 个小项目挑战!—— InsectCatchGame组件
仓库地址:https://github.com/SunACong/50-vue-projects
项目预览地址:https://50-vue-projects.vercel.app/

今天,我将带你一步步构建一个有趣的小游戏——“抓昆虫”(Catch The Insect),使用 Vue 3 的组合式 API (<script setup>) 和 Tailwind CSS 来打造一个响应式、交互性强的游戏应用。这个游戏虽然简单,但包含了状态管理、计时器、动画、事件处理等前端开发中的关键概念。
🎮 游戏概述
“抓昆虫”是一款看似简单实则极具挑战性的游戏。玩家需要在屏幕上点击快速移动的昆虫来得分,但随着时间推移,昆虫会越来越多,最终变得几乎不可能完成。这不仅考验反应速度,也是一场耐心的较量!
🎯 游戏目标
- 选择你喜欢(或最讨厌)的昆虫作为目标。
- 在限定时间内尽可能多地点击昆虫以获得高分。
- 体验“不可能完成”的挑战乐趣。
🛠️ 技术栈与核心概念
| 技术/概念 | 说明 |
|---|---|
Vue 3 <script setup> |
使用最新的组合式 API 语法糖,让代码更简洁、逻辑更清晰。 |
ref / computed |
管理响应式数据和派生状态。 |
onMounted / onBeforeUnmount |
生命周期钩子,用于初始化和清理资源(如定时器)。 |
| Tailwind CSS | 实现快速布局和样式设计,支持响应式与动画效果。 |
localStorage(可选扩展) |
可用于保存最高分记录。 |
动态样式绑定 :style |
控制昆虫的位置、旋转和缩放动画。 |
| 事件监听与处理 | 处理点击、右键菜单、图片加载错误等。 |
🖼️ 游戏界面结构
游戏分为三个主要界面:
1. 开始屏幕(Start Screen)
<div v-if="currentScreen === 'start'" ...>
<h1>Catch The Insect</h1>
<button @click="currentScreen = 'select'">Play Game</button>
</div>
- 居中显示标题和开始按钮。
- 点击按钮后跳转到选择昆虫界面。
2. 昆虫选择界面(Select Insect)
<div v-else-if="currentScreen === 'select'" ...>
<h1>What is your "favorite" insect?</h1>
<ul>
<li v-for="insect in insectsList" :key="insect.id">
<button @click="selectInsect(insect)">
<p>{{ insect.name }}</p>
<img :src="insect.src" alt="insect.name" />
</button>
</li>
</ul>
</div>
- 展示四种昆虫供玩家选择:苍蝇、蚊子、蜘蛛、蟑螂。
- 每个昆虫按钮包含名称和图片。
- 点击后调用
selectInsect()函数,进入游戏主界面。
3. 游戏主界面(Game Screen)
<div v-else-if="currentScreen === 'game'" ...>
<h3>Time: {{ formattedTime }}</h3>
<h3>Score: {{ score }}</h3>
<div v-if="messageVisible" class="...">Are you annoyed yet?...</div>
<!-- 昆虫元素 -->
<div v-for="(insect, index) in activeInsects" ...>
<img :src="selectedInsect.src" ... />
</div>
</div>
- 显示当前时间和得分。
- 当分数超过 19 时,弹出挑衅性提示:“Are you annoyed yet? You are playing an impossible game!!”
- 动态渲染所有活跃的昆虫实例。
💡 核心逻辑实现
🧠 响应式状态管理
const currentScreen = ref('start')
const selectedInsect = ref(null)
const score = ref(0)
const time = ref(0)
const messageVisible = ref(false)
const activeInsects = ref([])
const gameTimer = ref(null)
const insectTimer = ref(null)
这些 ref 变量构成了游戏的核心状态,Vue 会自动追踪它们的变化并更新视图。
⏱ 计时与昆虫生成
游戏计时器
function startGame() {
gameTimer.value = setInterval(() => time.value++, 1000)
}
每秒增加一次时间。
昆虫生成机制
function createInsect() {
const x = Math.random() * (window.innerWidth - 100) + 50
const y = Math.random() * (window.innerHeight - 100) + 50
const rotation = Math.random() * 360
activeInsects.value.push({ x, y, rotation, caught: false })
// 下一只昆虫出现时间:1~1.5秒之间
insectTimer.value = setTimeout(createInsect, 1000 + Math.random() * 500)
}
- 随机生成位置(避开边缘50px防止溢出)。
- 随机旋转角度增加视觉多样性。
- 使用
setTimeout实现递归调用,持续生成新昆虫。
🎯 捕捉昆虫逻辑
function catchInsect(index) {
activeInsects.value[index].caught = true
score.value++
if (score.value > 19) messageVisible.value = true
// 200ms后移除昆虫
setTimeout(() => {
activeInsects.value.splice(index, 1)
}, 200)
// 500ms后生成新昆虫
setTimeout(createInsect, 500)
}
- 点击昆虫后标记为
caught,触发动画(缩放至0)。 - 分数加1。
- 弹出“挑衅”消息。
- 延迟移除昆虫,避免立即消失影响体验。
- 快速生成新昆虫,加快游戏节奏。
🎨 动画与样式控制
昆虫动画效果
<div
:style="{
top: `${insect.y}px`,
left: `${insect.x}px`,
transform: `translate(-50%, -50%) scale(${insect.caught ? 0 : 1}) rotate(${insect.rotation}deg)`,
}"
class="transition-transform duration-300 ease-in-out"
>
translate(-50%, -50%):使昆虫中心对齐其坐标点。scale(0):被捕获时缩放为0,实现“消失”动画。rotate(...):随机旋转增加真实感。transition-transform:平滑过渡缩放和旋转效果。
消息提示动画
<div v-if="messageVisible" class="... opacity-100 transition-all duration-500">
利用 opacity 和 transition 实现淡入淡出效果。
🖼 图片错误处理
function handleImageError(event) {
event.target.src = 'https://via.placeholder.com/100?text=No+Image'
}
防止因图片链接失效导致界面错乱,提升健壮性。
🧹 资源清理
onBeforeUnmount(() => {
if (gameTimer.value) clearInterval(gameTimer.value)
if (insectTimer.value) clearTimeout(insectTimer.value)
})
避免内存泄漏,确保组件卸载时清除所有定时器。
📊 数据与计算属性
const formattedTime = computed(() => {
const minutes = Math.floor(time.value / 60).toString().padStart(2, '0')
const seconds = (time.value % 60).toString().padStart(2, '0')
return `${minutes}:${seconds}`
})
使用 computed 实时格式化时间为 MM:SS 格式,自动更新。
🐞 潜在问题与优化建议
| 问题 | 建议解决方案 |
|---|---|
| 频繁创建/销毁 DOM 节点 | 可考虑使用对象池(Object Pooling)复用昆虫元素,减少性能开销。 |
| 图片资源依赖外部链接 | 将图片本地化或使用 CDN 稳定地址,避免加载失败。 |
| 无结束机制 | 可添加“游戏结束”逻辑,例如:时间到达一定值后停止生成新昆虫,或设置最大昆虫数量上限。 |
| 缺乏音效与反馈 | 添加点击音效、得分动画等增强沉浸感。 |
| 移动端适配 | 当前 cursor-pointer 和点击事件在移动端可能体验不佳,需测试并优化触摸交互。 |
🎨 Tailwind CSS 样式重点
| 类名 | 作用 | 应用场景 |
|---|---|---|
relative |
设置元素为相对定位,允许其内部的绝对定位元素相对于它进行定位。 | 用于根 div,确保内部昆虫元素可以相对于整个屏幕进行绝对定位。 |
h-screen w-screen |
设置高度和宽度为视口高度和宽度的 100%。 | 确保背景颜色覆盖整个浏览器窗口。 |
overflow-hidden |
隐藏超出容器的内容。 | 防止任何溢出内容显示在游戏界面之外。 |
bg-[#516dff] |
自定义背景颜色,使用任意颜色值。 | 给游戏界面设置特定的背景色。 |
font-[Press_Start_2P] |
引入自定义字体(需先通过 @font-face 或 Google Fonts 加载)。 | 为游戏文本应用特定的游戏风格字体。 |
text-white |
设置文本颜色为白色。 | 提高对比度,使文字在深色背景下清晰可见。 |
transition-margin |
结合其他类使用来创建基于边距变化的过渡效果。 | 在不同屏幕之间切换时提供平滑过渡效果。 |
duration-500 |
指定过渡效果的持续时间为 500ms。 | 让页面切换或状态变化更加流畅。 |
ease-out |
定义过渡的速度曲线,开始快结束慢。 | 使动画看起来更自然。 |
flex |
启用 Flexbox 布局。 | 用于垂直和水平居中内容。 |
items-center justify-center |
使用 Flexbox 居中对齐子元素。 | 让标题和按钮在屏幕上居中显示。 |
px-6 py-4 |
设置内边距:左右各 6 个单位,上下各 4 个单位。 | 调整按钮的内边距,使其看起来更加舒适。 |
hover:opacity-90 |
当鼠标悬停时降低透明度。 | 提供视觉反馈,表明按钮是可交互的。 |
focus:outline-none |
移除聚焦时的默认轮廓。 | 改善用户体验,特别是对于非键盘导航用户。 |
list-none |
移除列表项的默认样式。 | 清除昆虫选择列表的默认样式,以便自定义设计。 |
m-4 |
设置所有方向的外边距为 4 个单位。 | 给每个昆虫选项添加适当的间距。 |
border-2 border-white |
设置边框宽度为 2px 并指定颜色为白色。 | 突出显示昆虫选择按钮。 |
hover:bg-white hover:text-[#516dff] |
当鼠标悬停时改变背景颜色和文本颜色。 | 提供交互反馈,帮助用户识别可点击区域。 |
absolute |
将元素位置设为绝对定位,相对于最近的已定位祖先元素。 | 允许精确控制昆虫的位置。 |
transition-transform duration-300 ease-in-out |
定义变换属性的过渡效果,持续时间 300ms,速度曲线为 ease-in-out。 | 实现昆虫捕捉时的缩放动画。 |
object-contain |
调整图片大小以适应容器,同时保持宽高比。 | 确保昆虫图片不会失真。 |
top-5 left-5 right-5 |
分别设置距离顶部、左侧和右侧的距离为 5 个单位。 | 控制计分板的位置。 |
🏁 总结
这个项目非常适合用来学习 Vue 3 的响应式系统、生命周期管理以及如何结合 CSS 实现流畅的动画效果。它既是一个小游戏,也是一个绝佳的学习案例。
- ✅ 难度递增系统:随着分数提高,昆虫移动速度加快或出现更多种类。
- ✅排行榜功能:结合
localStorage保存最高分。 - ✅主题切换:白天/黑夜模式。
- ✅昆虫行为模拟:让昆虫有简单的行为,如躲避鼠标、随机游走。
现在,你准备好挑战自己的耐心了吗?快去试试看能坚持多久!🪰
👉 下一篇,我们将完成收尾工作,完成项目的下半部分主要是介绍以下课程的合作者About组件和一个Footer组件,也是项目的最后一篇文章。🚀
感谢阅读,欢迎点赞、收藏和分享 😊
更多推荐



所有评论(0)