HTML5 web应用
- 编写页面结构
- 模块化职责分离 专业、可维护、可扩展
- css 负责样式
- link 在head 引入
- js负责交互
- script 在body 的底部 引入
- 浏览器是执行前端代码的程序
- 下载并解析html 结构
- link 引入css html(结构) + css(样式) 先结合 实现静态页面
- 前端的天职就是 快速实现页面
提问
- 为什么script 在body 的底部 引入 详看script.js
- (为什么)script 交互放到后面
- (因为script会)阻塞html的下载和执行
静态页面
HTML & CSS 生成静态页面
-
CSS Reset 的作用是通过统一或清除不同浏览器对HTML元素的默认样式差异,为样式开发提供一个一致、可预测的基础。
-
*****选择器,匹配所有元素,性能不好
-
业界推荐的css reset 列出所有的元素,代替*
-
html结构 通过选择器 标签选择器 类选择器
-
背景的使用 background-size cover | contain
- cover 以盒子为主, 背景图片会等比例放大/收缩, 覆盖整个盒子,可能会有部分背景图片被裁剪
- contain 以背景图片为主, 背景图片会等比例放大/收缩, 直到在盒子里面完全显示图片。 background-position bottom center background-repeat no-repeat
-
rem ,vh 相对单位,解决移动端 设备尺寸不一致的问题 建议不要使用px 绝对单位
-
vh相对于视窗
-
rem 相对于html 根元素的字体大小 html font-size 10px
-
flex + 居中 完成 布局
- display:flex;弹性布局 9个.key 子元素就不会换行了(块级),在一行
- judtify-content: center;水平居中
- align-items: center;垂直居中
-
使用vh, rem 单位代替px 绝对单位, 适配移动端
script.js代码及其详细解析
我们来逐行、逐段详细解释这段 JavaScript 代码,从执行环境、语法结构、事件机制、DOM 操作、浏览器行为等多个维度全面剖析。
原始代码(带注释)
javascript
// 页面的最底部,在静态页面出现之后再执行
// document 整个文档 添加了一个事件监听
// 首要渲染界面, html + css, 不需要js 参与
// DOMContentLoaded html 文档加载完后在执行
// DOM 文档结构
// script阻塞html 的下载
document.addEventListener('DOMContentLoaded', function () {
// 页面加载完成后执行的代码
// 可以获取页面元素、添加事件监听器等
function playSound(event) {
// 事件对象,在事件发生的时候会给回调函数
// keyCode 按下的键的编码
console.log(event.keyCode, '/////////');
let keyCode = event.keyCode;
let element = document.querySelector('.key[data-key="' + keyCode + '"]');
console.log(element);
// 动态DOM编程
element.classList.add('playing');
}
// 事件监听
window.addEventListener('keydown', playSound);
});
1.整体功能概述
这段 JS 实现了一个 键盘响应式 UI 反馈系统:
- 当用户按下某个键盘按键时,
- 程序查找页面上具有对应
data-key属性的.key元素, - 并为其添加 CSS 类名
playing, - 从而触发视觉变化(如缩放、变色、阴影等),模拟"被按下"的效果。
常用于制作网页版电子琴、打击乐、快捷键提示等交互场景。
2.代码结构总览
| 行号 | 内容 | 类型 |
|---|---|---|
| 1-7 | 注释 | 说明性文本 |
| 8 | document.addEventListener(...) |
主入口:等待 DOM 加载完成 |
| 9 | { ... } |
回调函数体 |
| 10-17 | function playSound(event) |
核心逻辑函数 |
| 19 | window.addEventListener(...) |
绑定键盘事件 |
下面我们逐层拆解。
3.第一层:document.addEventListener('DOMContentLoaded', ...)
✅ 完整语句(无注释):
javascript
document.addEventListener('DOMContentLoaded', function () {
function playSound(event) {
console.log(event.keyCode, '/////////');
let keyCode = event.keyCode;
let element = document.querySelector('.key[data-key="' + keyCode + '"]');
console.log(element);
element.classList.add('playing');
}
window.addEventListener('keydown', playSound);
});
📌 分析:
1. document
- 是浏览器提供的全局对象之一,代表当前网页的 Document 对象。
- 它是 DOM(Document Object Model)的根节点,包含整个 HTML 结构。
- 所有页面元素都可以通过
document查找或创建。
2. .addEventListener(type, listener)
- 是 DOM 节点的一个方法,用于注册事件监听器。
- 参数:
type: 事件类型字符串,这里是'DOMContentLoaded'listener: 当事件触发时要执行的函数(回调函数)
3. 'DOMContentLoaded'
- 这是一个 DOM 事件 ,由浏览器在 HTML 文档解析完毕后自动触发。
- 触发时机:外部资源(图片、CSS、JS)可能还未加载完,但 DOM 树已经建立完成。
- 与之对比:
load事件:所有资源(包括图片、样式表、脚本)全部加载完成后才触发。DOMContentLoaded更早,适合操作 DOM。
4. function() { ... }
- 匿名函数,作为回调函数传入
addEventListener。 - 它不会立即执行,而是等到
'DOMContentLoaded'事件发生时才运行。 - 这种模式称为 事件驱动编程(Event-driven Programming)
💡 为什么必须用它?
因为如果直接写 JS 而此时 HTML 还没加载出来,比如:
javascript
<script>
document.querySelector('.key'); // 返回 null,因为还没生成
</script>
<body>...</body>
所以要把 JS 放到底部或包裹在 DOMContentLoaded 中,确保 DOM 已准备好。
4.第二层:playSound(event) 函数详解
这是整个程序的核心逻辑单元。
✅ 函数定义:
javascript
function playSound(event) {
console.log(event.keyCode, '/////////');
let keyCode = event.keyCode;
let element = document.querySelector('.key[data-key="' + keyCode + '"]');
console.log(element);
element.classList.add('playing');
}
1. function playSound(...)
- 定义一个名为
playSound的具名函数。 - 具名函数的好处是:
- 调试时堆栈更清晰(错误信息会显示函数名)
- 可以被多次引用而不重复定义
2. (event)
- 参数
event是一个由浏览器自动传入的 事件对象(Event Object) - 它包含了此次事件的所有相关信息:
- 按了哪个键?
- 是否同时按了 Shift/Ctrl?
- 事件何时发生?
- 如何阻止默认行为?
⚠️ 注意:这里不能省略
event参数,否则函数内部无法访问事件数据。
🔹 第一步:打印按键码
javascript
console.log(event.keyCode, '/////////');
解析:
event.keyCode- 是
KeyboardEvent接口的一个属性。 - 表示被按下键的 数字编码(ASCII 或虚拟键码)
- 示例:
- A → 65
- S → 83
- D → 68
- F → 70
- G → 71
- H → 72
- J → 74
- K → 75
- L → 76
- 是
📝 虽然
keyCode已被官方标记为 废弃(deprecated) ,但在旧项目中仍广泛使用。现代推荐使用event.key或event.code。
-
console.log(event.keyCode, '/////////');- 将内容输出到浏览器开发者工具的控制台(Console)。
- 用于调试,确认是否捕获到了按键。
-
'/////////'- 自定义分隔符,方便在大量日志中快速定位这条输出。
🔹 第二步:保存 keyCode 到变量
javascript
let keyCode = event.keyCode;
解析:
- 使用
let声明一个块级作用域变量keyCode - 把
event.keyCode的值复制过来 - 目的是避免后续频繁访问
event.keyCode,提高可读性和维护性
📌 等价于:
javascript
const keyCode = event.keyCode; // 更推荐用 const,因为不变
🔹 第三步:查找匹配的 DOM 元素
javascript
let element = document.querySelector('.key[data-key="' + keyCode + '"]');
这是最关键的 DOM 查询操作。
🔍 分解选择器:
| 部分 | 含义 |
|---|---|
.key |
class 为 key 的元素 |
[data-key="65"] |
具有属性 data-key 且其值为 "65" 的元素 |
'.key[data-key="65"]' |
同时满足以上两个条件的元素 |
🔧 document.querySelector(selector)
- 方法作用:返回文档中第一个匹配指定 CSS 选择器的元素
- 返回值类型:
- 找到 → 返回
Element对象(如<div class="key">...</div>) - 未找到 → 返回
null
- 找到 → 返回
🧩 字符串拼接过程:
假设用户按下了 A 键(keyCode=65):
javascript
'.key[data-key="' + 65 + '"]'
→ '.key[data-key="65"]'
然后去页面中搜索:
javascript
<div class="key" data-key="65">A</div>
✅ 匹配成功 → element 得到该 div 节点
🔹 第四步:再次打印元素(调试用途)
javascript
console.log(element);
- 输出刚才查找到的 DOM 元素
- 在浏览器控制台中会显示为可展开的 HTML 节点
- 用于验证查询是否成功
例如:
javascript
<div class="key" data-key="65">A</div>
🔹 第五步:动态修改类名(实现视觉反馈)
javascript
element.classList.add('playing');
这是实现"按钮按下"动画的关键一步。
1. element.classList
- 是一个
DOMTokenList对象,表示该元素的所有 CSS 类名集合 - 它是类数组对象 ,支持
.add,.remove,.toggle,.contains等方法
2. .add('playing')
- 向
classList中添加一个新的类名'playing' - 如果该类已存在,则不做任何事(不会重复添加)
🎯 效果示例:
原始 HTML:
html
<div class="key" data-key="65">A</div>
执行后变为:
html
<div class="key playing" data-key="65">A</div>
html文件内容不会改变,只是输出该语句的效果
🎨 配合 CSS 实现动画:
CSS内容如下:
css
.playing{
transform: scale(1.1); /*放大1.5倍*/
border-color: #ffc600;
box-shadow: 0 0 1rem #ffc600;
}
这样就会产生"按下放大"的动画效果。
5.第三层:绑定键盘事件监听
javascript
window.addEventListener('keydown', playSound);
📌 拆解:
1. window
- 浏览器中最顶层的对象,代表整个浏览器窗口
- 几乎所有全局变量和函数都是它的属性(如
window.document,window.console)
2. 'keydown'
- 键盘事件类型之一
- 触发时机:当用户按下任意键盘按键时触发一次
- 其他相关事件:
keyup:按键抬起keypress:字符输入(已废弃)
⚠️ 注意:某些特殊键(如 Ctrl、Alt)也可能触发
keydown,需注意过滤
3. playSound
- 是之前定义的函数名
- 这里是函数引用(function reference),不是调用!
✅ 正确:
javascript
window.addEventListener('keydown', playSound); // 传函数本身
❌ 错误:
javascript
window.addEventListener('keydown', playSound()); // 立即执行,返回 undefined
6.完整执行流程图解
我们以用户按下"A"键为例:
bash
[用户按下 A 键]
↓
触发 window 上的 'keydown' 事件
↓
浏览器调用 playSound(event)
↓
event.keyCode = 65
↓
console.log(65, '/////////')
↓
keyCode = 65
↓
查询 .key[data-key="65"]
↓
找到 <div class="key" data-key="65">A</div>
↓
element = 该 div 节点
↓
console.log(element) → 输出元素
↓
element.classList.add('playing')
↓
DOM 更新:<div class="key playing" ...>
↓
CSS 生效 → 视觉反馈(如缩放、变色)
7.涉及的核心技术点总结
| 技术 | 说明 |
|---|---|
| DOM (Document Object Model) | 将 HTML 转换为树形结构,供 JS 操作 |
| Event Listener | 监听用户行为(如按键、点击)并响应 |
Event Object (event) |
包含事件详细信息的对象 |
keyCode |
按键的数字编码(建议改用 key 或 code) |
querySelector |
使用 CSS 选择器查找元素 |
data-* 属性 |
自定义数据属性,用于存储元数据 |
classList.add() |
动态操作 CSS 类,实现状态切换 |
DOMContentLoaded |
确保 DOM 构建完成后再执行 JS |
8.潜在优化方向(进阶)
即使现在"无报错",也可以进一步提升健壮性和用户体验:
| 优化点 | 说明 |
|---|---|
✅ 使用 event.key 替代 keyCode |
更直观,如 'a', ' ' |
✅ 添加 setTimeout 移除 playing |
避免类堆积 |
| ✅ 支持鼠标点击 | element.addEventListener('click', ...) |
| ✅ 播放音效 | new Audio('clap.wav').play() |
| ✅ 防抖/节流 | 防止高频触发 |
| ✅ 错误处理 | 即使找不到也不崩溃 |
9.总结
这段 JS 代码虽然简短,但涵盖了前端开发中的多个核心概念:
- 事件驱动编程 :通过监听
keydown实现交互; - DOM 操作 :使用
querySelector查找元素; - 动态样式控制 :通过
classList.add修改外观; - 生命周期管理 :利用
DOMContentLoaded确保执行时机; - 数据绑定 :用
data-key将按键与 UI 元素关联。
它是一个典型的"事件 → 查询 → 更新 DOM → 视觉反馈"模式,是现代 Web 交互的基础模型。
只要保证 HTML 中每个 .key 都有对应的 data-key,并且用户按下的键是预设的,这段代码就能完美运行,无任何报错。
配合的 HTML 和 CSS 源码
为了让这段 JS 正常工作,需要如下配套代码:
index.html
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HTML5 敲击乐</title>
<link rel="stylesheet" href="./style.css">
</head>
<body>
<div class="keys">
<!-- dom 上添加一个数据属性 -->
<div class="key" data-key="65">
<h3>A</h3>
<span class="sound">clap</span>
</div>
<div class="key" data-key="83">
<h3>S</h3>
<span class="sound">hihat</span>
</div>
<div class="key" data-key="68">
<h3>D</h3>
<span class="sound">kick</span>
</div>
<div class="key" data-key="70">
<h3>F</h3>
<span class="sound">openhat</span>
</div>
<div class="key" data-key="71">
<h3>G</h3>
<span class="sound">boom</span>
</div>
<div class="key" data-key="72">
<h3>H</h3>
<span class="sound">ride</span>
</div>
<div class="key" data-key="74">
<h3>J</h3>
<span class="sound">snare</span>
</div>
<div class="key" data-key="75">
<h3>K</h3>
<span class="sound">tom</span>
</div>
<div class="key" data-key="76">
<h3>L</h3>
<span class="sound">tink</span>
</div>
</div>
<script src="./script.js"></script>
</body>
</html>
style.css
css
/* css reset
body,h3.. 有些浏览器的默认样式
div.. 又没有
统一一下,reset 样式重置
* 所有元素
*/
* {
margin: 0; /* margin:外边距 */
padding: 0;
}
/*
请返回标准的css reset代码,业内推荐的
Eric Meyer's Reset CSS v3.0.0 (https://meyerweb.com/eric/tools/css/reset/)
License: none (public domain)
*/
html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, i, u, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
body {
line-height: 1;
}
ol, ul {
list-style: none;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
/* 建议补充:现代开发常用的全局设置 */
*, *::before, *::after {
box-sizing: border-box;
}
img {
max-width: 100%;
height: auto;
display: block; /* 避免图片下方出现间隙 */
}
a {
text-decoration: none;
color: inherit;
}
/* 业务样式 */
html{
height: 100%;
background-color: green;
}
html {
font-size: 10px;
background: url('./background.jpg') bottom center no-repeat;
background-size: cover;
}
.keys {
display:flex; /*弹性布局*/
min-height: 100vh;/*现代的相对单位,不同手机,高度不一样,100vh 占满整个
不同设备间的兼容*/
/* background: green; 背景颜色调试法*/
align-items: center;
justify-content: center;
}
.key {
/* background: red; */
border: .4rem solid black; /*黑色边框 */
border-radius: 0.5rem;
margin: 1rem;
font-size: 1.5rem;
padding: 1rem 0.5rem;
width: 10rem;
text-align: center;
color: white;
background: rgba(0,0,0, 0.4); /* rgba:带有透明度的背景颜色 */
text-shadow: 0 0 .5rem black;/* text-shadow:使文字带有阴影效果 */
}
.key h3 {
display: block;
font-size: 4rem;
}
.key .sound {
font-size: 1.2rem;
text-transform: uppercase;
letter-spacing: 0.1rem;
color: #ffc600;
}
.playing{
transform: scale(1.1); /*放大1.5倍*/
border-color: #ffc600;
box-shadow: 0 0 1rem #ffc600;
}
项目结构及运行结果
项目结构

运行结果
运行index.html页面如下

此时在页面内鼠标右键选择检查

出现右侧界面后选择Console控制台

此时按下电脑键盘上的A键或(ASDFGHJKL中任意一个键),控制台输出如下内容
同时界面内的A键样式改变,如图

按下多个键效果相同,例如分别按下S D F键后,页面效果及控制台输出如下
