HTML5 从入门到工程实践:语义化、表单、多媒体、性能与项目规范详解

前言

HTML 是所有 Web 页面和 Web 应用的基础。

无论使用 Vue、React、Angular,还是直接开发传统网站,浏览器最终处理的页面结构仍然是 HTML。掌握 HTML 不只是记住标签,更重要的是理解:

  • 浏览器如何解析 HTML
  • 如何使用语义化标签描述内容
  • 如何构建可访问、可维护的页面
  • 如何处理表单、图片、音视频等资源
  • 如何提高页面性能和搜索引擎友好度
  • 如何在真实项目中组织、验证和部署 HTML

本文将从最基础的 HTML 文档开始,逐步讲到表单、多媒体、响应式图片、无障碍、安全、性能优化和工程化实践,并通过一个完整项目串联主要知识点。


1. HTML5 是什么

1.1 HTML 的定义

HTML 的全称是 HyperText Markup Language,即超文本标记语言。

它使用一系列标签描述网页内容的结构和含义,例如:

html 复制代码
<h1>文章标题</h1>
<p>这是一段正文。</p>
<a href="/about.html">关于我们</a>

HTML 不是编程语言,因为它本身通常不负责条件判断、循环和复杂计算。它是一种标记语言,核心任务是描述内容:

  • 这是标题
  • 这是段落
  • 这是导航
  • 这是表格
  • 这是表单
  • 这是图片
  • 这是文章主体

1.2 "HTML5"在今天意味着什么

历史上,HTML5 是 HTML 的一次重要演进,引入或规范了大量现代 Web 能力,例如:

  • headermainarticlesection 等语义化元素
  • audiovideo
  • Canvas
  • 更丰富的表单控件
  • Web Storage
  • History API
  • Web Workers
  • 自定义数据属性

现在 HTML 主要以持续更新的 Living Standard 形式演进,因此开发中所说的"HTML5",通常泛指现代 HTML 标准和相关 Web 平台能力。

需要注意:地理定位、WebSocket、Web Workers 等能力经常被统称为"HTML5 API",但它们并不全是 HTML 标签,而是 Web 平台提供的独立 API。

HTML 标准可参考:

WHATWG HTML Living Standard

1.3 HTML5 的主要特点

HTML5 相比早期页面开发方式,主要带来了以下变化:

  1. 文档声明更简单
  2. 页面结构更语义化
  3. 浏览器原生支持音视频
  4. 表单控件更加丰富
  5. 更适合移动端页面
  6. 提供更多浏览器端 API
  7. 更重视可访问性与跨设备能力
  8. 减少对 Flash 等插件的依赖

2. HTML、CSS 与 JavaScript 的关系

一个现代网页通常由 HTML、CSS 和 JavaScript 共同组成。

技术 主要职责 类比
HTML 描述页面结构与内容 建筑骨架
CSS 控制颜色、尺寸、布局和动画 装修与外观
JavaScript 处理交互、数据和业务逻辑 控制系统
HTTP 在浏览器和服务器之间传输资源 运输网络

例如:

html 复制代码
<!doctype html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>计数器</title>

  <style>
    button {
      padding: 8px 16px;
      color: white;
      background: #2563eb;
      border: 0;
      border-radius: 6px;
    }
  </style>
</head>
<body>
  <h1>当前数量:<span id="count">0</span></h1>
  <button id="increase" type="button">增加</button>

  <script>
    const countElement = document.querySelector("#count");
    const button = document.querySelector("#increase");

    let count = 0;

    button.addEventListener("click", () => {
      count += 1;
      countElement.textContent = String(count);
    });
  </script>
</body>
</html>

在这个例子中:

  • HTML 定义标题、数字和按钮
  • CSS 设置按钮样式
  • JavaScript 响应点击事件并修改数字

工程中应该尽量做到职责分离:

text 复制代码
project/
├─ index.html
├─ styles/
│  └─ main.css
└─ scripts/
   └─ main.js

3. 第一个 HTML5 页面

创建一个名为 index.html 的文件:

html 复制代码
<!doctype html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta
    name="viewport"
    content="width=device-width, initial-scale=1"
  >
  <meta
    name="description"
    content="这是我的第一个 HTML5 页面"
  >
  <title>我的第一个 HTML5 页面</title>
</head>
<body>
  <h1>Hello HTML5</h1>
  <p>这是我的第一个 HTML5 页面。</p>
</body>
</html>

保存后,直接使用浏览器打开即可。

3.1 <!doctype html>

html 复制代码
<!doctype html>

这行代码告诉浏览器:当前文档按照现代 HTML 标准解析。

它不是普通 HTML 标签,也不需要闭合。

如果缺少正确的文档声明,浏览器可能进入兼容模式,导致布局和标准行为出现差异。

3.2 <html>

html 复制代码
<html lang="zh-CN">

html 是整个文档的根元素。

lang="zh-CN" 表示页面主要语言是简体中文。正确设置语言有利于:

  • 屏幕阅读器选择正确的发音规则
  • 浏览器提供翻译功能
  • 搜索引擎理解页面语言
  • 拼写检查工具正确工作

3.3 <head>

head 保存页面元数据和资源声明,例如:

  • 字符编码
  • 页面标题
  • SEO 描述
  • 样式表
  • 图标
    -预加载资源
  • JavaScript 文件

head 中的内容大多不会直接显示在页面正文中。

3.4 <body>

body 中放置用户可见的页面内容,例如:

  • 标题
  • 段落
  • 图片
  • 表格
  • 表单
  • 导航
  • 按钮

4. HTML 的基本语法

4.1 元素、标签与内容

一个普通 HTML 元素通常由开始标签、内容和结束标签组成:

html 复制代码
<p>这是一段内容</p>

其中:

  • <p> 是开始标签
  • 这是一段内容 是文本内容
  • </p> 是结束标签
  • 整体称为一个元素

4.2 属性

属性用于提供元素的附加信息:

html 复制代码
<a href="https://example.com" target="_blank">
  访问网站
</a>

这里:

  • href 指定链接地址
  • target="_blank" 表示在新标签页打开

属性通常写在开始标签中:

html 复制代码
<元素名 属性名="属性值">内容</元素名>

推荐始终使用引号包裹属性值:

html 复制代码
<input type="text" name="username">

4.3 布尔属性

有些属性只要存在就表示开启:

html 复制代码
<input type="checkbox" checked>
<input type="text" required>
<button type="button" disabled>不可点击</button>

下面的写法没有必要:

html 复制代码
<input required="true">

对于布尔属性,重要的是属性是否存在,而不是字符串值是不是 "true"

4.4 空元素

部分元素不能包含子内容,因此没有结束标签:

html 复制代码
<img src="avatar.png" alt="用户头像">
<input type="text">
<meta charset="UTF-8">
<link rel="stylesheet" href="styles.css">
<br>
<hr>

HTML 中通常不需要写成 XML 风格:

html 复制代码
<img src="avatar.png" alt="用户头像" />

两种形式在 HTML 中通常都能被浏览器处理,但团队应保持统一风格。

4.5 标签嵌套

HTML 元素可以嵌套:

html 复制代码
<article>
  <h2>文章标题</h2>
  <p>文章内容</p>
</article>

必须保持正确的嵌套顺序。

错误示例:

html 复制代码
<p>
  <strong>重要内容
</p>
  </strong>

正确示例:

html 复制代码
<p>
  <strong>重要内容</strong>
</p>

4.6 注释

html 复制代码
<!-- 这是 HTML 注释 -->

注释适合解释必要的结构或兼容性处理,但不要在注释中放入:

  • 密码
  • Token
  • 内网地址
  • 私密业务数据
  • 不希望用户看到的信息

HTML 注释仍可能被发送到浏览器,用户可以通过"查看网页源代码"看到它。

4.7 字符实体

当页面需要显示 HTML 保留字符时,可以使用字符实体:

字符 实体
< &lt;
> &gt;
& &amp;
不换行空格 &nbsp;
" &quot;
' '

示例:

html 复制代码
<p>表达式:5 &lt; 10</p>
<p>技术栈:HTML &amp; CSS</p>

不要使用大量 &nbsp; 控制布局,页面间距应该交给 CSS。


5. 文档结构与页面元数据

一个适合实际项目使用的文档结构如下:

html 复制代码
<!doctype html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta
    name="viewport"
    content="width=device-width, initial-scale=1"
  >
  <meta
    name="description"
    content="HTML5 从入门到工程实践"
  >
  <meta name="theme-color" content="#2563eb">

  <title>HTML5 从入门到工程实践</title>

  <link rel="icon" href="/favicon.ico">
  <link rel="stylesheet" href="/styles/main.css">

  <script type="module" src="/scripts/main.js"></script>
</head>
<body>
  <header>网站头部</header>
  <main>页面主体</main>
  <footer>网站底部</footer>
</body>
</html>

5.1 字符编码

html 复制代码
<meta charset="UTF-8">

UTF-8 可以表示绝大多数语言字符,现代 Web 项目应统一使用 UTF-8。

为了尽早确定编码,通常将字符编码声明放在 head 前部。

5.2 移动端视口

html 复制代码
<meta
  name="viewport"
  content="width=device-width, initial-scale=1"
>

它告诉移动端浏览器:

  • 页面布局宽度跟随设备宽度
  • 初始缩放比例为 1

缺少这项配置时,移动端可能按照桌面页面宽度进行缩放。

5.3 页面标题

html 复制代码
<title>HTML5 从入门到工程实践</title>

标题通常会显示在:

  • 浏览器标签页
  • 收藏夹
  • 搜索结果
  • 分享预览的候选信息中

每个页面应该具有清晰且相对独立的标题。

5.4 页面描述

html 复制代码
<meta
  name="description"
  content="系统介绍 HTML5 语法、语义化、表单、多媒体和工程实践"
>

页面描述可能被搜索引擎用作搜索结果摘要,但搜索引擎也可能根据搜索内容重新选择摘要。

5.5 引入 CSS

html 复制代码
<link rel="stylesheet" href="/styles/main.css">

5.6 引入 JavaScript

现代项目推荐使用 ES Module:

html 复制代码
<script type="module" src="/scripts/main.js"></script>

模块脚本具有自己的作用域,并支持:

javascript 复制代码
import { formatDate } from "./date.js";

6. 文本与内容标签

6.1 标题

HTML 提供六级标题:

html 复制代码
<h1>页面主标题</h1>
<h2>一级章节</h2>
<h3>二级章节</h3>
<h4>三级章节</h4>
<h5>四级章节</h5>
<h6>五级章节</h6>

推荐按照内容层级使用标题,而不是根据默认字体大小选择标签。

错误做法:

html 复制代码
<h1>文章标题</h1>
<h4>文章第一章</h4>
<h2>第一章的小节</h2>

正确做法:

html 复制代码
<h1>文章标题</h1>
<h2>文章第一章</h2>
<h3>第一章的小节</h3>

标题大小应由 CSS 控制。

6.2 段落

html 复制代码
<p>HTML 用于描述页面内容结构。</p>

不要为了制造间距连续写空段落:

html 复制代码
<p>&nbsp;</p>
<p>&nbsp;</p>

应该使用 CSS 的 marginpadding

6.3 强调与重要性

html 复制代码
<p>
  提交前请<strong>确认订单金额</strong>。
</p>

<p>
  这里<em>特别强调</em>操作顺序。
</p>
  • strong 表示较高的重要性
  • em 表示语气或强调
  • b 通常表示视觉上需要注意但不增加重要性
  • i 可以表示术语、外语、特殊语气等

不要单纯为了加粗就滥用 strong

6.4 代码相关标签

html 复制代码
<p>使用 <code>npm run build</code> 构建项目。</p>

<pre><code>const message = "Hello HTML5";
console.log(message);</code></pre>

<p>按下 <kbd>Ctrl</kbd> + <kbd>S</kbd> 保存。</p>

<p>程序输出:<samp>Build completed</samp></p>
  • code:代码片段
  • pre:保留空格和换行
  • kbd:用户键盘输入
  • samp:程序输出

6.5 引用

块级引用:

html 复制代码
<blockquote cite="https://example.com/article">
  <p>语义化 HTML 能够让内容结构更加清晰。</p>
</blockquote>

行内引用:

html 复制代码
<p>他认为:<q>结构应该优先于样式。</q></p>

6.6 时间

html 复制代码
<time datetime="2026-08-23">2026 年 8 月 23 日</time>

机器可读的 datetime 有利于工具理解时间含义。

6.7 删除与新增

html 复制代码
<p>
  原价 <del>199 元</del>,
  现价 <ins>99 元</ins>。
</p>

6.8 其他常用元素

html 复制代码
<p>搜索关键词:<mark>HTML5</mark></p>

<p>水的化学式是 H<sub>2</sub>O。</p>

<p>2<sup>10</sup> 等于 1024。</p>

<p><small>活动最终解释权归平台所有。</small></p>

<hr>

hr 表示主题层面的分隔,不应仅作为装饰线使用。


7. HTML5 语义化布局

7.1 为什么要语义化

早期页面经常使用大量无含义的 div

html 复制代码
<div class="header"></div>
<div class="nav"></div>
<div class="content"></div>
<div class="footer"></div>

现代 HTML 可以直接描述结构:

html 复制代码
<header></header>
<nav></nav>
<main></main>
<footer></footer>

语义化的好处包括:

  • 代码更容易阅读
  • 屏幕阅读器更容易导航
  • 搜索引擎更容易理解内容
  • 浏览器阅读模式更容易提取正文
  • 团队协作和后期维护更简单

7.2 常用语义化元素

元素 含义
header 页面或区域头部
nav 主要导航区域
main 页面主要内容
article 可独立分发的完整内容
section 按主题划分的内容区域
aside 与主体间接相关的补充内容
footer 页面或区域底部
figure 图片、图表、代码等独立内容
figcaption figure 的说明文字
address 与当前内容相关的联系信息

7.3 完整布局示例

html 复制代码
<body>
  <a href="#main-content" class="skip-link">
    跳到主要内容
  </a>

  <header>
    <h1>前端技术博客</h1>

    <nav aria-label="主导航">
      <ul>
        <li><a href="/">首页</a></li>
        <li><a href="/articles/">文章</a></li>
        <li><a href="/about/">关于</a></li>
      </ul>
    </nav>
  </header>

  <main id="main-content">
    <article>
      <header>
        <h2>HTML5 从入门到实践</h2>
        <p>
          发布于
          <time datetime="2026-08-23">2026 年 8 月 23 日</time>
        </p>
      </header>

      <section>
        <h3>HTML 基础</h3>
        <p>这里介绍 HTML 基础语法。</p>
      </section>

      <section>
        <h3>HTML 工程实践</h3>
        <p>这里介绍工程规范。</p>
      </section>

      <footer>
        <p>作者:示例作者</p>
      </footer>
    </article>

    <aside aria-label="相关文章">
      <h2>相关文章</h2>
      <ul>
        <li><a href="/css-guide/">CSS 入门</a></li>
        <li><a href="/javascript-guide/">JavaScript 入门</a></li>
      </ul>
    </aside>
  </main>

  <footer>
    <p>&copy; 2026 前端技术博客</p>
  </footer>
</body>

7.4 articlesectiondiv 的区别

使用 article

当内容可以相对独立地发布、复用或分发时使用:

html 复制代码
<article>
  <h2>新闻标题</h2>
  <p>新闻内容</p>
</article>

典型场景:

  • 博客文章
  • 新闻
  • 商品卡片
  • 论坛帖子
  • 用户评论
使用 section

当内容属于一个明确主题区域时使用:

html 复制代码
<section>
  <h2>产品功能</h2>
  <p>功能介绍</p>
</section>

section 通常应该拥有标题。

使用 div

当只是为了布局、分组或挂载脚本,而没有更合适的语义元素时使用:

html 复制代码
<div class="card-grid">
  <!-- 卡片列表 -->
</div>

原则是:优先使用含义准确的元素,确实没有合适语义时再使用 div


8. 超链接与 URL 路径

8.1 基本链接

html 复制代码
<a href="https://example.com">访问示例网站</a>

8.2 相对路径与绝对路径

html 复制代码
<!-- 当前站点根目录 -->
<a href="/about.html">关于我们</a>

<!-- 当前目录 -->
<a href="./detail.html">详情</a>

<!-- 上一级目录 -->
<a href="../index.html">返回首页</a>

<!-- 完整绝对地址 -->
<a href="https://example.com/docs/">外部文档</a>

工程中应明确项目的部署根路径。部署到子目录时,以 / 开头的路径可能指向域名根目录,而不是当前项目目录。

8.3 页面内锚点

html 复制代码
<a href="#chapter-security">跳到安全章节</a>

<section id="chapter-security">
  <h2>安全实践</h2>
</section>

同一页面中的 id 应保持唯一。

8.4 邮件与电话

html 复制代码
<a href="mailto:contact@example.com">发送邮件</a>

<a href="tel:+8613800000000">拨打电话</a>

8.5 新标签页与安全

html 复制代码
<a
  href="https://example.com"
  target="_blank"
  rel="noopener noreferrer"
>
  在新标签页打开
</a>
  • noopener 阻止新页面通过 window.opener 控制原页面
  • noreferrer 还会限制 Referer 信息发送

是否使用 noreferrer 需要结合业务统计和隐私要求决定。

8.6 不要使用链接模拟按钮

错误示例:

html 复制代码
<a href="javascript:void(0)" onclick="saveData()">
  保存
</a>

正确示例:

html 复制代码
<button type="button" id="save-button">
  保存
</button>

基本原则:

  • 跳转到其他地址使用 a
  • 执行页面操作使用 button

9. 图片与响应式图片

9.1 基本图片

html 复制代码
<img
  src="/images/html5-cover.webp"
  alt="HTML5 页面结构示意图"
  width="1200"
  height="675"
>

重要属性:

  • src:图片地址
  • alt:替代文本
  • width:图片固有宽度
  • height:图片固有高度

同时写明宽高,有利于浏览器提前计算布局空间,减少页面加载时的布局跳动。

9.2 如何编写 alt

信息型图片:

html 复制代码
<img
  src="/images/sales-chart.png"
  alt="2025 年销售额从第一季度的 100 万元增长到第四季度的 260 万元"
>

装饰性图片:

html 复制代码
<img src="/images/decorative-line.svg" alt="">

作为链接的图片,需要描述链接的用途:

html 复制代码
<a href="/">
  <img src="/images/logo.svg" alt="示例商城首页">
</a>

不要写成没有意义的描述:

html 复制代码
<img src="chart.png" alt="图片">

9.3 延迟加载

html 复制代码
<img
  src="/images/article-cover.webp"
  alt="文章封面"
  width="1200"
  height="675"
  loading="lazy"
  decoding="async"
>

loading="lazy" 适合首屏之外的图片。

首屏最关键的大图通常不应该延迟加载,否则可能影响最大内容绘制时间。

9.4 figurefigcaption

html 复制代码
<figure>
  <img
    src="/images/browser-rendering.png"
    alt="浏览器从 HTML 构建 DOM 的流程"
    width="960"
    height="540"
  >
  <figcaption>
    图 1:浏览器解析 HTML 并构建 DOM
  </figcaption>
</figure>

9.5 srcsetsizes

html 复制代码
<img
  src="/images/product-800.webp"
  srcset="
    /images/product-480.webp 480w,
    /images/product-800.webp 800w,
    /images/product-1200.webp 1200w
  "
  sizes="
    (max-width: 600px) 100vw,
    (max-width: 1000px) 50vw,
    800px
  "
  alt="蓝色机械键盘"
  width="1200"
  height="800"
>

其中:

  • srcset 提供多个候选资源
  • 480w 表示图片固有宽度为 480 像素
  • sizes 描述图片在不同视口下预计占据的显示宽度
  • 浏览器根据视口、设备像素比和网络情况选择资源

9.6 picture 与图片格式切换

html 复制代码
<picture>
  <source
    type="image/avif"
    srcset="/images/cover.avif"
  >
  <source
    type="image/webp"
    srcset="/images/cover.webp"
  >
  <img
    src="/images/cover.jpg"
    alt="HTML5 技术文章封面"
    width="1200"
    height="675"
  >
</picture>

浏览器从上到下选择支持的资源,最后的 img 是必要的回退内容。

还可以根据不同屏幕使用不同裁剪:

html 复制代码
<picture>
  <source
    media="(max-width: 600px)"
    srcset="/images/banner-mobile.webp"
  >
  <img
    src="/images/banner-desktop.webp"
    alt="夏季技术大会"
    width="1440"
    height="600"
  >
</picture>

10. 列表与表格

10.1 无序列表

当项目没有严格顺序时使用:

html 复制代码
<ul>
  <li>HTML</li>
  <li>CSS</li>
  <li>JavaScript</li>
</ul>

10.2 有序列表

当项目存在顺序时使用:

html 复制代码
<ol>
  <li>创建 HTML 文件</li>
  <li>编写页面结构</li>
  <li>添加样式</li>
  <li>添加交互逻辑</li>
</ol>

10.3 描述列表

html 复制代码
<dl>
  <dt>HTML</dt>
  <dd>用于描述页面结构和内容。</dd>

  <dt>CSS</dt>
  <dd>用于控制页面样式和布局。</dd>

  <dt>JavaScript</dt>
  <dd>用于实现页面交互和业务逻辑。</dd>
</dl>

10.4 表格

表格用于展示二维表格数据,不应拿来做页面整体布局。

html 复制代码
<table>
  <caption>前端课程安排</caption>

  <thead>
    <tr>
      <th scope="col">课程</th>
      <th scope="col">课时</th>
      <th scope="col">难度</th>
    </tr>
  </thead>

  <tbody>
    <tr>
      <th scope="row">HTML5</th>
      <td>12</td>
      <td>入门</td>
    </tr>
    <tr>
      <th scope="row">CSS</th>
      <td>20</td>
      <td>中等</td>
    </tr>
  </tbody>

  <tfoot>
    <tr>
      <th scope="row">合计</th>
      <td>32</td>
      <td>-</td>
    </tr>
  </tfoot>
</table>

表格元素:

元素 作用
table 表格容器
caption 表格标题
thead 表头区域
tbody 表格主体
tfoot 表尾区域
tr
th 表头单元格
td 普通单元格

scope="col"scope="row" 可以帮助辅助技术理解表头与数据之间的关系。


11. HTML5 表单

表单是 HTML 工程中最重要的部分之一,常用于:

  • 登录
  • 注册
  • 搜索
  • 下单
  • 文件上传
  • 用户反馈
  • 后台管理

11.1 基本表单

html 复制代码
<form action="/api/register" method="post">
  <div>
    <label for="username">用户名</label>
    <input
      id="username"
      name="username"
      type="text"
      required
    >
  </div>

  <div>
    <label for="password">密码</label>
    <input
      id="password"
      name="password"
      type="password"
      minlength="8"
      required
    >
  </div>

  <button type="submit">注册</button>
</form>

11.2 actionmethod

html 复制代码
<form action="/search" method="get">

GET 通常适合:

  • 搜索
  • 筛选
  • 分页
  • 读取数据
  • 可以通过 URL 分享的查询
html 复制代码
<form action="/orders" method="post">

POST 通常适合:

  • 创建数据
  • 提交敏感或较大内容
  • 会改变服务端状态的操作

具体接口语义仍应由后端 API 设计确定。

11.3 name 属性的重要性

html 复制代码
<input id="email" name="email" type="email">

传统表单提交时,服务端接收的数据字段名来自 name,不是 id

如果没有 name,该控件通常不会进入表单提交数据。

11.4 label 与表单控件

推荐显式关联:

html 复制代码
<label for="email">邮箱</label>
<input id="email" name="email" type="email">

for 的值必须与控件的 id 完全一致。

这样可以:

  • 让屏幕阅读器读出控件名称
  • 点击文字时聚焦或选中控件
  • 扩大复选框和单选框的可点击区域

不要仅使用 placeholder 代替标签:

html 复制代码
<!-- 不推荐 -->
<input type="email" placeholder="请输入邮箱">

用户开始输入后,placeholder 会消失,而且它不一定能可靠承担标签的无障碍职责。

11.5 常用输入类型

html 复制代码
<input type="text">
<input type="password">
<input type="email">
<input type="url">
<input type="tel">
<input type="number">
<input type="date">
<input type="time">
<input type="datetime-local">
<input type="month">
<input type="week">
<input type="color">
<input type="range">
<input type="checkbox">
<input type="radio">
<input type="file">
<input type="search">
<input type="hidden">

移动端浏览器可能根据 type 显示不同的虚拟键盘。

也可以使用 inputmode 进一步提示输入模式:

html 复制代码
<label for="verification-code">验证码</label>
<input
  id="verification-code"
  name="verificationCode"
  type="text"
  inputmode="numeric"
  autocomplete="one-time-code"
  pattern="[0-9]{6}"
  maxlength="6"
>

inputmode 只是输入提示,不等于数据校验。

11.6 原生校验属性

html 复制代码
<input
  type="text"
  name="nickname"
  minlength="2"
  maxlength="20"
  required
>

<input
  type="number"
  name="age"
  min="18"
  max="120"
  required
>

<input
  type="text"
  name="code"
  pattern="[A-Z]{2}-[0-9]{4}"
  title="请输入两个大写字母、短横线和四位数字"
>

常用校验属性:

属性 作用
required 必填
minlength 最小文本长度
maxlength 最大文本长度
min 最小数值或日期
max 最大数值或日期
step 数值步长
pattern 正则模式

浏览器校验只能改善用户体验,不能代替服务端校验。用户可以绕过前端直接调用接口。

11.7 单选框与复选框

单选框使用相同的 name 形成一组:

html 复制代码
<fieldset>
  <legend>请选择配送方式</legend>

  <label>
    <input
      type="radio"
      name="delivery"
      value="standard"
      checked
    >
    普通配送
  </label>

  <label>
    <input
      type="radio"
      name="delivery"
      value="express"
    >
    加急配送
  </label>
</fieldset>

复选框:

html 复制代码
<fieldset>
  <legend>请选择感兴趣的技术</legend>

  <label>
    <input type="checkbox" name="skills" value="html">
    HTML
  </label>

  <label>
    <input type="checkbox" name="skills" value="css">
    CSS
  </label>

  <label>
    <input type="checkbox" name="skills" value="javascript">
    JavaScript
  </label>
</fieldset>

11.8 下拉选择

html 复制代码
<label for="city">城市</label>

<select id="city" name="city" required>
  <option value="">请选择城市</option>

  <optgroup label="华北">
    <option value="beijing">北京</option>
    <option value="tianjin">天津</option>
  </optgroup>

  <optgroup label="华东">
    <option value="shanghai">上海</option>
    <option value="hangzhou">杭州</option>
  </optgroup>
</select>

11.9 多行文本

html 复制代码
<label for="introduction">个人介绍</label>

<textarea
  id="introduction"
  name="introduction"
  rows="6"
  maxlength="500"
></textarea>

textarea 的默认值写在开始标签与结束标签之间:

html 复制代码
<textarea name="content">默认内容</textarea>

11.10 文件上传

html 复制代码
<form
  action="/api/upload"
  method="post"
  enctype="multipart/form-data"
>
  <label for="avatar">上传头像</label>

  <input
    id="avatar"
    name="avatar"
    type="file"
    accept="image/png,image/jpeg,image/webp"
  >

  <button type="submit">上传</button>
</form>

accept 只是帮助用户筛选文件,服务端仍必须检查:

  • 文件真实类型
  • 文件大小
  • 文件名
  • 文件内容
  • 存储位置
  • 是否包含恶意代码

11.11 按钮类型

html 复制代码
<button type="submit">提交</button>
<button type="reset">重置</button>
<button type="button">普通按钮</button>

表单内的 button 应明确写出 type,避免普通按钮意外触发表单提交。

11.12 使用 FormData

html 复制代码
<form id="profile-form">
  <label for="nickname">昵称</label>
  <input id="nickname" name="nickname" required>

  <button type="submit">保存</button>
</form>

<script>
  const form = document.querySelector("#profile-form");

  form.addEventListener("submit", async (event) => {
    event.preventDefault();

    if (!form.checkValidity()) {
      form.reportValidity();
      return;
    }

    const formData = new FormData(form);

    const response = await fetch("/api/profile", {
      method: "POST",
      body: formData
    });

    if (!response.ok) {
      throw new Error(`保存失败:${response.status}`);
    }
  });
</script>

发送 FormData 时,通常不要手动设置 Content-Type,浏览器会自动生成包含 boundary 的请求头。


12. 音频与视频

12.1 音频

html 复制代码
<audio controls preload="metadata">
  <source src="/media/podcast.ogg" type="audio/ogg">
  <source src="/media/podcast.mp3" type="audio/mpeg">

  您的浏览器不支持音频播放。
  <a href="/media/podcast.mp3">下载音频</a>
</audio>

12.2 视频

html 复制代码
<video
  controls
  preload="metadata"
  poster="/images/video-poster.webp"
  width="1280"
  height="720"
>
  <source src="/media/course.webm" type="video/webm">
  <source src="/media/course.mp4" type="video/mp4">

  <track
    kind="subtitles"
    src="/media/course-zh.vtt"
    srclang="zh-CN"
    label="简体中文"
    default
  >

  您的浏览器不支持视频播放。
  <a href="/media/course.mp4">下载视频</a>
</video>

12.3 常用属性

属性 作用
controls 显示浏览器播放控件
autoplay 自动播放
muted 静音
loop 循环播放
poster 视频封面
preload="none" 不主动预加载
preload="metadata" 只预加载元数据
preload="auto" 允许浏览器预加载媒体

浏览器通常会限制带声音的自动播放,因此不要把关键业务流程建立在自动播放必然成功的假设上。

12.4 字幕与无障碍

视频最好提供:

  • 同步字幕
  • 音频描述
  • 文本稿
  • 键盘可操作的播放控件

字幕文件通常使用 WebVTT 格式:

text 复制代码
WEBVTT

00:00:00.000 --> 00:00:03.000
欢迎学习 HTML5。

00:00:03.500 --> 00:00:07.000
本节介绍语义化标签。

13. 常用交互元素

13.1 detailssummary

html 复制代码
<details>
  <summary>HTML 和 HTML5 有什么区别?</summary>
  <p>
    HTML5 是现代 HTML 演进中的重要版本,
    今天通常泛指现代 HTML 与相关 Web 能力。
  </p>
</details>

默认展开:

html 复制代码
<details open>
  <summary>课程目录</summary>
  <p>第一章:HTML 基础</p>
</details>

13.2 对话框 dialog

html 复制代码
<button id="open-dialog" type="button">
  删除账户
</button>

<dialog id="confirm-dialog">
  <h2>确认删除账户?</h2>
  <p>删除后数据将无法恢复。</p>

  <form method="dialog">
    <button value="cancel">取消</button>
    <button value="confirm">确认删除</button>
  </form>
</dialog>

<script>
  const openButton = document.querySelector("#open-dialog");
  const dialog = document.querySelector("#confirm-dialog");

  openButton.addEventListener("click", () => {
    dialog.showModal();
  });

  dialog.addEventListener("close", () => {
    if (dialog.returnValue === "confirm") {
      console.log("执行删除");
    }
  });
</script>

showModal() 会以模态形式打开对话框,并帮助浏览器处理焦点和背景交互限制。

复杂对话框仍需测试:

  • 初始焦点
  • 键盘操作
  • Escape 关闭行为
  • 关闭后的焦点恢复
  • 屏幕阅读器表现

13.3 进度条

html 复制代码
<label for="upload-progress">上传进度</label>

<progress
  id="upload-progress"
  value="60"
  max="100"
>
  60%
</progress>

进度未知时可以省略 value

html 复制代码
<progress aria-label="正在加载"></progress>

13.4 度量值

html 复制代码
<label for="disk-usage">磁盘使用率</label>

<meter
  id="disk-usage"
  min="0"
  max="100"
  low="60"
  high="85"
  optimum="30"
  value="72"
>
  72%
</meter>
  • progress 表示任务完成进度
  • meter 表示已知范围内的测量值

14. Canvas、SVG 与 Template

14.1 Canvas

Canvas 是基于像素的绘图区域,适合:

  • 数据可视化
  • 游戏
  • 图片处理
  • 动态图形
  • 粒子效果
html 复制代码
<canvas
  id="chart"
  width="600"
  height="300"
  aria-label="季度销售额柱状图"
>
  您的浏览器不支持 Canvas。
</canvas>

<script>
  const canvas = document.querySelector("#chart");
  const context = canvas.getContext("2d");

  const values = [120, 180, 150, 240];
  const labels = ["Q1", "Q2", "Q3", "Q4"];

  context.fillStyle = "#2563eb";
  context.font = "16px sans-serif";

  values.forEach((value, index) => {
    const x = 50 + index * 130;
    const height = value;

    context.fillRect(x, 260 - height, 80, height);
    context.fillText(labels[index], x + 25, 285);
  });
</script>

Canvas 内容本身不像普通 HTML 那样具有丰富的可访问语义。重要图表应提供:

  • 文本说明
  • 数据表格
  • 合理的替代内容
  • 必要的键盘交互

14.2 SVG

SVG 是矢量图形,适合:

  • 图标
  • Logo
  • 流程图
  • 几何图形
  • 可缩放插图
html 复制代码
<svg
  viewBox="0 0 200 120"
  role="img"
  aria-labelledby="svg-title svg-desc"
>
  <title id="svg-title">增长趋势图</title>
  <desc id="svg-desc">
    折线从左下方向右上方增长
  </desc>

  <polyline
    points="10,100 50,80 90,85 130,45 190,20"
    fill="none"
    stroke="#2563eb"
    stroke-width="6"
  >
  </polyline>
</svg>

Canvas 与 SVG 的区别:

对比项 Canvas SVG
图形类型 位图 矢量图
DOM 节点 图形不是独立 DOM 元素 每个图形可以是 DOM 元素
大量对象 通常更适合高频绘制 节点过多可能影响性能
缩放 可能失真 通常保持清晰
交互 需要自行计算坐标 可直接绑定事件
可访问性 需要额外替代内容 更容易补充语义

14.3 template

template 中的内容不会立即渲染,可以在需要时克隆:

html 复制代码
<template id="user-card-template">
  <article class="user-card">
    <h2 class="user-card__name"></h2>
    <p class="user-card__email"></p>
  </article>
</template>

<div id="user-list"></div>

<script>
  const template = document.querySelector("#user-card-template");
  const userList = document.querySelector("#user-list");

  const users = [
    {
      name: "张三",
      email: "zhangsan@example.com"
    },
    {
      name: "李四",
      email: "lisi@example.com"
    }
  ];

  const fragment = document.createDocumentFragment();

  for (const user of users) {
    const card = template.content.cloneNode(true);

    card.querySelector(".user-card__name").textContent = user.name;
    card.querySelector(".user-card__email").textContent = user.email;

    fragment.append(card);
  }

  userList.append(fragment);
</script>

14.4 自定义元素

自定义元素名称必须包含短横线:

html 复制代码
<user-card name="张三"></user-card>

定义组件:

javascript 复制代码
class UserCard extends HTMLElement {
  connectedCallback() {
    const name = this.getAttribute("name") ?? "未命名用户";

    const article = document.createElement("article");
    const heading = document.createElement("h2");

    heading.textContent = name;
    article.append(heading);

    this.replaceChildren(article);
  }
}

customElements.define("user-card", UserCard);

这属于 Web Components 的一部分。复杂组件还会涉及:

  • Shadow DOM
  • 插槽 slot
  • 自定义事件
  • 属性变化监听
  • 样式隔离
  • 服务端渲染兼容性

15. HTML DOM 基础

浏览器读取 HTML 后,会将文档解析为 DOM,即 Document Object Model。

例如:

html 复制代码
<main>
  <h1>HTML5 教程</h1>
  <p>欢迎学习 HTML。</p>
</main>

可以理解为:

text 复制代码
document
└─ html
   ├─ head
   └─ body
      └─ main
         ├─ h1
         │  └─ "HTML5 教程"
         └─ p
            └─ "欢迎学习 HTML。"

JavaScript 可以读取和修改 DOM:

javascript 复制代码
const heading = document.querySelector("h1");

heading.textContent = "HTML5 工程实践";
heading.classList.add("page-title");
heading.setAttribute("data-status", "updated");

15.1 常用 DOM 查询

javascript 复制代码
document.querySelector("#app");
document.querySelector(".card");
document.querySelectorAll("[data-action]");
document.getElementById("app");

15.2 创建节点

javascript 复制代码
const listItem = document.createElement("li");
listItem.textContent = "HTML5";

document.querySelector("ul").append(listItem);

15.3 事件监听

javascript 复制代码
const button = document.querySelector("#save-button");

button.addEventListener("click", () => {
  console.log("保存按钮被点击");
});

15.4 事件委托

当列表项很多或会动态变化时,可以把事件监听放在父元素上:

html 复制代码
<ul id="task-list">
  <li>
    学习 HTML
    <button type="button" data-action="delete">删除</button>
  </li>
</ul>
javascript 复制代码
const taskList = document.querySelector("#task-list");

taskList.addEventListener("click", (event) => {
  const button = event.target.closest("[data-action='delete']");

  if (!button) {
    return;
  }

  button.closest("li")?.remove();
});

16. SEO 与页面元数据

16.1 页面标题与描述

html 复制代码
<title>HTML5 从入门到工程实践 - 前端技术博客</title>

<meta
  name="description"
  content="系统学习 HTML5 语法、语义化、表单、多媒体、性能、安全和工程化实践。"
>

16.2 规范地址

html 复制代码
<link
  rel="canonical"
  href="https://example.com/articles/html5-guide/"
>

当多个 URL 可以访问相同或高度相似的内容时,规范地址可以向搜索引擎表达首选地址。

16.3 Robots

html 复制代码
<meta name="robots" content="index,follow">

不希望测试页面进入搜索结果时可以使用:

html 复制代码
<meta name="robots" content="noindex,nofollow">

但敏感内容不能依赖 noindex 保护,必须通过服务端权限控制。

16.4 Open Graph

Open Graph 不是 HTML 核心语义的一部分,但经常用于社交平台分享预览:

html 复制代码
<meta property="og:type" content="article">
<meta property="og:title" content="HTML5 从入门到工程实践">
<meta
  property="og:description"
  content="系统学习现代 HTML 页面开发。"
>
<meta
  property="og:image"
  content="https://example.com/images/html5-cover.webp"
>
<meta
  property="og:url"
  content="https://example.com/articles/html5-guide/"
>

16.5 结构化数据

可以通过 JSON-LD 提供机器可读的结构化信息:

html 复制代码
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Article",
  "headline": "HTML5 从入门到工程实践",
  "datePublished": "2026-08-23",
  "dateModified": "2026-08-23",
  "author": {
    "@type": "Person",
    "name": "示例作者"
  }
}
</script>

结构化数据必须与页面真实可见内容一致,不能填写虚假评分、作者或商品信息。

16.6 SEO 的核心仍然是内容

HTML 层面应重点保证:

  • 页面有明确标题
  • 标题层级合理
  • 主要内容直接存在于页面中
  • 链接可以正常访问
  • 图片具有正确替代文本
  • 页面加载速度合理
  • 移动端可正常使用
  • URL 稳定且语义清晰
  • 不制造重复、隐藏或虚假内容

17. Web 无障碍

Web 无障碍通常称为 Accessibility,也常缩写为 A11y。

它的目标是让不同能力、不同设备和不同交互方式的用户都能使用网站。

W3C 指南可参考:

Developing for Web Accessibility

17.1 优先使用原生元素

不推荐:

html 复制代码
<div class="button" onclick="submitForm()">
  提交
</div>

推荐:

html 复制代码
<button type="submit">提交</button>

原生按钮天然具有:

  • 键盘支持
  • 焦点能力
  • 按钮语义
  • 禁用状态
  • 表单行为
  • 辅助技术支持

17.2 页面语言

html 复制代码
<html lang="zh-CN">

局部外语内容可以单独标记:

html 复制代码
<p>
  HTML 的全称是
  <span lang="en">HyperText Markup Language</span>。
</p>

17.3 跳过重复导航

html 复制代码
<a class="skip-link" href="#main-content">
  跳到主要内容
</a>

<nav aria-label="主导航">
  <!-- 导航 -->
</nav>

<main id="main-content">
  <!-- 主要内容 -->
</main>
css 复制代码
.skip-link {
  position: absolute;
  top: 0;
  left: 0;
  padding: 8px 12px;
  color: #fff;
  background: #111827;
  transform: translateY(-120%);
}

.skip-link:focus {
  transform: translateY(0);
}

17.4 保留可见焦点

不要直接删除焦点轮廓:

css 复制代码
/* 不推荐 */
button:focus {
  outline: none;
}

可以提供更清晰的焦点样式:

css 复制代码
:focus-visible {
  outline: 3px solid #f59e0b;
  outline-offset: 3px;
}

17.5 动态消息

html 复制代码
<p id="save-status" role="status" aria-live="polite"></p>
javascript 复制代码
const status = document.querySelector("#save-status");

status.textContent = "保存成功";

对于普通状态更新,role="status"aria-live="polite" 可以让辅助技术在适当时机朗读信息。

不要滥用 aria-live,频繁更新会不断打断用户。

17.6 ARIA 的使用原则

ARIA 用于补充语义,不应该替代已有的原生 HTML。

不推荐:

html 复制代码
<div role="button" tabindex="0">保存</div>

推荐:

html 复制代码
<button type="button">保存</button>

一个实用原则是:

如果原生 HTML 元素已经能够表达语义和行为,就优先使用原生元素。

17.7 无障碍检查清单

  • 页面是否设置正确语言
  • 标题层级是否合理
  • 是否只有视觉样式而没有语义
  • 表单是否拥有标签
  • 错误信息是否能被读取
  • 图片是否具有合适的 alt
  • 纯装饰图片是否使用空 alt
  • 所有功能是否能通过键盘操作
  • 焦点是否清晰可见
  • 对话框是否正确管理焦点
  • 视频是否提供字幕
  • 颜色是否是传递信息的唯一方式
  • 动态内容是否提供状态提示
  • 放大页面后是否仍然可以使用

18. HTML 安全实践

HTML 本身看起来简单,但错误地插入外部数据会产生严重安全问题。

18.1 防止 XSS

危险示例:

javascript 复制代码
const comment = getCommentFromServer();

document.querySelector("#comment").innerHTML = comment;

如果 comment 包含:

html 复制代码
<img src="invalid" onerror="alert('XSS')">

它可能被浏览器作为 HTML 执行。

只需要显示文本时,应使用:

javascript 复制代码
document.querySelector("#comment").textContent = comment;

如果业务必须渲染富文本,应使用经过安全评估的 HTML 清理方案,并明确允许哪些标签和属性。

18.2 不要拼接不可信 HTML

危险示例:

javascript 复制代码
container.innerHTML = `
  <a href="${userUrl}">${userName}</a>
`;

更安全的做法:

javascript 复制代码
const link = document.createElement("a");

link.textContent = userName;

const url = new URL(userUrl, window.location.origin);

if (!["http:", "https:"].includes(url.protocol)) {
  throw new Error("不支持的链接协议");
}

link.href = url.href;
container.replaceChildren(link);

需要根据数据所处的上下文分别处理:

  • HTML 文本上下文
  • HTML 属性上下文
  • URL 上下文
  • CSS 上下文
  • JavaScript 上下文

一种转义方式不能安全覆盖所有上下文。

18.3 内容安全策略

内容安全策略通常应通过 HTTP 响应头配置:

http 复制代码
Content-Security-Policy: default-src 'self'; script-src 'self'; style-src 'self'; img-src 'self' data: https:; object-src 'none'; base-uri 'self'; frame-ancestors 'none'

CSP 可以降低部分内容注入攻击的影响,但它不是输入校验和输出编码的替代品。

18.4 iframe 沙箱

html 复制代码
<iframe
  src="https://example.com/embed/"
  title="第三方内容"
  sandbox="allow-scripts"
  loading="lazy"
></iframe>

sandbox 可以限制嵌入页面的能力。不要为了让第三方页面正常运行,就不加分析地开放所有权限。

尤其应谨慎组合:

text 复制代码
allow-scripts
allow-same-origin

具体权限必须根据嵌入内容的可信度和功能需求设计。

18.5 表单安全

前端表单不能代替服务端安全控制。

服务端必须处理:

  • 身份认证
  • 权限校验
  • CSRF 防护
  • 输入校验
  • 输出编码
  • 文件类型和大小校验
  • 接口频率限制
  • 重复提交和幂等性
  • 审计日志

18.6 不在 HTML 中存放秘密

下面的信息不应该直接写入前端 HTML 或 JavaScript:

  • 数据库密码
  • 私钥
  • 管理员 Token
  • 云服务密钥
  • 可绕过权限的内部接口凭据

发送到浏览器的内容都应视为用户可以查看和修改。


19. HTML 页面性能优化

19.1 脚本加载方式

普通脚本
html 复制代码
<script src="/scripts/app.js"></script>

普通经典脚本可能阻塞 HTML 解析,直到脚本下载并执行完成。

defer
html 复制代码
<script defer src="/scripts/app.js"></script>

特点:

  • 与 HTML 解析并行下载
  • 在文档解析完成后执行
  • 多个 defer 脚本通常保持文档顺序
async
html 复制代码
<script async src="/scripts/analytics.js"></script>

特点:

  • 与 HTML 解析并行下载
  • 下载完成后尽快执行
  • 多个脚本之间不保证执行顺序

适合相对独立的脚本,例如部分统计脚本。

模块脚本
html 复制代码
<script type="module" src="/scripts/app.js"></script>

模块脚本默认具有类似延迟执行的行为,并支持 importexport

19.2 图片性能

建议:

  • 使用合适的图片尺寸
  • 优先评估 WebP 或 AVIF
  • 为图片写明 widthheight
  • 首屏外图片使用 loading="lazy"
  • 通过 srcset 提供多尺寸资源
  • 不要把大图只靠 CSS 缩小
  • 首屏关键图片不要随意延迟加载
  • 压缩图片但保留可接受质量

19.3 资源提示

预加载关键资源:

html 复制代码
<link
  rel="preload"
  href="/fonts/site-font.woff2"
  as="font"
  type="font/woff2"
  crossorigin
>

预连接第三方源:

html 复制代码
<link rel="preconnect" href="https://cdn.example.com">

资源提示不是越多越好。错误预加载会抢占带宽,反而拖慢真正重要的资源。

19.4 减少 DOM 复杂度

避免无意义的深层嵌套:

html 复制代码
<!-- 不推荐 -->
<div>
  <div>
    <div>
      <div>
        <p>内容</p>
      </div>
    </div>
  </div>
</div>

可以简化为:

html 复制代码
<section>
  <p>内容</p>
</section>

复杂 DOM 会增加:

  • HTML 解析成本
  • CSS 匹配成本
  • 布局与绘制成本
  • JavaScript 查询和更新成本
  • 维护难度

19.5 减少布局偏移

图片和嵌入内容应预留空间:

html 复制代码
<img
  src="/images/banner.webp"
  alt="活动横幅"
  width="1440"
  height="600"
>

不要等 JavaScript 加载后才突然插入没有预留区域的大块内容。

19.6 性能需要测量

常见工具:

  • Chrome DevTools Network
  • Chrome DevTools Performance
  • Lighthouse
  • PageSpeed Insights
  • WebPageTest
  • 浏览器 Performance API

重点指标通常包括:

  • LCP:最大内容绘制
  • INP:交互响应
  • CLS:累计布局偏移

优化前应先测量,找到真正的瓶颈,再进行针对性修改。


20. 渐进增强与兼容性

渐进增强的核心思想是:

  1. 先提供可以正常工作的基础内容
  2. 再添加样式和高级交互
  3. 高级能力不可用时,核心功能仍可完成

20.1 保留基础表单能力

html 复制代码
<form action="/search" method="get">
  <label for="keyword">关键词</label>
  <input id="keyword" name="keyword" type="search">
  <button type="submit">搜索</button>
</form>

即使 JavaScript 加载失败,用户仍可以提交搜索。

JavaScript 可以在此基础上增加:

  • 即时搜索
  • 加载状态
  • 自动完成
  • 无刷新更新

20.2 功能检测

不要仅根据浏览器名称判断功能:

javascript 复制代码
if ("showModal" in HTMLDialogElement.prototype) {
  console.log("支持原生 dialog");
}

对于 CSS,可以使用:

css 复制代码
@supports (display: grid) {
  .layout {
    display: grid;
    grid-template-columns: 2fr 1fr;
  }
}

20.3 回退内容

html 复制代码
<video controls>
  <source src="/video/course.mp4" type="video/mp4">

  您的浏览器无法播放视频,
  <a href="/video/course.mp4">请下载视频</a>。
</video>

21. 完整工程项目实战

下面实现一个简单的"技术文章收藏夹",包含:

  • 语义化页面结构
  • 表单与原生校验
  • template
  • dialog
  • 本地存储
  • 动态状态提示
  • 安全 DOM 操作
  • 响应式布局
  • 基本无障碍支持

21.1 项目目录

text 复制代码
html5-reading-list/
├─ index.html
├─ styles.css
└─ app.js

21.2 index.html

html 复制代码
<!doctype html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta
    name="viewport"
    content="width=device-width, initial-scale=1"
  >
  <meta
    name="description"
    content="使用 HTML5 构建的技术文章收藏夹"
  >

  <title>技术文章收藏夹</title>

  <link rel="stylesheet" href="./styles.css">
  <script type="module" src="./app.js"></script>
</head>
<body>
  <a class="skip-link" href="#main-content">
    跳到主要内容
  </a>

  <header class="site-header">
    <div class="container">
      <h1>技术文章收藏夹</h1>
      <p>保存值得阅读的技术文章</p>
    </div>
  </header>

  <main id="main-content" class="container">
    <section aria-labelledby="add-heading">
      <h2 id="add-heading">添加文章</h2>

      <form id="article-form">
        <div class="form-field">
          <label for="article-title">文章标题</label>
          <input
            id="article-title"
            name="title"
            type="text"
            minlength="2"
            maxlength="100"
            autocomplete="off"
            required
          >
        </div>

        <div class="form-field">
          <label for="article-url">文章地址</label>
          <input
            id="article-url"
            name="url"
            type="url"
            placeholder="https://example.com/article"
            required
          >
        </div>

        <div class="form-field">
          <label for="article-category">分类</label>
          <select
            id="article-category"
            name="category"
            required
          >
            <option value="">请选择分类</option>
            <option value="HTML">HTML</option>
            <option value="CSS">CSS</option>
            <option value="JavaScript">JavaScript</option>
            <option value="工程化">工程化</option>
            <option value="其他">其他</option>
          </select>
        </div>

        <button type="submit">添加文章</button>
      </form>

      <p
        id="status-message"
        class="status"
        role="status"
        aria-live="polite"
      ></p>
    </section>

    <section aria-labelledby="list-heading">
      <div class="section-heading">
        <h2 id="list-heading">收藏列表</h2>

        <button
          id="open-clear-dialog"
          class="danger-button"
          type="button"
        >
          清空全部
        </button>
      </div>

      <p id="empty-message">
        暂无收藏文章,请先添加一篇文章。
      </p>

      <ul id="article-list" class="article-list"></ul>
    </section>
  </main>

  <template id="article-template">
    <li class="article-item">
      <article>
        <header>
          <h3 class="article-title"></h3>
          <span class="article-category"></span>
        </header>

        <p>
          <a
            class="article-link"
            target="_blank"
            rel="noopener noreferrer"
          ></a>
        </p>

        <footer>
          <time class="article-time"></time>

          <button
            class="delete-button"
            type="button"
            data-action="delete"
          >
            删除
          </button>
        </footer>
      </article>
    </li>
  </template>

  <dialog id="clear-dialog">
    <h2>确认清空全部收藏?</h2>
    <p>该操作无法撤销。</p>

    <form method="dialog">
      <button value="cancel">取消</button>
      <button class="danger-button" value="confirm">
        确认清空
      </button>
    </form>
  </dialog>

  <footer class="site-footer">
    <div class="container">
      <p>
        HTML5 工程实践示例 ·
        <time datetime="2026">2026</time>
      </p>
    </div>
  </footer>
</body>
</html>

21.3 styles.css

css 复制代码
:root {
  color-scheme: light;
  font-family:
    Inter,
    "PingFang SC",
    "Microsoft YaHei",
    sans-serif;
  line-height: 1.6;
  color: #1f2937;
  background: #f3f4f6;
}

* {
  box-sizing: border-box;
}

body {
  min-width: 320px;
  margin: 0;
}

button,
input,
select {
  font: inherit;
}

button {
  padding: 10px 18px;
  color: #fff;
  cursor: pointer;
  background: #2563eb;
  border: 0;
  border-radius: 8px;
}

button:hover {
  background: #1d4ed8;
}

button:focus-visible,
input:focus-visible,
select:focus-visible,
a:focus-visible {
  outline: 3px solid #f59e0b;
  outline-offset: 3px;
}

.container {
  width: min(960px, calc(100% - 32px));
  margin-inline: auto;
}

.skip-link {
  position: fixed;
  z-index: 100;
  top: 8px;
  left: 8px;
  padding: 8px 12px;
  color: #fff;
  background: #111827;
  border-radius: 6px;
  transform: translateY(-150%);
}

.skip-link:focus {
  transform: translateY(0);
}

.site-header {
  padding: 48px 0;
  color: #fff;
  background: #1d4ed8;
}

.site-header h1,
.site-header p {
  margin: 0;
}

main {
  display: grid;
  gap: 24px;
  padding-block: 32px;
}

section {
  padding: 24px;
  background: #fff;
  border-radius: 12px;
  box-shadow: 0 6px 20px rgb(15 23 42 / 8%);
}

.form-field {
  display: grid;
  gap: 6px;
  margin-bottom: 16px;
}

.form-field label {
  font-weight: 600;
}

.form-field input,
.form-field select {
  width: 100%;
  padding: 10px 12px;
  border: 1px solid #9ca3af;
  border-radius: 8px;
}

.status {
  min-height: 1.6em;
  margin-bottom: 0;
  color: #166534;
}

.section-heading {
  display: flex;
  gap: 16px;
  align-items: center;
  justify-content: space-between;
}

.article-list {
  display: grid;
  gap: 16px;
  padding: 0;
  list-style: none;
}

.article-item {
  padding: 16px;
  border: 1px solid #d1d5db;
  border-radius: 10px;
}

.article-item header,
.article-item footer {
  display: flex;
  gap: 12px;
  align-items: center;
  justify-content: space-between;
}

.article-title {
  margin: 0;
}

.article-category {
  padding: 2px 8px;
  color: #1e40af;
  background: #dbeafe;
  border-radius: 999px;
}

.article-link {
  overflow-wrap: anywhere;
}

.article-time {
  color: #6b7280;
}

.danger-button,
.delete-button {
  background: #b91c1c;
}

.danger-button:hover,
.delete-button:hover {
  background: #991b1b;
}

dialog {
  width: min(420px, calc(100% - 32px));
  padding: 24px;
  border: 0;
  border-radius: 12px;
}

dialog::backdrop {
  background: rgb(15 23 42 / 60%);
}

dialog form {
  display: flex;
  gap: 12px;
  justify-content: flex-end;
}

.site-footer {
  padding: 24px 0;
  color: #d1d5db;
  text-align: center;
  background: #111827;
}

@media (max-width: 600px) {
  .section-heading,
  .article-item header,
  .article-item footer {
    align-items: stretch;
    flex-direction: column;
  }
}

21.4 app.js

javascript 复制代码
const STORAGE_KEY = "html5-reading-list";

const form = document.querySelector("#article-form");
const titleInput = document.querySelector("#article-title");
const list = document.querySelector("#article-list");
const template = document.querySelector("#article-template");
const emptyMessage = document.querySelector("#empty-message");
const statusMessage = document.querySelector("#status-message");

const clearDialog = document.querySelector("#clear-dialog");
const openClearDialogButton = document.querySelector(
  "#open-clear-dialog"
);

let articles = loadArticles();

renderArticles();

form.addEventListener("submit", (event) => {
  event.preventDefault();

  if (!form.checkValidity()) {
    form.reportValidity();
    return;
  }

  const formData = new FormData(form);
  const title = String(formData.get("title") ?? "").trim();
  const rawUrl = String(formData.get("url") ?? "").trim();
  const category = String(formData.get("category") ?? "").trim();

  let url;

  try {
    url = new URL(rawUrl);
  } catch {
    showStatus("请输入有效的文章地址。");
    return;
  }

  if (!["http:", "https:"].includes(url.protocol)) {
    showStatus("文章地址只支持 HTTP 或 HTTPS。");
    return;
  }

  articles.unshift({
    id: createId(),
    title,
    url: url.href,
    category,
    createdAt: new Date().toISOString()
  });

  saveArticles();
  renderArticles();

  form.reset();
  titleInput.focus();

  showStatus(`已添加文章:${title}`);
});

list.addEventListener("click", (event) => {
  const deleteButton = event.target.closest(
    "[data-action='delete']"
  );

  if (!deleteButton) {
    return;
  }

  const item = deleteButton.closest("[data-id]");

  if (!item) {
    return;
  }

  const articleId = item.dataset.id;
  const article = articles.find(({ id }) => id === articleId);

  articles = articles.filter(({ id }) => id !== articleId);

  saveArticles();
  renderArticles();

  showStatus(
    article
      ? `已删除文章:${article.title}`
      : "文章已删除"
  );
});

openClearDialogButton.addEventListener("click", () => {
  if (articles.length === 0) {
    showStatus("当前没有需要清空的文章。");
    return;
  }

  clearDialog.showModal();
});

clearDialog.addEventListener("close", () => {
  if (clearDialog.returnValue !== "confirm") {
    return;
  }

  articles = [];
  saveArticles();
  renderArticles();

  showStatus("已清空全部收藏文章。");
});

function renderArticles() {
  const fragment = document.createDocumentFragment();

  for (const article of articles) {
    const item = template.content.firstElementChild.cloneNode(true);

    item.dataset.id = article.id;

    const title = item.querySelector(".article-title");
    const category = item.querySelector(".article-category");
    const link = item.querySelector(".article-link");
    const time = item.querySelector(".article-time");
    const deleteButton = item.querySelector(".delete-button");

    title.textContent = article.title;
    category.textContent = article.category;

    link.href = article.url;
    link.textContent = article.url;

    time.dateTime = article.createdAt;
    time.textContent = formatDate(article.createdAt);

    deleteButton.setAttribute(
      "aria-label",
      `删除文章:${article.title}`
    );

    fragment.append(item);
  }

  list.replaceChildren(fragment);

  const isEmpty = articles.length === 0;

  emptyMessage.hidden = !isEmpty;
  openClearDialogButton.disabled = isEmpty;
}

function loadArticles() {
  try {
    const value = localStorage.getItem(STORAGE_KEY);

    if (!value) {
      return [];
    }

    const parsedValue = JSON.parse(value);

    return Array.isArray(parsedValue)
      ? parsedValue.filter(isValidArticle)
      : [];
  } catch (error) {
    console.error("读取本地数据失败", error);
    return [];
  }
}

function saveArticles() {
  try {
    localStorage.setItem(
      STORAGE_KEY,
      JSON.stringify(articles)
    );
  } catch (error) {
    console.error("保存本地数据失败", error);
    showStatus("数据保存失败,请检查浏览器存储设置。");
  }
}

function isValidArticle(value) {
  return (
    value &&
    typeof value.id === "string" &&
    typeof value.title === "string" &&
    typeof value.url === "string" &&
    typeof value.category === "string" &&
    typeof value.createdAt === "string"
  );
}

function createId() {
  if (typeof crypto.randomUUID === "function") {
    return crypto.randomUUID();
  }

  return `${Date.now()}-${Math.random().toString(16).slice(2)}`;
}

function formatDate(value) {
  const date = new Date(value);

  return new Intl.DateTimeFormat("zh-CN", {
    dateStyle: "medium",
    timeStyle: "short"
  }).format(date);
}

function showStatus(message) {
  statusMessage.textContent = message;
}

21.5 运行项目

最简单的方式是直接打开 index.html

为了更接近真实开发环境,可以在项目目录启动本地服务器:

bash 复制代码
npx serve .

也可以使用 Vite:

bash 复制代码
npm create vite@latest

对于已有静态目录,可以安装 Vite:

bash 复制代码
npm init -y
npm install --save-dev vite
npx vite

不要直接双击 HTML 来测试所有模块化功能,因为 file:// 环境与真实 HTTP 环境存在差异。


22. HTML 工程化工具

22.1 代码格式化

安装 Prettier:

bash 复制代码
npm install --save-dev prettier

格式化文件:

bash 复制代码
npx prettier --write "**/*.{html,css,js,json,md}"

检查格式但不修改:

bash 复制代码
npx prettier --check "**/*.{html,css,js,json,md}"

.prettierignore 示例:

text 复制代码
dist
coverage
node_modules

22.2 HTML 静态检查

安装 HTML Validate:

bash 复制代码
npm install --save-dev html-validate

执行检查:

bash 复制代码
npx html-validate "src/**/*.html" "*.html"

.htmlvalidate.json 示例:

json 复制代码
{
  "extends": ["html-validate:recommended"]
}

22.3 package.json 脚本

json 复制代码
{
  "name": "html5-reading-list",
  "private": true,
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview",
    "format": "prettier --write .",
    "format:check": "prettier --check .",
    "lint:html": "html-validate \"*.html\""
  },
  "devDependencies": {
    "html-validate": "latest",
    "prettier": "latest",
    "vite": "latest"
  }
}

实际团队项目应提交锁文件,通过锁文件固定安装结果,而不是每次安装时任意漂移依赖版本。

22.4 编辑器配置

.editorconfig

ini 复制代码
root = true

[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
trim_trailing_whitespace = true

[*.md]
trim_trailing_whitespace = false

22.5 推荐的 CI 检查

持续集成中可以执行:

bash 复制代码
npm ci
npm run format:check
npm run lint:html
npm run build

还可以继续增加:

  • JavaScript 静态检查
  • CSS 静态检查
  • 单元测试
  • 端到端测试
  • 无障碍自动检查
  • 链接有效性检查
  • 构建产物体积检查

23. HTML 与后端接口协作

23.1 前后端共同关注的数据契约

前端表单:

html 复制代码
<form id="register-form">
  <label for="register-email">邮箱</label>
  <input
    id="register-email"
    name="email"
    type="email"
    required
  >

  <label for="register-password">密码</label>
  <input
    id="register-password"
    name="password"
    type="password"
    minlength="8"
    required
  >

  <button type="submit">注册</button>
</form>

发送 JSON:

javascript 复制代码
const form = document.querySelector("#register-form");

form.addEventListener("submit", async (event) => {
  event.preventDefault();

  if (!form.checkValidity()) {
    form.reportValidity();
    return;
  }

  const formData = new FormData(form);

  const payload = {
    email: String(formData.get("email") ?? ""),
    password: String(formData.get("password") ?? "")
  };

  const response = await fetch("/api/users", {
    method: "POST",
    headers: {
      "Content-Type": "application/json"
    },
    body: JSON.stringify(payload)
  });

  const result = await response.json().catch(() => null);

  if (!response.ok) {
    throw new Error(
      result?.message ?? `注册失败:${response.status}`
    );
  }

  console.log("注册成功", result);
});

前后端需要明确:

  • 字段名称
  • 字段类型
  • 是否必填
  • 长度和范围
  • 时间格式
  • 空值含义
  • 错误码
  • 错误信息格式
  • 身份认证方式
  • 幂等规则
  • 文件上传限制

23.2 正确展示后端错误

html 复制代码
<p
  id="form-error"
  role="alert"
  hidden
></p>
javascript 复制代码
function showFormError(message) {
  const errorElement = document.querySelector("#form-error");

  errorElement.textContent = message;
  errorElement.hidden = false;
}

不要将后端返回内容直接写入 innerHTML

23.3 防止重复提交

javascript 复制代码
const submitButton = form.querySelector(
  "button[type='submit']"
);

form.addEventListener("submit", async (event) => {
  event.preventDefault();

  if (submitButton.disabled) {
    return;
  }

  submitButton.disabled = true;
  submitButton.textContent = "提交中......";

  try {
    await submitData();
  } finally {
    submitButton.disabled = false;
    submitButton.textContent = "提交";
  }
});

前端禁用按钮只能改善体验。真正的重复请求和资金类操作仍需由服务端通过幂等机制处理。


24. 测试、验证与质量检查

24.1 HTML 语法验证

可以使用 W3C Markup Validation Service:

https://validator.w3.org/

它可以发现:

  • 标签嵌套错误
  • 无效属性
  • 重复 id
  • 元素使用位置错误
  • 缺少必要属性

24.2 浏览器开发者工具

重点检查:

Elements
  • DOM 是否符合预期
  • 标签是否被浏览器自动修正
  • ARIA 属性是否正确
  • 元素尺寸和样式是否正确
Console
  • JavaScript 是否报错
  • 资源是否存在安全警告
  • 是否存在废弃 API 提示
Network
  • 是否有 404
  • 文件是否过大
  • 缓存是否生效
  • Content-Type 是否正确
  • 请求是否重复
  • 图片是否加载了不必要的大尺寸版本
Lighthouse

检查:

  • Performance
  • Accessibility
  • Best Practices
  • SEO

自动化评分不是最终质量结论,但适合作为问题发现工具。

24.3 键盘测试

只使用键盘完成以下操作:

  1. 使用 Tab 正向移动
  2. 使用 Shift + Tab 反向移动
  3. 使用 Enter 激活链接
  4. 使用 Space 或 Enter 激活按钮
  5. 操作单选框、复选框和下拉框
  6. 打开和关闭对话框
  7. 确认焦点不会消失
  8. 确认没有键盘陷阱

24.4 响应式测试

至少检查:

  • 320px 左右的小屏
  • 常见手机宽度
  • 平板宽度
  • 笔记本宽度
  • 大屏显示器
  • 横屏模式
  • 页面放大到 200%
  • 较长中文和英文内容
  • 系统大字体设置

24.5 自动化端到端测试

安装 Playwright:

bash 复制代码
npm install --save-dev @playwright/test
npx playwright install

示例:

javascript 复制代码
import { test, expect } from "@playwright/test";

test("可以添加一篇文章", async ({ page }) => {
  await page.goto("http://localhost:5173");

  await page.getByLabel("文章标题").fill("HTML5 教程");
  await page
    .getByLabel("文章地址")
    .fill("https://example.com/html5");
  await page.getByLabel("分类").selectOption("HTML");

  await page.getByRole("button", {
    name: "添加文章"
  }).click();

  await expect(
    page.getByRole("heading", {
      name: "HTML5 教程"
    })
  ).toBeVisible();

  await expect(
    page.getByRole("status")
  ).toContainText("已添加文章");
});

24.6 上线前检查清单

  • HTML 文档声明正确
  • 页面语言正确
  • 字符编码为 UTF-8
  • viewport 配置正确
  • 每个页面拥有合适标题
  • 页面描述准确
  • 标题层级合理
  • id 没有重复
  • 图片拥有合适的 alt
  • 图片宽高明确
  • 表单控件拥有标签
  • 按钮类型明确
  • 键盘可以完成主要操作
  • 焦点样式清晰
  • 动态消息可以被感知
  • 外部输入没有直接写入 innerHTML
  • 没有把密钥写入前端
  • 页面不存在 404 资源
  • 移动端布局正常
  • 构建和自动化测试通过
  • HTTP 安全头已配置
  • 生产环境使用 HTTPS

25. 构建与部署

25.1 构建

使用 Vite:

bash 复制代码
npm run build

默认会生成类似目录:

text 复制代码
dist/
├─ index.html
└─ assets/
   ├─ index-a1b2c3.js
   └─ index-d4e5f6.css

部署时应发布构建后的目录,而不是将开发依赖一起上传。

25.2 部署路径

如果网站部署在:

text 复制代码
https://example.com/

资源根路径可以是:

html 复制代码
<script src="/assets/app.js"></script>

如果部署在子目录:

text 复制代码
https://example.com/docs/

则必须确认构建工具的基础路径配置,否则 /assets/app.js 会请求:

text 复制代码
https://example.com/assets/app.js

而不是:

text 复制代码
https://example.com/docs/assets/app.js

25.3 MIME 类型

服务端应返回正确的 Content-Type,例如:

text 复制代码
.html   text/html
.css    text/css
.js     text/javascript
.json   application/json
.svg    image/svg+xml
.webp   image/webp
.woff2  font/woff2

错误的 MIME 类型可能导致模块脚本、样式表或字体无法加载。

25.4 缓存策略

带内容哈希的静态资源:

text 复制代码
app.a1b2c3.js
styles.d4e5f6.css

可以设置较长缓存:

http 复制代码
Cache-Control: public, max-age=31536000, immutable

HTML 文件通常需要更谨慎的缓存策略,因为它负责引用最新资源:

http 复制代码
Cache-Control: no-cache

这里的 no-cache 不等于完全不存储,而是要求使用前向服务器验证资源是否仍然有效。

25.5 压缩

服务端应针对文本资源启用 Brotli 或 Gzip:

  • HTML
  • CSS
  • JavaScript
  • JSON
  • SVG

已经压缩的图片和视频通常不需要再次进行通用压缩。

25.6 常用安全响应头

根据项目需求评估:

http 复制代码
Content-Security-Policy: default-src 'self'
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
Permissions-Policy: camera=(), microphone=(), geolocation=()
Strict-Transport-Security: max-age=31536000; includeSubDomains

安全头应该在测试环境验证,尤其是 CSP。配置过严可能阻止合法资源加载,配置过松则失去保护价值。


26. 常见错误与反例

26.1 使用 div 实现所有元素

不推荐:

html 复制代码
<div class="title">文章标题</div>
<div class="button">提交</div>

推荐:

html 复制代码
<h1>文章标题</h1>
<button type="submit">提交</button>

26.2 根据默认样式选择标签

不推荐:

html 复制代码
<h4>我只是想要小一点的标题</h4>

应该根据内容层级选择标题,再用 CSS 控制大小。

26.3 使用 br 制造间距

不推荐:

html 复制代码
<p>第一段</p>
<br>
<br>
<br>
<p>第二段</p>

推荐:

css 复制代码
p {
  margin-block: 1em;
}

26.4 图片缺少替代文本

不推荐:

html 复制代码
<img src="payment-result.png">

推荐:

html 复制代码
<img
  src="payment-result.png"
  alt="支付成功,订单号为 202608230001"
>

26.5 使用 placeholder 代替标签

不推荐:

html 复制代码
<input type="email" placeholder="请输入邮箱">

推荐:

html 复制代码
<label for="email">邮箱</label>
<input
  id="email"
  name="email"
  type="email"
  placeholder="name@example.com"
>

26.6 表单按钮没有声明类型

不推荐:

html 复制代码
<form>
  <button id="preview">预览</button>
</form>

浏览器可能把它当成提交按钮。

推荐:

html 复制代码
<button id="preview" type="button">预览</button>

26.7 信任前端校验

错误认识:

HTML 已经设置 required,后端就不需要验证。

前端校验可以被绕过,服务端必须重新验证全部外部输入。

26.8 直接使用 innerHTML

不推荐:

javascript 复制代码
result.innerHTML = userInput;

仅显示文本时使用:

javascript 复制代码
result.textContent = userInput;

26.9 使用表格完成页面布局

不推荐:

html 复制代码
<table>
  <tr>
    <td>左侧导航</td>
    <td>主要内容</td>
  </tr>
</table>

页面布局应使用语义化结构和 CSS:

html 复制代码
<div class="layout">
  <aside>左侧导航</aside>
  <main>主要内容</main>
</div>

26.10 认为浏览器能显示就代表代码正确

浏览器具有强大的错误恢复能力,会自动修正部分无效 HTML。

例如:

html 复制代码
<p>
  段落内容
  <div>错误嵌套的块级区域</div>
</p>

浏览器可能自动关闭 p,导致最终 DOM 与源代码看起来不一致。

因此需要结合:

  • HTML 验证工具
  • 浏览器 Elements 面板
  • 自动化检查

27. 常见面试题

27.1 HTML5 的文档声明是什么?

html 复制代码
<!doctype html>

它用于让浏览器按照现代标准模式解析页面。

27.2 什么是语义化 HTML?

语义化 HTML 是使用含义准确的元素描述内容结构,例如:

  • 使用 nav 表示导航
  • 使用 main 表示主要内容
  • 使用 article 表示可独立分发的内容
  • 使用 button 表示按钮
  • 使用 table 表示二维表格数据

语义化有利于可维护性、无障碍、搜索引擎理解和浏览器功能。

27.3 sectionarticlediv 有什么区别?

  • article:相对独立、可单独分发的内容
  • section:按主题划分的内容区域
  • div:没有额外语义的通用分组容器

27.4 idclass 有什么区别?

  • id 应在当前文档中保持唯一
  • class 可以被多个元素复用
  • id 常用于锚点、标签关联和精确查询
  • class 常用于样式和组件状态

27.5 asyncdefer 有什么区别?

defer

  • 并行下载
  • HTML 解析完成后执行
  • 通常保持脚本顺序

async

  • 并行下载
  • 下载完成后尽快执行
  • 不保证脚本顺序

模块脚本默认具有类似延迟执行的行为。

27.6 为什么图片要写 widthheight

它可以让浏览器在图片加载前计算宽高比例并预留空间,从而减少页面布局跳动。

27.7 alt="" 和不写 alt 有什么区别?

html 复制代码
<img src="line.svg" alt="">

alt 明确表示该图片是装饰性的,辅助技术通常可以忽略它。

不写 alt 可能让辅助技术尝试读取文件名或以其他不理想方式处理图片。

27.8 为什么前端校验不能代替后端校验?

用户可以:

  • 禁用 JavaScript
  • 修改 DOM
  • 修改网络请求
  • 直接调用后端接口
  • 编写脚本绕过页面

因此服务端必须将所有客户端数据视为不可信输入。

27.9 为什么不推荐使用 innerHTML 插入用户数据?

因为用户数据可能被解析为 HTML,进而注入脚本、事件属性或危险链接,形成 XSS。

只显示文本时应使用 textContent

27.10 localStorage 有什么特点?

  • 以字符串形式存储数据
  • 数据通常跨页面刷新保留
  • 同源页面共享
  • API 是同步的
  • 容量有限
  • 用户可以修改或清除
  • 不适合保存密码、Token 明文或可信业务状态

服务端权限判断绝不能依赖 localStorage 中的数据。


28. 学习路线

第一阶段:掌握基础语法

学习:

  • HTML 文档结构
  • 元素与属性
  • 标题和段落
  • 链接和图片
  • 列表和表格
  • 基础表单

目标:能够独立编写一个结构正确的静态页面。

第二阶段:理解语义化

学习:

  • header
  • nav
  • main
  • article
  • section
  • aside
  • footer
  • 标题层级
  • 内容模型

目标:页面不再是由大量无含义的 div 堆叠而成。

第三阶段:学习表单与多媒体

学习:

  • 各种 input 类型
  • 原生校验
  • label
  • fieldset
  • 文件上传
  • audio
  • video
  • track

目标:能够实现可用、可访问的业务表单和媒体页面。

第四阶段:掌握 DOM 与 JavaScript 协作

学习:

  • DOM 查询
  • DOM 创建和更新
  • 事件监听
  • 事件委托
  • template
  • FormData
  • Fetch API
  • 本地存储

目标:完成一个不依赖框架的小型交互项目。

第五阶段:学习工程质量

学习:

  • Web 无障碍
  • SEO
  • HTML 安全
  • 性能优化
  • 响应式设计
  • HTML 验证
  • 自动格式化
  • 静态检查
  • 自动化测试

目标:页面不仅"能显示",还要可维护、可测试、可访问和可上线。

第六阶段:进入框架和完整工程

在掌握 HTML 基础后,再学习:

  • CSS 布局与响应式设计
  • JavaScript 和 TypeScript
  • Vue、React 或其他框架
  • SSR 与 SSG
  • Web Components
  • 构建工具
  • 前后端接口设计
  • 自动化测试
  • CI/CD

框架会变化,但语义、表单、可访问性、安全和浏览器解析规则仍然是底层基础。


29. 总结

HTML5 的学习不能停留在"记标签"。

真正的工程能力包括:

  1. 使用正确的文档结构
  2. 根据内容含义选择元素
  3. 编写结构清晰的语义化页面
  4. 正确处理链接、图片、表格和表单
  5. 使用原生能力实现基础交互
  6. 让页面支持键盘和辅助技术
  7. 安全地处理用户和服务端数据
  8. 优化资源加载与页面性能
  9. 使用工具验证 HTML 质量
  10. 通过构建、测试和部署形成完整工程流程

学习 HTML 的一个重要原则是:

先用 HTML 表达正确的结构和语义,再使用 CSS 控制表现,最后使用 JavaScript 增强交互。

当 JavaScript 加载失败、CSS 没有生效,或者用户只能使用键盘和屏幕阅读器时,页面仍然应该尽可能保留清晰的内容和基础功能。

这才是现代 HTML 工程实践的真正价值。


30. 参考资料

相关推荐
人民广场吃泡面5 小时前
前端该重视的编程思想
前端
冬夜戏雪5 小时前
agent的trace 和eval
开发语言·前端·javascript
IT_陈寒5 小时前
Redis的DEL命令居然没删干净数据?这个坑我爬了半天
前端·人工智能·后端
kyriewen5 小时前
面试官让我现场用 AI 改一个真实 bug——他打断我的 3 个理由
前端·面试·ai编程
计算机魔术师6 小时前
斯坦福研究:AI 对入门级岗位冲击最大
前端
Simon_He6 小时前
一个渲染内核,五个框架包:我的流式 Markdown 渲染库是怎么长成“全家桶”的
前端·vue.js·markdown
小满zs7 小时前
关于HBuilderX+ 微信小程序开发工具启动的问题
前端·uni-app