用事件驱动编程从零构建打字速度游戏:Web-Dev-For-Beginners 实战指南
【免费下载链接】Web-Dev-For-Beginners24 Lessons, 12 Weeks, Get Started as a Web Developer项目地址: https://gitcode.com/GitHub_Trending/we/Web-Dev-For-Beginners
本指南以 Web-Dev-For-Beginners 课程第 4 模块《Event-Driven Programming - Build a Typing Game》为核心,带你理解浏览器如何通过事件与 JavaScript 通信,并亲手实现一个实时校验输入、逐词高亮、统计完成时间的打字速度游戏。读完本文,你将掌握addEventListener()事件监听、DOM 动态更新、输入校验与计时逻辑的组合运用,并能独立完成可运行的完整项目。
事件驱动编程:网页"活"起来的核心机制
思考你常用的应用或网站,是什么让它们显得鲜活、响应灵敏?关键在于对用户操作的即时反应——每一次点击、滑动或按键,都会产生一个"事件"(event),这正是 Web 开发魔力的来源。
Web 编程与普通脚本最大的不同在于不可预知性:我们永远不知道用户何时会点击按钮、何时开始打字——可能立即发生,可能等五分钟,也可能根本不发生。因此我们不能像写菜谱那样自上而下地编写代码,而是要写一段"耐心等待"的代码:事件未发生时它安静待命,事件一旦触发便立刻响应。这与 19 世纪电报员守在机器旁等待消息到来的工作方式异曲同工。
什么是事件?简单说,事件就是"发生了某件事":点击按钮是事件、输入一个字母是事件、移动鼠标也是事件。事件驱动编程(Event-Driven Programming)让我们为代码建立"监听-响应"机制,其核心构件是事件监听器(Event Listener)。
可以把事件监听器想象成代码的"门铃":
- 安装门铃用
addEventListener() - 告诉它要"听"什么声音,例如
'click'或'keypress' - 指定门铃响起时执行的逻辑(自定义函数)
事件监听器的四个关键特性:
- 监听特定的用户动作,如点击、按键、鼠标移动
- 执行事件发生时你编写的自定义代码
- 响应用户的交互,营造无缝体验
- 处理同一元素上的多个事件(通过不同的监听器)
NOTE:创建事件监听器的方式有很多种——可以使用匿名函数,也可以创建具名函数;可以使用
element.click = handler之类的快捷写法,也可以使用addEventListener()。本练习聚焦addEventListener()与匿名函数,因为这是 Web 开发者最常用的技术,而且它最灵活:适用于所有事件类型,事件名可以作为参数传入。
常见事件一览
浏览器提供数十种可监听的事件,页面上用户几乎任何操作都会触发事件。幸运的是,日常应用通常只需要其中一小部分。以下是几个常用事件(包含我们游戏中用到的两种):
| 事件 | 描述 | 常见用途 |
|---|---|---|
click | 用户点击了某元素 | 按钮、链接、可交互元素 |
contextmenu | 用户点击鼠标右键 | 自定义右键菜单 |
select | 用户选中了文本 | 文本编辑、复制操作 |
input | 用户输入了文本 | 表单校验、实时搜索 |
理解这些事件类型时要注意:
- 用户与页面上特定元素交互时触发事件
- 事件对象(event object)提供了用户动作的详细信息
- 事件支持你构建响应式、可交互的 Web 应用
- 事件行为在不同浏览器和设备间保持一致
游戏整体设计:从需求到流程
我们将创建一个打字速度游戏,检验玩家(也是开发者最重要的基本功之一)的键入技能。有趣的是,今天仍在使用的 QWERTY 键盘布局诞生于 1870 年代的打字机时代,而良好的打字能力对今天的程序员依旧价值连城。
游戏的整体流程如下:
游戏运行机制:
- 玩家点击开始按钮后启动,显示一句随机名言
- 逐词实时追踪玩家的输入进度
- 高亮当前单词,引导玩家注意力
- 出现输入错误时提供即时的视觉反馈
- 整句输入完成时计算并显示总用时
项目文件结构
保持简洁,整个游戏只需要三个文件——这也是驱动大部分 Web 应用的三件套:index.html(页面结构)、script.js(全部游戏逻辑)、style.css(视觉样式)。
打开终端创建项目目录:
# Linux 或 macOS mkdir typing-game && cd typing-game # Windows md typing-game && cd typing-game命令作用:新建名为typing-game的目录、自动进入该目录、为游戏开发准备好干净的工作区。
接着用 VS Code 打开目录:
code .最后在编辑器里新建上述三个文件:index.html负责游戏结构与内容、script.js承载所有游戏逻辑与事件监听器、style.css定义视觉外观。
参考:本仓库的完整可运行实现位于 4-typing-game/solution/,包含
index.html、index.js、index.css三个文件,可直接对照学习。
构建用户界面(HTML)
如同设计飞船控制面板,我们需要把所有玩家需要的元素放在最顺手的位置。分析一个打字游戏的需求,需要以下 UI 元素:
| UI 元素 | 用途 | HTML 元素 |
|---|---|---|
| 名言展示区 | 显示要输入的文字 | <p>且id="quote" |
| 消息区 | 显示状态与完成消息 | <p>且id="message" |
| 文本输入框 | 玩家输入名言的位置 | <input>且id="typed-value" |
| 开始按钮 | 开始游戏 | <button>且id="start" |
UI 结构设计要点:从上到下逻辑化组织内容、为每个元素分配唯一 ID 以便 JavaScript 定位、建立清晰的视觉层级、使用语义化 HTML 保证可访问性。
新建index.html,添加如下 HTML:
<!-- inside index.html --> <html> <head> <title>Typing game</title> <link rel="stylesheet" href="style.css"> </head> <body> <h1>Typing game!</h1> <p>Practice your typing skills with a quote from Sherlock Holmes. Click **start** to begin!</p> <p id="quote"></p> <!-- This will display our quote --> <p id="message"></p> <!-- This will display any status messages --> <div> <input type="text" aria-label="current word" id="typed-value" /> <!-- The textbox for typing --> <button type="button" id="start">Start</button> <!-- To start the game --> </div> <script src="script.js"></script> </body> </html>这段 HTML 结构的作用:
- 在
<head>中链接CSS 样式表 - 创建清晰的标题与用户指引
- 建立带特定 ID 的占位段落,供动态内容填充
- 包含带可访问性属性(
aria-label)的输入框 - 提供触发游戏的开始按钮
- 在末尾加载JavaScript 文件以获得最佳性能
仓库中的 4-typing-game/solution/index.html 在此基础上做了增强:引入了 Font Awesome 图标、视觉隐藏但仍保留给屏幕阅读器的
<label>(class="hidden-label")、输入框占位符(placeholder="Type here...")以及实时日期时间显示,体现了可访问性与用户体验的进阶实践。
启动应用:使用 Live Server
开发中频繁测试能尽早发现问题、实时看到进展。VS Code 的 Live Server 扩展既能在本地托管应用,又能在每次保存时自动刷新浏览器。
安装步骤如下:
- 通过链接进入扩展页面,点击Install(安装)——浏览器会提示打开 VS Code 并引导完成安装,部分情况可能需要重启 VS Code
- 安装完成后,按
Ctrl-Shift-P(macOS 为Cmd-Shift-P)打开命令面板——它提供所有 VS Code 命令的快速访问入口,支持边输入边搜索 - 输入并执行"Live Server: Open with Live Server"
Live Server 的作用:启动本地开发服务器、保存文件时自动刷新浏览器、默认在localhost:5500提供服务。打开浏览器访问https://localhost:5500,你应该能看到刚创建的页面。接下来添加功能。
添加样式(CSS)
从 1980 年代起,研究者就发现即时的视觉反馈能显著提升用户表现并减少错误——这正是我们要实现的。游戏必须让玩家一目了然:知道当前该输入哪个词,输错时能立刻察觉。
新建style.css,添加如下样式:
/* inside style.css */ .highlight { background-color: yellow; } .error { background-color: lightcoral; border: red; }这两个 CSS 类的作用:
highlight用黄色背景高亮当前单词,提供清晰的视觉引导error用浅珊瑚色背景提示输入错误- 在不打断打字流程的前提下提供即时反馈
- 使用对比色保证可访问性与清晰的视觉传达
✅ 关于布局你可以完全自由发挥,花点时间让页面更美观:更换字体、给标题上色、调整元素尺寸。
仓库解决方案 4-typing-game/solution/index.css 展示了一套完整的增强样式:渐变背景(linear-gradient(135deg, #f9fafb, #e3f2ef))、带左边框与圆角的引用展示卡片(#quote)、输入框聚焦时的发光效果(input#typed-value:focus的box-shadow)、开始按钮的悬浮位移动画(button#start:hover的transform: translateY(-2px)),以及针对窄屏的@media (max-width: 600px)响应式规则——值得逐一对照实践。
编写游戏逻辑(JavaScript)
HTML 与 CSS 搭好了"躯壳",JavaScript 则是让游戏真正动起来的"引擎"。整个实现分三步推进:
| 步骤 | 目的 | 你将学到 |
|---|---|---|
| 创建常量 | 准备名言数组与 DOM 引用 | 变量管理与 DOM 选择 |
| 开始游戏的事件监听器 | 处理游戏初始化 | 事件处理与 UI 更新 |
| 打字输入的事件监听器 | 实时处理用户输入 | 输入校验与动态反馈 |
这种结构化方式能帮助你:把代码组织成逻辑清晰的模块、增量构建功能以便调试、理解应用各部分如何协同工作、为未来项目沉淀可复用模式。
新建script.js。
第一步:创建常量
就像任务控制中心在发射前调试好全部监测系统,先把所有"资源"准备到位,既能避免后续到处查找,也能防止拼写错误。
需要准备的数据:
| 数据类型 | 用途 | 示例 |
|---|---|---|
| 名言数组 | 存放游戏所有可选名言 | ['Quote 1', 'Quote 2', ...] |
| 单词数组 | 将当前名言拆分为单词 | ['When', 'you', 'have', ...] |
| 单词索引 | 追踪玩家正在输入的单词 | 0, 1, 2, 3... |
| 开始时间 | 计算用时进行计分 | Date.now() |
还需要对 UI 元素的引用:文本输入框(typed-value)、名言展示区(quote)、消息区(message)。
// inside script.js // all of our quotes const quotes = [ 'When you have eliminated the impossible, whatever remains, however improbable, must be the truth.', 'There is nothing more deceptive than an obvious fact.', 'I ought to know by this time that when a fact appears to be opposed to a long train of deductions it invariably proves to be capable of bearing some other interpretation.', 'I never make exceptions. An exception disproves the rule.', 'What one man can invent another can discover.', 'Nothing clears up a case so much as stating it to another person.', 'Education never ends, Watson. It is a series of lessons, with the greatest for the last.', ]; // store the list of words and the index of the word the player is currently typing let words = []; let wordIndex = 0; // the starting time let startTime = Date.now(); // page elements const quoteElement = document.getElementById('quote'); const messageElement = document.getElementById('message'); const typedValueElement = document.getElementById('typed-value');这段初始化代码的作用:
- 用
const存放名言数组——名言不会改变 - 用
let初始化追踪变量——这些值会在游戏过程中更新 - 用
document.getElementById()捕获DOM 元素引用,保证高效访问 - 用清晰、描述性的变量名搭建全部游戏功能的基础
- 把相关数据与元素逻辑化组织,便于后续维护
✅ 你可以向数组中加入更多名言。
💡Pro Tip:随时可以用
document.getElementById()获取元素。由于我们会频繁引用这些元素,最好用常量封装以避免字符串字面量的拼写错误。Vue.js、React 等框架能帮你更好地集中管理代码。常量方式还能获得 IDE 的自动补全与错误检查支持,并在元素 ID 变更时更容易重构。
第二步:添加开始逻辑
当用户点击start,我们需要:随机选择名言、设置用户界面、为当前单词与计时建立追踪。在script.js末尾追加:
// at the end of script.js document.getElementById('start').addEventListener('click', () => { // get a quote const quoteIndex = Math.floor(Math.random() * quotes.length); const quote = quotes[quoteIndex]; // Put the quote into an array of words words = quote.split(' '); // reset the word index for tracking wordIndex = 0; // UI updates // Create an array of span elements so we can set a class const spanWords = words.map(function(word) { return `<span>${word} </span>`}); // Convert into string and set as innerHTML on quote display quoteElement.innerHTML = spanWords.join(''); // Highlight the first word quoteElement.childNodes[0].className = 'highlight'; // Clear any prior messages messageElement.innerText = ''; // Setup the textbox // Clear the textbox typedValueElement.value = ''; // set focus typedValueElement.focus(); // set the event handler // Start the timer startTime = new Date().getTime(); });按逻辑段拆解这段代码:
📊 单词追踪设置:
- 用
Math.floor()与Math.random()随机选取名言,保证每次不同 - 用
split(' ')将名言转换为单词数组 - 将
wordIndex重置为 0——玩家从第一个单词开始 - 为一轮新游戏准备好游戏状态
🎨 UI 设置与显示:
- 创建
<span>元素数组,把每个单词包裹起来以便单独设置样式 - 拼接span 元素为单个字符串,高效更新 DOM
- 给第一个单词添加
highlightCSS 类实现高亮 - 清除上一局的游戏消息,获得干净起点
⌨️ 文本框准备:
- 清空输入框中的既有文本
- 聚焦文本框,让玩家可以立即开始输入
- 准备好新游戏会话的输入区域
⏱️ 计时器初始化:
- 用
new Date().getTime()记录当前时间戳 - 为精确计算打字速度与完成时间提供基础
- 启动本局游戏的性能追踪
第三步:添加打字逻辑
这是游戏的心脏:每敲入一个字符,代码都会检查输入内容、给出反馈并决定下一步走向——类似 1970 年代 WordStar 等早期文字处理器为打字员提供的实时反馈。在script.js末尾继续追加:
// at the end of script.js typedValueElement.addEventListener('input', () => { // Get the current word const currentWord = words[wordIndex]; // get the current value const typedValue = typedValueElement.value; if (typedValue === currentWord && wordIndex === words.length - 1) { // end of sentence // Display success const elapsedTime = new Date().getTime() - startTime; const message = `CONGRATULATIONS! You finished in ${elapsedTime / 1000} seconds.`; messageElement.innerText = message; } else if (typedValue.endsWith(' ') && typedValue.trim() === currentWord) { // end of word // clear the typedValueElement for the new word typedValueElement.value = ''; // move to the next word wordIndex++; // reset the class name for all elements in quote for (const wordElement of quoteElement.childNodes) { wordElement.className = ''; } // highlight the new word quoteElement.childNodes[wordIndex].className = 'highlight'; } else if (currentWord.startsWith(typedValue)) { // currently correct // highlight the next word typedValueElement.className = ''; } else { // error state typedValueElement.className = 'error'; } });理解打字逻辑的瀑布式判断:这个函数采用"瀑布"(waterfall)方式,按从最具体到最通用的顺序依次检查条件:
🏁 场景一:整句完成
- 检查输入值是否等于当前单词且处于最后一个单词
- 用当前时间减开始时间计算用时
- 除以 1,000 将毫秒转换为秒
- 显示包含完成时间的祝贺消息
✅ 场景二:单词完成
- 输入以空格结尾时判定单词完成
- 验证
trim()后的输入与当前单词完全一致(typedValue.endsWith(' ') && typedValue.trim() === currentWord的复合条件确保玩家确实按下了空格键确认) - 清空输入框以输入下一个单词
- 递增
wordIndex推进到下一词 - 移除所有元素的类并高亮新单词,更新视觉高亮
📝 场景三:正在输入且正确
- 验证当前单词以已输入内容开头(
currentWord.startsWith(typedValue)) - 移除错误样式,表明当前输入正确
- 不打断输入,允许继续打字
❌ 场景四:错误状态
- 输入内容与预期单词开头不匹配时触发
- 应用
errorCSS 类,提供即时视觉反馈 - 帮助玩家快速发现并纠正错误
源码印证:仓库中的解决方案 4-typing-game/solution/index.js 对同一逻辑做了 ES6+ 重构,可作为对照学习的范本:使用
document.querySelector()统一选择元素;用getRandomQuote()、renderQuote()、highlightWord()等具名工具函数取代匿名函数;用[...quoteElement.children].forEach(...)与classList.toggle("highlight", i === index)更安全地切换高亮类;在完成时设置typedValueElement.disabled = true禁用输入框(这正是课后挑战中"UI 状态管理"的参考实现);并用Date.now()与toFixed(2)格式化完成时间。对比两种写法能同时理解"最小实现"与"工程化重构"两种风格。
测试你的应用
现在进入测试阶段!如果一开始没有完美运行,完全正常——即使经验丰富的开发者也会经常发现 bug,这是开发过程的一部分。
点击start,然后开始打字吧!
需要测试的要点:
- 验证点击 Start 会显示随机名言
- 确认输入时当前单词高亮正确
- 检查错误输入时会出现错误样式
- 测试完成单词后高亮会正确推进
- 验证完成整句名言后显示带用时的完成消息
常用调试技巧:
- 打开浏览器控制台(F12)检查JavaScript 错误
- 确认所有文件名完全匹配(注意大小写敏感)
- 确保Live Server 正在运行并正常刷新
- 测试不同名言以验证随机选择逻辑
进阶挑战
GitHub Copilot Agent 挑战 🎮
使用 Agent 模式完成以下挑战,练习高级事件处理、数据分析与动态 UI 更新:
任务:为打字游戏实现一个难度调节系统:
- 追踪玩家的打字速度(每分钟字数 WPM)与准确率百分比
- 自动在三档难度间切换:Easy(简单名言)、Medium(当前名言)、Hard(带标点的复杂名言)
- 在 UI 上显示当前难度等级与玩家统计数据
- 实现连击计数器——连续 3 次良好表现后提升难度
- 添加视觉反馈(颜色、动画)标识难度变化
需要补充相应的 HTML 元素、CSS 样式与 JavaScript 函数,包含合理的错误处理,并使用适当的 ARIA 标签保证可访问性。
🚀 核心挑战:扩展游戏功能
| 功能 | 描述 | 练习的技能 |
|---|---|---|
| 输入控制 | 完成后禁用input事件监听器,点击按钮时重新启用 | 事件管理与状态控制 |
| UI 状态管理 | 完成名言后禁用文本框 | DOM 属性操作 |
| 模态对话框 | 用模态对话框显示成功消息 | 高级 UI 模式与可访问性 |
| 最高分系统 | 用localStorage存储最高分 | 浏览器存储 API 与数据持久化 |
实现提示:
- 查阅
localStorage.setItem()与localStorage.getItem()实现持久化存储 - 练习动态添加与移除事件监听器
- 探索 HTML
<dialog>元素或 CSS 模态模式 - 禁用/启用表单控件时考虑可访问性
(提示:解决方案 4-typing-game/solution/index.js 中typedValueElement.disabled = true一行即为"UI 状态管理"挑战的直接参考。)
学习路线:从 5 分钟到一个月
⚡ 接下来 5 分钟:
- 用不同名言测试游戏,确保运行顺畅
- 尝试修改 CSS——换掉高亮与错误颜色
- 边玩边打开浏览器 DevTools(F12)观察 Console
- 挑战自己尽快完成一句名言
⏰ 这一小时:
- 往数组里加更多名言(比如来自你喜欢的书籍或电影)
- 实现挑战部分的 localStorage 最高分系统
- 添加每局结束后的每分钟字数(WPM)计算器
- 为正确输入、错误与完成添加音效
📅 这一周:
- 构建多人对战版本
- 创建不同难度级别,变化名言复杂度
- 添加进度条显示名言完成度
- 实现带个人统计追踪的用户账户
- 设计自定义主题供用户选择
🗓️ 这一个月:
- 创建包含指法教学的分级打字课程
- 构建分析功能,展示哪些字母或单词最容易出错
- 支持不同语言与键盘布局
- 集成教育 API 从文学数据库拉取名言
- 发布增强版打字游戏供他人使用
回顾与自学
浏览器为开发者提供了大量可用事件,通读这份清单,思考每个事件的适用场景:下拉菜单、实时校验的表单、响应点击的游戏——你看到的每一个交互,背后都是事件驱动编程的力量。
课后作业
完成本课后,通过 4-typing-game/typing-game/assignment.md 中的作业(Create a new keyboard game)检验学习成果:设计并构建一个你自己的键盘游戏,要求包含至少 3 个不同的键盘事件监听器、即时的视觉反馈、完整的游戏逻辑(计分/关卡/进度机制)以及清晰的用户界面说明。可参考的创意方向包括节奏游戏、像素画创作器、拼词游戏、贪吃蛇、音乐合成器或编程术语专项打字等;评分标准涵盖功能完整性、代码质量、用户体验与创意性四个维度。
【免费下载链接】Web-Dev-For-Beginners24 Lessons, 12 Weeks, Get Started as a Web Developer项目地址: https://gitcode.com/GitHub_Trending/we/Web-Dev-For-Beginners
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考