Web-Dev-For-Beginners:基于事件驱动编程从零构建打字速度游戏
2026/9/7 18:13:14 网站建设 项目流程

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 listener)、addEventListener()clickinput事件的处理流程,以及如何用 DOM 操作实现实时视觉反馈。读完本篇并动手完成后,你将能够独立完成"用户输入 → 事件分发 → 状态更新 → 界面反馈"这一完整交互闭环,并可进一步实现难度分级、排行榜等进阶功能。

为什么 Web 编程是事件驱动的

网站"感知"你点击按钮、输入文字的能力,全部来自事件驱动编程(event-driven programming)。每一次点击、按键、鼠标移动,浏览器都会向 JavaScript 代码发送一条消息——即事件(event),由你决定如何响应。

Web 编程与传统脚本的最大差异在于不可预测性:你无法预知用户何时点击按钮——可能立刻点,可能等五分钟,也可能根本不会点。因此不能像写菜谱那样从上到下线性执行,而是让代码"耐心地等待某件事发生"。文档用了一个形象的类比:这就像 19 世纪的电报员守在机器旁,消息一到达就立即响应。

那么什么算一个"事件"?简单说就是"发生的事情":点击按钮是事件,输入一个字母是事件,移动鼠标也是事件。事件驱动编程的核心手段是event listener(事件监听器)——一种"门铃":

  • 安装门铃:调用addEventListener()
  • 告诉它监听什么信号:如'click''keypress'
  • 指定被触发时执行什么:你的自定义函数。

事件监听器的工作方式:

  • 监听(Listens)特定的用户行为,如点击、按键、鼠标移动;
  • 在指定事件发生时执行(Executes)你的自定义代码;
  • 立即响应(Responds)用户交互,创造无缝体验;
  • 通过不同的监听器处理(Handles)同一元素上的多个事件。

注意:创建事件监听器有多种方式——可以用匿名函数或命名函数,可以用click属性这样的快捷方式,也可以用addEventListener()。本课聚焦addEventListener()与匿名函数的组合,因为这是 Web 开发者最常用的方式,也是弹性最大的方式:addEventListener()适用于所有事件,事件名以参数形式传入。

常用事件一览

浏览器提供了几十种可监听的事件,但大多数交互应用只依赖少数几个核心事件。浏览器官方文档(MDN 的 Web Events 页面)列出了完整清单——基本上用户在页面上的任何操作都会产生事件。以下几个是最常见的(含本课使用的两个):

事件说明常见用途
click用户点击了某个元素按钮、链接、交互元素
contextmenu用户点击了鼠标右键自定义右键菜单
select用户选中了部分文本文本编辑、复制操作
input用户输入了文本表单校验、实时搜索

理解这些事件类型的要点:

  • 由用户与页面特定元素的交互触发(Triggers);
  • 通过事件对象提供(Provides)关于用户操作的详细信息;
  • 让你能够构建(Enables)响应式、交互式的 Web 应用;
  • 在不同浏览器和设备上行为一致(Works)。

打字游戏的设计与总体流程

本课要构建的游戏用于探索 JavaScript 中事件的工作方式:测试玩家的打字能力——这是所有开发者都应有的一项常被低估的技能。顺带一提,我们今天使用的 QWERTY 键盘布局是 19 世纪 70 年代为打字机设计的,而良好的打字技巧对今天的程序员依然同样值钱!

游戏的整体流程如下:

游戏的工作方式:

  • 玩家点击开始按钮时启动,并显示一条随机名言;
  • 逐词追踪玩家的打字进度(实时);
  • 高亮当前词语,引导玩家注意力;
  • 对打字错误提供即时的视觉反馈
  • 名言输入完成后计算并显示总耗时。

文件结构与项目初始化

保持干净的文件结构会让项目更专业。本项目只使用三个经典文件:index.html(页面结构)、script.js(游戏逻辑与事件监听器)、style.css(视觉样式)。

打开控制台或终端,执行以下命令创建项目目录:

# Linux or macOS mkdir typing-game && cd typing-game # Windows md typing-game && cd typing-game

这些命令的作用:创建名为typing-game的新目录存放项目文件,并自动进入该目录,为你的游戏开发建立一个干净的工作区。

然后打开 Visual Studio Code:

code .

该命令会在当前目录启动 VS Code,把你的项目文件夹载入编辑器,提供开发所需的全部工具。

在 VS Code 中向该文件夹添加三个文件:

  • index.html—— 游戏页面的结构与内容;
  • script.js—— 全部游戏逻辑与事件监听器;
  • style.css—— 视觉外观与样式。

构建用户界面

先想清楚游戏需要什么:如果你玩一个打字游戏,屏幕上应该看到什么?需求如下:

UI 元素用途HTML 元素
Quote Display显示要输入的文字id="quote"<p>
Message Area显示状态与成功消息id="message"<p>
Text Input玩家输入名言的输入框id="typed-value"<input>
Start Button开始游戏id="start"<button>

UI 结构的特点:内容自上而下逻辑组织;为元素分配唯一 ID 以便 JavaScript 定位;提供清晰的视觉层次;使用语义化 HTML 元素保障可访问性。每个元素都需要 ID 供后续 JavaScript 操作,同时要在 HTML 中引用 CSS 与 JS 文件。

创建index.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 文件以获得最佳性能(确保 DOM 已就绪)。

启动应用:Live Server

开发中频繁测试能尽早发现问题。VS Code 有一个名为 Live Server 的扩展,它会本地托管你的应用,并在每次保存文件后自动刷新浏览器,显著提升开发效率。

安装步骤与使用方式:

  1. 通过 VS Code 扩展市场安装 Live Server 扩展(点击 Install;浏览器可能会引导你回到 VS Code,安装过程中可能要求重启 VS Code);
  2. 安装完成后,在 VS Code 中按Ctrl-Shift-P(macOS 为Cmd-Shift-P)打开命令面板——它提供对所有 VS Code 命令的快速访问,支持边输入边搜索;
  3. 输入 "Live Server: Open with Live Server" 并执行;
  4. 浏览器访问https://localhost:5500(Live Server 默认本地地址),即可看到你创建的页面。

Live Server 的作用:为项目启动本地开发服务器;保存文件时自动刷新浏览器;从本地 URL(通常是localhost:5500)提供你的文件。

添加 CSS:让反馈一目了然

视觉反馈自计算早期就是用户界面的关键。我们的游戏必须让玩家立即知道该输入哪个词、出错时要马上看见。创建style.css并加入:

/* inside style.css */ .highlight { background-color: yellow; } .error { background-color: lightcoral; border: red; }

这两个类的作用:

  • .highlight用黄色背景标出当前应输入的词语,提供清晰的视觉引导;
  • .error用浅珊瑚色背景与红色边框标示打字错误;
  • 提供即时反馈而不打断用户的输入流;
  • 使用对比色保障可访问性与清晰的视觉传达。

CSS 布局可以随意发挥——花点时间让页面更好看:换字体、给标题上色、调整元素大小。仓库中的参考实现 solution/index.css 展示了如何在此基础上做完整美化:.highlight.error两个功能类保持不变,另外的样式为#quote添加了左侧色条与圆角卡片背景(index.css 第 54–65 行)、为输入框加入聚焦时的发光过渡(第 88–91 行)、为开始按钮加入悬停上移动效(第 113–116 行),并包含 600px 断点的响应式规则(第 139–156 行)。这印证了一个要点:游戏逻辑只依赖.highlight.error两个 class 名,视觉呈现可以自由发挥而无需改动 JavaScript

JavaScript:让游戏活起来

HTML 是车身,CSS 是外观,JavaScript 才是引擎。按以下三个步骤递进构建:

步骤目的学到的内容
添加常量设置名言数组与 DOM 引用变量管理与 DOM 选取
开始游戏的事件监听器处理游戏初始化事件处理与 UI 更新
打字输入的事件监听器实时处理用户输入输入校验与动态反馈

这种结构化方法帮助你:把代码组织成逻辑清晰的分区;增量构建功能便于调试;理解应用各部分如何协作;为未来项目积累可复用模式。

先创建script.js,然后逐步添加代码。

第一步:添加常量

先准备齐所有资源。游戏需要的基本数据:

数据类型用途示例
名言数组存储游戏所有候选名言['Quote 1', 'Quote 2', ...]
词语数组把当前名言拆分为单个词['When', 'you', 'have', ...]
词索引追踪玩家正在输入的词0, 1, 2, 3...
开始时间计算耗时用于计分Date.now()

同时需要三个 UI 元素的引用:

元素ID用途
文本输入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初始化追踪变量(wordswordIndexstartTime),因为这些值会在游戏过程中更新;
  • 通过document.getElementById()提前获取 DOM 元素引用以便高效访问;
  • 用清晰的命名变量为全部游戏功能打下基础。

技巧:用document.getElementById()随时可以取回元素。由于这些元素会被反复引用,用常量替代字符串字面量可以避免拼写错误;Vue.js、React 等框架能帮你更好地集中管理这类引用。这个做法的价值在于:防止多次引用时出现拼写错误;描述性常量名提升可读性;获得更好的 IDE 自动补全与错误检查;日后元素 ID 变更时重构更容易。

第二步:开始游戏的事件监听器

现在要写第一个真正的事件监听器。某处有个玩家终将点击"Start"按钮——可能是立刻,可能是喝完咖啡之后——当它发生时,你的游戏必须已经准备好。用户点击start时,我们需要:选取一条名言、初始化界面、设置当前词追踪与计时。

// 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(); });

代码逻辑分四块:

词追踪设置(Word Tracking Setup):

  • Math.floor(Math.random() * quotes.length)选取随机名言,保证每局不同;
  • split(' ')把名言切成单词数组;
  • wordIndex重置为 0,因为玩家从第一个词开始;
  • 为新一轮游戏准备好状态。

UI 设置与显示(UI Setup and Display):

  • 为每个词创建<span>元素,使其可单独设置样式类;
  • 把 span 数组连接成单个字符串,一次性写入innerHTML,高效完成 DOM 更新;
  • 通过给childNodes[0]添加highlight类高亮第一个词;
  • 清空上一局的消息,提供干净起点。

输入框准备(Textbox Preparation):

  • 清空输入框中的既有文本;
  • 调用focus()将焦点设到输入框,玩家可以立刻开始打字;
  • 为新一局准备好输入区域(下一节会在此注册input监听器)。

计时器初始化(Timer Initialization):

  • new Date().getTime()捕获当前时间戳(等价于Date.now());
  • 支撑打字速度与完成耗时的精确计算。

第三步:打字输入的事件监听器

这是游戏的核心:每输入一个字母,代码都会检查输入、给出反馈、决定下一步。它类似于 20 世纪 70 年代 WordStar 等早期文字处理软件为打字员提供实时反馈的方式。

// 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'; } });

理解打字逻辑的流程:该函数采用"瀑布式"判断,条件从最具体到最一般依次检查:

场景 1:名言完成(Quote Complete)

  • 检查输入值与当前词相等处于最后一个词(wordIndex === words.length - 1);
  • 用当前时间减去开始时间计算耗时(毫秒);
  • 除以 1000 转换为秒;
  • 显示含完成耗时的祝贺消息。

场景 2:一个词完成(Word Complete)

  • 当输入以空格结尾(endsWith(' '))时检测到词结束;
  • trim()验证去除空格后的输入与当前词完全一致;
  • 清空输入框,为新词做准备;
  • wordIndex自增,前进到下一个词;
  • 先移除所有 span 的 class,再给新词加上highlight类,完成视觉高亮的"接力"。

场景 3:正在正确输入(Typing in Progress)

  • currentWord.startsWith(typedValue)验证已输入部分是否为当前词的合法前缀;
  • 移除任何错误样式,表明输入正确;
  • 允许玩家不被打断地继续输入。

场景 4:错误状态(Error State)

  • 当输入文本与期望词的前缀不匹配时触发;
  • 给输入框应用errorCSS 类,提供即时视觉反馈;
  • 帮助玩家快速发现并纠正错误。

注意判断顺序的讲究:必须先检查"整句完成",再检查"词完成"——因为最后一词输入完成时不会再加空格,若顺序颠倒,最后一句永远无法判定完成。

对照仓库参考实现:同一逻辑的 ES6+ 重构版

仓库在 solution/index.js 中提供了一份现代 ES6+ 重构版实现,与教学代码逻辑一一对应,适合作为完成后的对照阅读材料,能印证上述事件处理链路的实际写法:

  • 监听器注册方式更简洁:solution/index.js 第 90–91 行直接用命名函数注册——startButton.addEventListener("click", startGame)typedValueElement.addEventListener("input", handleTyping),替代教学版中的匿名箭头函数,可读性更好且便于调试时定位函数名;
  • 高亮逻辑收敛为单一工具函数highlightWord(index)(第 44–48 行)用el.classList.toggle("highlight", i === index)一条语句同时完成"清除旧高亮 + 设置新高亮",而教学版需要显式循环清空再逐个赋值。从源码结构看,两种写法等价,但classListAPI 避免了直接操作className字符串覆盖其他类的潜在风险;
  • 错误反馈更完整:教学版只切换输入框的error类,重构版在错误分支额外写入messageElement.textContent = messages.error(第 84–85 行),把视觉反馈扩展到了消息区;
  • 游戏结束后的状态控制:完成时执行typedValueElement.disabled = true(第 73 行)禁用输入框——这正是挑战部分"UI State Management"要求的做法,防止玩家在完成后继续触发input事件;
  • 计时 API 差异:教学版用new Date().getTime(),重构版用Date.now(),二者返回相同的毫秒级时间戳,可互换使用。

此外,solution/index.html 在可访问性上做了补充:为输入框增加了隐藏的<label for="typed-value">(第 53 行),并配合 solution/index.css 中的.hidden-label视觉隐藏样式,保证屏幕阅读器用户也能获得标签。这提示我们:HTML 里aria-label之外,<label>关联是更稳健的可访问性实践。

测试你的应用

现在你已从零构建出一个真实可用的打字游戏。测试要点:

  • 点击 Start 确认显示随机名言;
  • 确认打字时当前词被正确高亮;
  • 确认拼错时出现错误样式;
  • 确认完成一个词后高亮正确前进到下一词;
  • 确认输入完整个名言后显示带耗时的完成消息。

常见调试技巧:

  • 用浏览器控制台(F12)查看 JavaScript 错误;
  • 核对所有文件名是否完全一致(大小写敏感);
  • 确认 Live Server 正在运行并能正常刷新;
  • 多测几条不同的名言,验证随机选择是否生效。

进阶挑战

挑战一:Copilot Agent 模式下的难度系统

课程提供了一个面向 AI 编程助手的挑战:使用 Agent 模式为打字游戏实现根据玩家表现动态调整的难度系统,练习高级事件处理、数据分析与动态 UI 更新。提示词要求实现的系统应:

  1. 追踪玩家的打字速度(每分钟词数)与准确率百分比;
  2. 在 Easy(简单名言)、Medium(现有名言)、Hard(含标点的复杂名言)三个难度级别间自动切换;
  3. 在 UI 上显示当前难度级别与玩家统计;
  4. 实现连击计数器:连续 3 次良好表现后提升难度;
  5. 为难度变化添加视觉反馈(颜色、动画)。

需要补充相应的 HTML 元素、CSS 样式与 JavaScript 函数,包含适当的错误处理,并用 ARIA 标签保证可访问性。

挑战二:手工进阶功能

功能说明练习的技能
Input Control游戏完成时禁用input事件监听器,点击按钮时重新启用事件管理与状态控制
UI State Management玩家完成名言时禁用文本框DOM 属性操作
Modal Dialog用模态对话框显示成功消息高级 UI 模式与可访问性
High Score SystemlocalStorage存储最高分浏览器存储 API 与数据持久化

实现提示:

  • 研究localStorage.setItem()localStorage.getItem()实现持久化存储;
  • 练习动态添加和移除事件监听器(对应addEventListener/removeEventListener);
  • 探索 HTMLdialog元素或 CSS 模态框模式;
  • 考虑禁用/启用表单控件时的可访问性。

其中"完成后禁用输入监听器"的做法在参考实现中已有雏形——solution/index.js 第 73 行在完成时disabled = true,你只需在此基础上补上点击 Start 时恢复监听与状态重置。

掌握时间线

接下来 5 分钟可以做的事:

  • 用不同名言测试你的打字游戏,确保运行顺畅
  • 尝试修改 CSS 样式——更换高亮色和错误色
  • 打开浏览器 DevTools(F12),在游戏过程中观察 Console
  • 挑战自己以最快速度完成一条名言

这一个小时可以完成的事:

  • 向数组中添加更多名言(可以来自你喜欢的书籍或电影)
  • 实现挑战部分的 localStorage 最高分系统
  • 创建一个每分钟词数计算器,在游戏结束后显示
  • 为正确输入、错误和完成添加音效

一周的进阶路线:

  • 构建支持好友同屏对战的多玩家版本
  • 创建具有不同名言复杂度的难度级别
  • 添加显示名言完成进度的进度条
  • 实现带个人统计追踪的用户账户
  • 设计自定义主题并允许用户选择样式

一个月的蜕变目标:

  • 创建逐步教授正确指法摆放的打字课程
  • 构建分析哪些字母或词导致最多错误的统计功能
  • 添加多语言和不同键盘布局支持
  • 集成教育类 API,从文学数据库获取名言
  • 发布你增强后的打字游戏供他人使用

课后作业:创建你自己的键盘游戏

学完本课基础后,正式作业是设计并构建你自己的键盘游戏——可以是另一种打字游戏、按键绘像素的艺术应用、用方向键控制的街机小游戏,或任何有创意的点子。要求见 Create a new keyboard game:

要求说明目的
Event Listeners至少响应 3 种不同的键盘事件展示对事件处理的理解
Visual Feedback对用户输入提供即时视觉响应展示 DOM 操作的掌握
Game Logic包含计分、关卡或进度机制练习实现应用状态
User Interface清晰的说明与直觉化的操作培养用户体验设计技能

文档给出的创意方向包括:节奏游戏(按键配合音乐或视觉提示)、像素艺术创作器(不同键画不同颜色)、单词构建器(按特定顺序拼词)、贪吃蛇(方向键控制收集物品)、音乐合成器(不同键发不同音符)、分类打字变体(编程术语、外语)、键盘鼓手(按键映射鼓点)。评分标准从四个维度展开:功能完整度与流畅度、代码质量与事件处理效率、用户体验的可直觉性与反馈质量、创意与键盘事件运用的新颖程度。

小结

本节的收获可以浓缩为一条完整的事件驱动链路:用addEventListener()#start按钮注册click监听完成随机名言渲染、词高亮与计时启动;为#typed-value输入框注册input监听,通过"前缀校验—词完成—句完成"的瀑布式判断实时更新wordIndex、高亮与错误样式。这正是每个交互网站的底层机制——下次看到表单边输边校验、菜单随点击展开、游戏随按键响应时,你都已经理解背后的原理了。完整课程脉络可参阅 Event-Driven Programming - Build a Typing Game,动手练习则从 课后作业 开始。

【免费下载链接】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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询