HTML5 权威指南:从入门到精通

HTML5 权威指南:从入门到精通

资料来源:WHATWG HTML Living Standard(html.spec.whatwg.org)、MDN Web Docs、W3C WAI、W3Schools、freeCodeCamp 等权威资源综合整理

最新标准:HTML Living Standard(WHATWG 维护,2019 年起为唯一权威版本)


目录

  1. [HTML 历史与标准演进](#HTML 历史与标准演进)
  2. 文档基础结构
  3. [所有 HTML 元素速查](#所有 HTML 元素速查)
  4. [语义化 HTML5](#语义化 HTML5)
  5. 表单与输入控件
  6. 多媒体元素
  7. [HTML5 核心 API](#HTML5 核心 API)
  8. 全局属性与数据属性
  9. 可访问性(ARIA)
  10. [SEO 与 Meta 标签](#SEO 与 Meta 标签)
  11. 性能优化最佳实践
  12. 完整页面模板

一、HTML 历史与标准演进

1.1 关键时间线

复制代码
1991  Tim Berners-Lee 发布首份 HTML 文档(18 个标签)
1995  HTML 2.0 ------ 首个 IETF 标准
1997  HTML 3.2 / HTML 4.0  W3C 接管
1999  HTML 4.01  ------  最后一个"传统"版本
2000  XHTML 1.0  ------  XML 严格语法分支
2004  WHATWG 成立(Apple + Mozilla + Opera)
2008  HTML5 首个公开草案
2014  HTML5 正式成为 W3C 推荐标准(Recommendation)
2019  W3C 将 HTML 和 DOM 标准主导权移交 WHATWG
现在  HTML Living Standard(无版本号,持续更新)

1.2 两大机构对比

W3C WHATWG
全称 World Wide Web Consortium Web Hypertext Application Technology Working Group
核心成员 全球 400+ 组织 Apple、Google、Mozilla、Microsoft
维护文档 历史版本(HTML5 ~5.3 已退休) HTML Living Standard(当前权威)
规范 URL www.w3.org/TR/html/ html.spec.whatwg.org
更新方式 版本快照 持续滚动更新

1.3 Inmon vs Kimball 的 HTML 版本

版本 发布年份 状态
HTML 4.01 1999 已退休(2018)
XHTML 1.0 / 1.1 2000/2001 已退休(2018)
HTML5 2014 已退休(2018)
HTML 5.1 / 5.2 2016/2017 已退休(2021)
HTML Living Standard 2019 至今 ✅ 当前标准

关键认知<!DOCTYPE html> 就是现代 HTML 的声明,已无版本之分。


二、文档基础结构

2.1 最小化合法文档

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>页面标题</title>
</head>
<body>
  <p>Hello, World!</p>
</body>
</html>

逐行解释:

作用
<!DOCTYPE html> 触发标准模式(Standards Mode),避免怪异模式渲染
<html lang="zh-CN"> 根元素;lang 属性帮助屏幕阅读器、搜索引擎识别语言
<meta charset="UTF-8"> 字符编码声明,必须在 <head> 最顶部,防止乱码
<meta name="viewport" ...> 移动端视口控制,是响应式设计的基础
<title> 浏览器标签栏标题,也是 SEO 最重要的标签之一

2.2 <head> 内常用元素

html 复制代码
<head>
  <!-- 必须 -->
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>页面标题(≤60字)</title>

  <!-- SEO -->
  <meta name="description" content="页面描述(150-160字)">
  <meta name="author" content="作者名">
  <meta name="robots" content="index, follow">
  <link rel="canonical" href="https://example.com/page">

  <!-- 社交媒体 Open Graph -->
  <meta property="og:title" content="标题">
  <meta property="og:description" content="描述">
  <meta property="og:image" content="https://example.com/img.jpg">
  <meta property="og:url" content="https://example.com/page">
  <meta property="og:type" content="website">

  <!-- Twitter Card -->
  <meta name="twitter:card" content="summary_large_image">
  <meta name="twitter:title" content="标题">

  <!-- 样式表(阻塞渲染,放最前) -->
  <link rel="stylesheet" href="styles.css">

  <!-- 预加载关键资源 -->
  <link rel="preload" href="hero.jpg" as="image">
  <link rel="preload" href="font.woff2" as="font" type="font/woff2" crossorigin>

  <!-- DNS 预解析 -->
  <link rel="dns-prefetch" href="//api.example.com">
  <link rel="preconnect" href="https://fonts.googleapis.com">

  <!-- 网站图标 -->
  <link rel="icon" type="image/svg+xml" href="/favicon.svg">
  <link rel="apple-touch-icon" href="/apple-touch-icon.png">

  <!-- 结构化数据(JSON-LD) -->
  <script type="application/ld+json">
  {
    "@context": "https://schema.org",
    "@type": "WebPage",
    "name": "页面标题"
  }
  </script>
</head>

2.3 文档内容模型(Content Model)

HTML 规范将所有内容分为七类,决定哪些元素可以嵌套哪些元素:

内容类别 典型元素 说明
元数据内容 <link> <meta> <script> <style> 只能出现在 <head>
流式内容 几乎所有块级元素 可出现在 <body>
分区内容 <article> <aside> <nav> <section> 在文档大纲中创建新区块
标题内容 <h1>~<h6> <hgroup> 定义区块标题
短语内容 <a> <em> <strong> <span> 段落级行内元素
嵌入内容 <img> <video> <audio> <canvas> <iframe> 引入外部资源
交互内容 <a> <button> <input> <select> 用户可交互的元素

三、所有 HTML 元素速查

3.1 文档元数据

元素 作用
<html> 根元素
<head> 文档头部元数据容器
<title> 文档标题(必须唯一)
<base> 所有相对 URL 的基础 URL
<link> 外部资源链接关系
<meta> 各类元数据
<style> 内嵌 CSS
<script> 内嵌或外部 JavaScript
<noscript> 无脚本时的备用内容

3.2 分区(语义)元素

元素 语义 注意事项
<body> 文档主体 每页只有一个
<main> 页面主要内容 每页只能有一个可见 <main>
<header> 页眉/区块标题区域 可在多个区块中使用
<footer> 页脚/区块附属信息 可在多个区块中使用
<nav> 导航链接区域 仅用于主要导航,非所有链接集合
<section> 主题内容分区(通常有标题) 不要用来替代 <div>
<article> 独立自包含内容 可独立分发,如博客文章
<aside> 相关但非必要的附属内容 侧边栏、广告、引用
<figure> 独立的媒体单元 图表、代码块、图片
<figcaption> <figure> 的说明文字 <figure> 的第一个或最后一个子元素
<address> 联系信息 仅用于最近的 <article><body> 的作者联系信息
<hgroup> 标题组合(主标题+副标题) 包含 <h1>-<h6> 的组合
<search> 搜索区域 HTML Living Standard 新增

3.3 标题元素

html 复制代码
<h1>一级标题(每页仅一个,SEO 最重要)</h1>
<h2>二级标题</h2>
<h3>三级标题</h3>
<h4>四级标题</h4>
<h5>五级标题</h5>
<h6>六级标题</h6>

标题层级最佳实践:

  • <h1> 每页只出现一次,描述页面核心主题
  • 标题层级不可跳级(h1 → h3 是错误的)
  • 不要用标题元素来控制字体大小(那是 CSS 的职责)

3.4 文本块级元素

元素 作用
<p> 段落
<blockquote> 块级引用,cite 属性指向来源
<pre> 预格式化文本(保留空白和换行)
<hr> 主题分隔线(空元素)
<div> 无语义的块级容器(最后选择)
<details> 可展开的详细信息容器
<summary> <details> 的可见摘要/标题
<dialog> 模态框或弹窗
<menu> 工具栏形式的命令列表

3.5 行内文本语义元素

元素 语义 渲染
<a> 超链接 蓝色下划线
<strong> 重要性(语义强调) 粗体
<em> 重音强调 斜体
<b> 无额外重要性的粗体(如关键词) 粗体
<i> 技术术语、外语、思想等 斜体
<u> 不清晰表达的标注 下划线
<s> 不再准确的内容 删除线
<del> 已删除的内容(带语义) 删除线
<ins> 新增的内容(带语义) 下划线
<mark> 标记/高亮文本 黄色背景
<small> 小注、版权信息 小字体
<sub> 下标 H₂O
<sup> 上标 E=mc²
<abbr> 缩写,title 属性提供全称 点状下划线
<cite> 作品名称 斜体
<q> 行内引用 自动加引号
<dfn> 定义术语 斜体
<code> 行内代码 等宽字体
<kbd> 键盘输入 等宽字体
<samp> 程序输出示例 等宽字体
<var> 变量名 斜体
<time> 时间/日期,datetime 属性为机器可读格式 普通文本
<data> 机器可读数据,value 属性 普通文本
<span> 无语义的行内容器 无样式
<bdi> 双向文本隔离(阿拉伯语、希伯来语等) 普通文本
<bdo> 显式指定文本方向 普通文本
<ruby> <rt> <rp> 注音标记(CJK 注音) 小号上标
<wbr> 建议换行位置 无可见效果
<br> 强制换行(空元素) 换行

3.6 列表元素

html 复制代码
<!-- 无序列表 -->
<ul>
  <li>苹果</li>
  <li>香蕉</li>
</ul>

<!-- 有序列表 -->
<ol start="3" reversed>
  <li>第三项</li>
  <li value="10">第十项</li>
</ol>

<!-- 描述列表(术语-定义对) -->
<dl>
  <dt>HTML</dt>
  <dd>超文本标记语言,网页结构的基础</dd>
  <dt>CSS</dt>
  <dd>层叠样式表,控制页面的外观和布局</dd>
</dl>

3.7 表格元素

html 复制代码
<table>
  <caption>2025年销售数据</caption>
  <colgroup>
    <col style="width: 40%">
    <col span="2" style="width: 30%">
  </colgroup>
  <thead>
    <tr>
      <th scope="col">产品</th>
      <th scope="col">Q1</th>
      <th scope="col">Q2</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>产品A</td>
      <td>1200</td>
      <td>1500</td>
    </tr>
  </tbody>
  <tfoot>
    <tr>
      <th scope="row">合计</th>
      <td>1200</td>
      <td>1500</td>
    </tr>
  </tfoot>
</table>

表格元素全览:

元素 作用
<table> 表格容器
<caption> 表格标题(首个子元素)
<colgroup> 列组(用于列样式)
<col> 单列定义(空元素)
<thead> 表头行组
<tbody> 表体行组(可多个)
<tfoot> 表脚行组
<tr> 表行
<th> 表头单元格;scope="row/col/rowgroup/colgroup"
<td> 数据单元格;colspan rowspan 合并单元格

3.8 嵌入元素

元素 用途 关键属性
<img> 嵌入图片 src alt width height loading srcset sizes
<picture> 响应式图片容器 包含 <source> 和备用 <img>
<source> 媒体/图片条件来源 srcset media type
<video> 嵌入视频 src controls autoplay muted loop poster
<audio> 嵌入音频 src controls autoplay muted loop
<track> 字幕轨道(用于 video/audio) kind src srclang label default
<canvas> 2D/WebGL 绘图画布 width height
<svg> 内联 SVG 矢量图 ---
<math> MathML 数学公式 ---
<iframe> 嵌入外部页面 src sandbox allow loading
<embed> 外部内容插件(不常用) src type
<object> 嵌入外部对象 data type
<map> 图片热点地图 name
<area> 热点区域(空元素) shape coords href alt

3.9 脚本元素

html 复制代码
<!-- 阻塞渲染(避免) -->
<script src="app.js"></script>

<!-- defer: HTML解析完后执行,不阻塞,保持顺序 -->
<script src="app.js" defer></script>

<!-- async: 下载完就执行,不等待HTML,不保证顺序 -->
<script src="analytics.js" async></script>

<!-- type="module": 自动defer,支持ES模块 -->
<script type="module" src="main.js"></script>

<!-- 内联脚本 -->
<script>
  console.log('Hello');
</script>

<!-- 内联模板 -->
<template id="my-template">
  <p>这段内容不会被渲染,可被JavaScript克隆使用</p>
</template>

<!-- 自定义元素定义占位 -->
<slot name="header">默认内容</slot>

四、语义化 HTML5

4.1 为什么语义化很重要

html 复制代码
<!-- ❌ 非语义化(旧式写法) -->
<div id="header">
  <div class="nav">...</div>
</div>
<div class="main-content">
  <div class="article">
    <div class="article-title">...</div>
  </div>
</div>
<div id="footer">...</div>

<!-- ✅ 语义化(现代写法) -->
<header>
  <nav>...</nav>
</header>
<main>
  <article>
    <h1>...</h1>
  </article>
</main>
<footer>...</footer>

语义化的四大价值:

  1. 可访问性:屏幕阅读器能识别地标区域,帮助视障用户导航
  2. SEO:搜索引擎更准确理解内容结构和权重
  3. 可维护性:代码可读性强,团队协作更高效
  4. 样式默认行为:浏览器为语义元素提供合理的默认样式

4.2 典型页面语义结构

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>...</head>
<body>

  <!-- 网站全局页眉 -->
  <header>
    <a href="/" aria-label="网站首页">
      <img src="logo.svg" alt="公司名称">
    </a>
    <nav aria-label="主导航">
      <ul>
        <li><a href="/products">产品</a></li>
        <li><a href="/about">关于我们</a></li>
        <li><a href="/contact">联系我们</a></li>
      </ul>
    </nav>
    <!-- 页内搜索 -->
    <search>
      <form action="/search" method="get">
        <label for="q">搜索</label>
        <input type="search" id="q" name="q">
        <button type="submit">搜索</button>
      </form>
    </search>
  </header>

  <!-- 页面主内容(每页唯一) -->
  <main id="main-content">

    <!-- 英雄区域 -->
    <section aria-labelledby="hero-heading">
      <h1 id="hero-heading">欢迎来到我们的网站</h1>
      <p>这里是介绍文字。</p>
    </section>

    <!-- 文章内容 -->
    <article>
      <header>
        <h2>文章标题</h2>
        <p>
          <time datetime="2025-03-30">2025年3月30日</time>
          by <address><a href="/author/zhang">张三</a></address>
        </p>
      </header>

      <section>
        <h3>第一部分</h3>
        <p>内容...</p>
        <figure>
          <img src="chart.png" alt="展示Q1销售数据的柱状图">
          <figcaption>图1:2025年Q1销售数据</figcaption>
        </figure>
      </section>

      <footer>
        <p>标签:<a href="/tag/html">HTML</a></p>
      </footer>
    </article>

    <!-- 相关内容(侧边栏) -->
    <aside aria-label="相关文章">
      <h2>你可能感兴趣</h2>
      <ul>
        <li><a href="/post/1">相关文章1</a></li>
        <li><a href="/post/2">相关文章2</a></li>
      </ul>
    </aside>

  </main>

  <!-- 网站全局页脚 -->
  <footer>
    <nav aria-label="页脚导航">
      <a href="/privacy">隐私政策</a>
      <a href="/terms">使用条款</a>
    </nav>
    <p><small>© 2025 公司名称. All rights reserved.</small></p>
  </footer>

</body>
</html>

4.3 常见语义误用纠错

误用 正确用法 原因
<table> 做布局 用 CSS Flexbox/Grid 表格语义是"表格数据"
<br> 用于段落间距 <p> 分段 + CSS margin <br> 只是换行,不表示段落
<b> 代替 <strong> 加粗用 <strong>(重要语义)或 CSS <b> 只是视觉粗体,无语义
<i> 代替 <em> 强调用 <em>,技术术语/书名才用 <i> 同上
<h1> 到处用来"大标题" 严格按层级使用 h1-h6 破坏文档大纲,影响 SEO 和无障碍
<div> 堆满导航 <nav> 包裹主导航链接集合 失去地标导航能力
<section> 当通用容器 无语义容器用 <div> <section> 必须有标题

4.4 <details><dialog> 实用示例

html 复制代码
<!-- 无需 JavaScript 的折叠内容 -->
<details>
  <summary>点击展开详细说明</summary>
  <p>这里是隐藏的详细内容,点击上方 summary 可以切换显示/隐藏。</p>
</details>

<!-- 原生模态框 -->
<dialog id="myDialog">
  <h2>确认操作</h2>
  <p>确定要删除此记录吗?</p>
  <form method="dialog">
    <button value="cancel">取消</button>
    <button value="confirm">确认</button>
  </form>
</dialog>
<button onclick="document.getElementById('myDialog').showModal()">
  打开模态框
</button>

五、表单与输入控件

5.1 表单容器属性

html 复制代码
<form
  action="/submit"          <!-- 提交目标 URL -->
  method="post"             <!-- GET 或 POST -->
  enctype="multipart/form-data"  <!-- 上传文件时必须设置 -->
  novalidate                <!-- 禁用浏览器内置验证(自定义验证时用) -->
  autocomplete="on"         <!-- 是否启用自动完成 -->
  target="_blank"           <!-- 提交结果在新标签打开 -->
>

5.2 所有 <input> 类型

html 复制代码
<!-- 文字类 -->
<input type="text">          <!-- 普通文本 -->
<input type="password">      <!-- 密码(隐藏字符) -->
<input type="email">         <!-- 邮箱(自动验证格式) -->
<input type="url">           <!-- URL(自动验证格式) -->
<input type="tel">           <!-- 电话号码(移动端弹出数字键盘) -->
<input type="search">        <!-- 搜索框(有清除按钮) -->
<input type="number">        <!-- 数字(有上下箭头) -->

<!-- 时间日期类 -->
<input type="date">          <!-- 日期选择器 -->
<input type="time">          <!-- 时间选择器 -->
<input type="datetime-local"> <!-- 本地日期时间选择器 -->
<input type="month">         <!-- 月份选择器 -->
<input type="week">          <!-- 周选择器 -->

<!-- 选择类 -->
<input type="checkbox">      <!-- 复选框 -->
<input type="radio">         <!-- 单选框 -->
<input type="range">         <!-- 滑块(min/max/step) -->
<input type="color">         <!-- 颜色选择器 -->

<!-- 文件 -->
<input type="file">          <!-- 文件选择;multiple 多文件;accept 过滤类型 -->
<input type="file" multiple accept="image/*,.pdf">

<!-- 隐藏 -->
<input type="hidden" name="token" value="abc123">

<!-- 按钮类(推荐用 <button> 替代) -->
<input type="submit" value="提交">
<input type="reset"  value="重置">
<input type="button" value="按钮">
<input type="image"  src="btn.png" alt="图片按钮">

5.3 常用输入属性

html 复制代码
<input
  type="text"
  id="username"
  name="username"           <!-- 提交时的 key,必须有 -->
  value="默认值"
  placeholder="请输入用户名(3-20字符)"
  required                  <!-- 必填 -->
  disabled                  <!-- 禁用(不提交) -->
  readonly                  <!-- 只读(会提交) -->
  autofocus                 <!-- 页面加载后自动聚焦 -->
  autocomplete="username"   <!-- 自动填充提示 -->
  minlength="3"             <!-- 最小字符数 -->
  maxlength="20"            <!-- 最大字符数 -->
  pattern="[a-zA-Z0-9_]+"  <!-- 正则验证 -->
  title="只允许字母、数字和下划线" <!-- 验证失败时的提示 -->
  spellcheck="true"         <!-- 开启拼写检查 -->
  inputmode="numeric"       <!-- 移动端键盘类型:numeric/email/tel/url/decimal -->
  tabindex="1"              <!-- Tab 键顺序 -->
  form="form-id"            <!-- 关联到指定 form(不在 form 内时) -->
  list="suggestions"        <!-- 关联 datalist -->
>

<!-- 数字类特有 -->
<input type="number" min="0" max="100" step="5">
<input type="range"  min="0" max="100" step="1" value="50">

<!-- 文件类特有 -->
<input type="file" accept=".jpg,.png,image/*" multiple capture="camera">

5.4 完整表单示例(含验证与无障碍)

html 复制代码
<form action="/register" method="post" novalidate id="registerForm">

  <!-- 姓名 -->
  <div class="form-group">
    <label for="fullname">
      姓名 <abbr title="必填" aria-label="必填">*</abbr>
    </label>
    <input
      type="text"
      id="fullname"
      name="fullname"
      required
      minlength="2"
      maxlength="50"
      autocomplete="name"
      aria-required="true"
      aria-describedby="fullname-hint fullname-error"
    >
    <span id="fullname-hint" class="hint">2-50个字符</span>
    <span id="fullname-error" class="error" role="alert" hidden>
      请输入有效的姓名
    </span>
  </div>

  <!-- 邮箱 -->
  <div class="form-group">
    <label for="email">邮箱地址 <abbr title="必填">*</abbr></label>
    <input
      type="email"
      id="email"
      name="email"
      required
      autocomplete="email"
      aria-required="true"
      aria-describedby="email-error"
    >
    <span id="email-error" class="error" role="alert" hidden>
      请输入有效的邮箱地址
    </span>
  </div>

  <!-- 密码 -->
  <div class="form-group">
    <label for="password">密码 <abbr title="必填">*</abbr></label>
    <input
      type="password"
      id="password"
      name="password"
      required
      minlength="8"
      pattern="^(?=.*[a-z])(?=.*[A-Z])(?=.*\d).{8,}$"
      autocomplete="new-password"
      aria-required="true"
      aria-describedby="password-hint"
    >
    <span id="password-hint" class="hint">
      至少8位,包含大小写字母和数字
    </span>
  </div>

  <!-- 手机号 -->
  <div class="form-group">
    <label for="phone">手机号码</label>
    <input
      type="tel"
      id="phone"
      name="phone"
      pattern="1[3-9]\d{9}"
      placeholder="13812345678"
      autocomplete="tel"
      inputmode="numeric"
      aria-describedby="phone-hint"
    >
    <span id="phone-hint" class="hint">选填,格式:1XXXXXXXXXX</span>
  </div>

  <!-- 出生日期 -->
  <div class="form-group">
    <label for="birthday">出生日期</label>
    <input
      type="date"
      id="birthday"
      name="birthday"
      min="1900-01-01"
      max="2010-12-31"
    >
  </div>

  <!-- 性别(单选) -->
  <fieldset>
    <legend>性别</legend>
    <label>
      <input type="radio" name="gender" value="male"> 男
    </label>
    <label>
      <input type="radio" name="gender" value="female"> 女
    </label>
    <label>
      <input type="radio" name="gender" value="other"> 其他
    </label>
  </fieldset>

  <!-- 兴趣爱好(复选) -->
  <fieldset>
    <legend>兴趣爱好(可多选)</legend>
    <label><input type="checkbox" name="hobby" value="reading"> 阅读</label>
    <label><input type="checkbox" name="hobby" value="coding"> 编程</label>
    <label><input type="checkbox" name="hobby" value="sports"> 运动</label>
  </fieldset>

  <!-- 城市(下拉) -->
  <div class="form-group">
    <label for="city">所在城市</label>
    <select id="city" name="city">
      <option value="">-- 请选择 --</option>
      <optgroup label="华东">
        <option value="shanghai">上海</option>
        <option value="hangzhou">杭州</option>
      </optgroup>
      <optgroup label="华南">
        <option value="guangzhou">广州</option>
        <option value="shenzhen">深圳</option>
      </optgroup>
    </select>
  </div>

  <!-- 简介(多行文本) -->
  <div class="form-group">
    <label for="bio">个人简介</label>
    <textarea
      id="bio"
      name="bio"
      rows="4"
      maxlength="500"
      placeholder="介绍一下自己(选填,不超过500字)"
    ></textarea>
  </div>

  <!-- 头像上传 -->
  <div class="form-group">
    <label for="avatar">头像</label>
    <input
      type="file"
      id="avatar"
      name="avatar"
      accept="image/jpeg,image/png,image/webp"
    >
  </div>

  <!-- 协议同意 -->
  <div class="form-group">
    <label>
      <input type="checkbox" name="agree" required aria-required="true">
      我已阅读并同意 <a href="/terms" target="_blank">使用条款</a>
    </label>
  </div>

  <!-- 操作按钮 -->
  <div class="form-actions">
    <button type="submit">注册</button>
    <button type="reset">重置</button>
    <button type="button" onclick="history.back()">取消</button>
  </div>

</form>

<!-- 带自动补全的输入 -->
<input list="browsers" name="browser" id="browser">
<datalist id="browsers">
  <option value="Chrome">
  <option value="Firefox">
  <option value="Safari">
  <option value="Edge">
</datalist>

5.5 表单验证 JavaScript 示例

javascript 复制代码
const form = document.getElementById('registerForm');

form.addEventListener('submit', (e) => {
  e.preventDefault();
  let isValid = true;

  // 清除旧错误
  form.querySelectorAll('.error').forEach(el => el.hidden = true);
  form.querySelectorAll('input').forEach(el => {
    el.removeAttribute('aria-invalid');
  });

  // 验证每个字段
  const fields = form.querySelectorAll('[required]');
  fields.forEach(field => {
    if (!field.validity.valid) {
      const errorId = field.getAttribute('aria-describedby')
        ?.split(' ')
        .find(id => id.endsWith('-error'));

      if (errorId) {
        const errorEl = document.getElementById(errorId);
        if (errorEl) {
          errorEl.textContent = getErrorMessage(field);
          errorEl.hidden = false;
        }
      }
      field.setAttribute('aria-invalid', 'true');
      isValid = false;
    }
  });

  if (isValid) {
    form.submit();
  } else {
    // 聚焦到第一个错误字段
    form.querySelector('[aria-invalid="true"]')?.focus();
  }
});

function getErrorMessage(field) {
  if (field.validity.valueMissing) return '此字段为必填项';
  if (field.validity.typeMismatch) return `请输入有效的${field.type}格式`;
  if (field.validity.tooShort) return `最少需要 ${field.minLength} 个字符`;
  if (field.validity.tooLong) return `最多允许 ${field.maxLength} 个字符`;
  if (field.validity.patternMismatch) return field.title || '格式不正确';
  if (field.validity.rangeUnderflow) return `最小值为 ${field.min}`;
  if (field.validity.rangeOverflow) return `最大值为 ${field.max}`;
  return '输入内容无效';
}

六、多媒体元素

6.1 图片优化

html 复制代码
<!-- 基础图片(always 必须有 alt) -->
<img src="photo.jpg" alt="描述图片内容的简短文字" width="800" height="600">

<!-- 纯装饰性图片(空 alt,屏幕阅读器跳过) -->
<img src="divider.png" alt="">

<!-- 响应式图片(不同尺寸) -->
<img
  src="photo-800.jpg"
  srcset="photo-400.jpg 400w,
          photo-800.jpg 800w,
          photo-1600.jpg 1600w"
  sizes="(max-width: 480px) 100vw,
         (max-width: 900px) 50vw,
         800px"
  alt="响应式图片示例"
  loading="lazy"
  decoding="async"
  fetchpriority="low"
>

<!-- 艺术方向图片(不同设备展示完全不同构图) -->
<picture>
  <!-- 移动端:竖向裁剪 -->
  <source
    srcset="hero-portrait.webp"
    media="(max-width: 767px)"
    type="image/webp"
  >
  <!-- 桌面端:横向全图 -->
  <source
    srcset="hero-landscape.webp"
    media="(min-width: 768px)"
    type="image/webp"
  >
  <!-- 备用(不支持 WebP 或 picture 的浏览器) -->
  <img src="hero-landscape.jpg" alt="网站英雄图" loading="eager" fetchpriority="high">
</picture>

<!-- 图片配说明 -->
<figure>
  <img src="chart.svg" alt="2025年Q1-Q4各地区销售对比柱状图">
  <figcaption>图1:2025年各季度各地区销售数据对比</figcaption>
</figure>

图片属性速查:

属性 说明
alt 文字描述 必须存在;装饰图用 ""
loading lazy / eager 懒加载(首屏用 eager)
decoding async / sync / auto 异步解码提升性能
fetchpriority high / low / auto LCP 图片用 high
width / height 数字(像素) 必须设置,防止 CLS 布局偏移
crossorigin anonymous / use-credentials 跨域图片资源

6.2 视频

html 复制代码
<video
  controls
  width="1280"
  height="720"
  poster="thumbnail.jpg"
  preload="metadata"
  playsinline
>
  <!-- 多格式支持(浏览器选第一个支持的) -->
  <source src="video.webm" type="video/webm">
  <source src="video.mp4"  type="video/mp4">

  <!-- 字幕(无障碍必须) -->
  <track kind="subtitles" src="subs-zh.vtt" srclang="zh" label="中文" default>
  <track kind="subtitles" src="subs-en.vtt" srclang="en" label="English">
  <track kind="captions"  src="cc-zh.vtt"   srclang="zh" label="中文字幕(无障碍)">
  <track kind="chapters"  src="chapters.vtt" srclang="zh" label="章节">

  <!-- 浏览器不支持时显示 -->
  <p>您的浏览器不支持视频播放,请<a href="video.mp4">下载视频</a>。</p>
</video>

常用 video 属性:

属性 说明
controls 显示播放控件
autoplay 自动播放(须配合 muted
muted 静音(自动播放的前提)
loop 循环播放
poster 封面图 URL
preload none/metadata/auto
playsinline iOS 内联播放(不全屏)

6.3 音频

html 复制代码
<audio controls preload="metadata">
  <source src="podcast.ogg" type="audio/ogg">
  <source src="podcast.mp3" type="audio/mpeg">
  您的浏览器不支持音频播放。
</audio>

七、HTML5 核心 API

7.1 Canvas API(2D 绘图)

javascript 复制代码
// HTML
// <canvas id="myCanvas" width="800" height="600"></canvas>

const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');

// === 矩形 ===
ctx.fillStyle = '#3498db';
ctx.fillRect(10, 10, 200, 100);          // 实心矩形
ctx.strokeStyle = '#e74c3c';
ctx.lineWidth = 3;
ctx.strokeRect(220, 10, 200, 100);       // 空心矩形
ctx.clearRect(50, 30, 100, 50);          // 清除区域

// === 路径 ===
ctx.beginPath();
ctx.moveTo(50, 200);
ctx.lineTo(200, 200);
ctx.lineTo(125, 100);
ctx.closePath();
ctx.fillStyle = 'rgba(46, 204, 113, 0.8)';
ctx.fill();
ctx.stroke();

// === 圆弧 ===
ctx.beginPath();
ctx.arc(400, 150, 80, 0, Math.PI * 2);  // 圆形
ctx.fillStyle = '#9b59b6';
ctx.fill();

// === 文字 ===
ctx.font = 'bold 24px "Microsoft YaHei", sans-serif';
ctx.fillStyle = '#2c3e50';
ctx.textAlign = 'center';
ctx.fillText('Hello Canvas!', 400, 400);

// === 渐变 ===
const gradient = ctx.createLinearGradient(0, 500, 800, 500);
gradient.addColorStop(0, '#f39c12');
gradient.addColorStop(1, '#e74c3c');
ctx.fillStyle = gradient;
ctx.fillRect(0, 480, 800, 80);

// === 图片 ===
const img = new Image();
img.onload = () => ctx.drawImage(img, 600, 100, 150, 100);
img.src = 'photo.jpg';

// === 变换 ===
ctx.save();
ctx.translate(400, 300);
ctx.rotate(Math.PI / 4);    // 45度
ctx.scale(1.5, 0.8);
ctx.fillRect(-50, -30, 100, 60);
ctx.restore();

// === 动画(requestAnimationFrame)===
let x = 0;
function animate() {
  ctx.clearRect(0, 0, canvas.width, canvas.height);
  ctx.fillStyle = '#3498db';
  ctx.fillRect(x, 300, 50, 50);
  x = (x + 2) % canvas.width;
  requestAnimationFrame(animate);
}
animate();

7.2 Geolocation API(地理位置)

javascript 复制代码
// 判断支持
if (!navigator.geolocation) {
  alert('浏览器不支持地理位置功能');
}

// 一次性获取
navigator.geolocation.getCurrentPosition(
  // 成功回调
  (position) => {
    const { latitude, longitude, accuracy, altitude } = position.coords;
    const timestamp = new Date(position.timestamp);
    console.log(`纬度: ${latitude}`);
    console.log(`经度: ${longitude}`);
    console.log(`精度: ${accuracy} 米`);
  },
  // 失败回调
  (error) => {
    const messages = {
      1: '用户拒绝了位置权限',
      2: '无法获取位置信息',
      3: '请求超时'
    };
    console.error(messages[error.code] || '未知错误');
  },
  // 选项
  {
    enableHighAccuracy: true,   // 高精度(耗电)
    timeout: 5000,              // 超时毫秒数
    maximumAge: 60000           // 缓存有效期(毫秒)
  }
);

// 持续监听(追踪位置变化)
const watchId = navigator.geolocation.watchPosition(
  (pos) => console.log('位置更新:', pos.coords),
  (err) => console.error(err)
);

// 停止监听
navigator.geolocation.clearWatch(watchId);

7.3 Web Storage API(本地存储)

javascript 复制代码
// ======== localStorage(永久存储,关闭浏览器也保留)========
// 存储(值必须是字符串)
localStorage.setItem('theme', 'dark');
localStorage.setItem('user', JSON.stringify({ name: '张三', age: 30 }));

// 读取
const theme = localStorage.getItem('theme');           // 'dark'
const user  = JSON.parse(localStorage.getItem('user')); // {name:'张三',age:30}

// 删除
localStorage.removeItem('theme');
localStorage.clear();  // 清空所有

// 遍历
for (let i = 0; i < localStorage.length; i++) {
  const key   = localStorage.key(i);
  const value = localStorage.getItem(key);
  console.log(key, value);
}

// ======== sessionStorage(会话存储,关闭标签页即清除)========
sessionStorage.setItem('formData', JSON.stringify({ step: 2 }));
const formData = JSON.parse(sessionStorage.getItem('formData'));

// ======== 封装工具函数(带过期时间)========
const storage = {
  set(key, value, ttl = null) {
    const item = { value, expiry: ttl ? Date.now() + ttl : null };
    localStorage.setItem(key, JSON.stringify(item));
  },
  get(key) {
    const raw = localStorage.getItem(key);
    if (!raw) return null;
    const item = JSON.parse(raw);
    if (item.expiry && Date.now() > item.expiry) {
      localStorage.removeItem(key);
      return null;
    }
    return item.value;
  },
  remove(key) { localStorage.removeItem(key); }
};

// 使用
storage.set('token', 'abc123', 7 * 24 * 60 * 60 * 1000); // 7天过期
const token = storage.get('token');

localStorage vs sessionStorage vs Cookie:

特性 localStorage sessionStorage Cookie
生命周期 永久(手动清除) 标签页关闭即清除 可设过期时间
大小限制 ~5MB ~5MB ~4KB
服务器访问 是(随请求发送)
跨标签页 共享 不共享 共享
跨域 否(同源) 否(同源) 可设 domain

7.4 Web Workers API(后台线程)

javascript 复制代码
// ======== 主线程 main.js ========
// 创建 Worker
const worker = new Worker('worker.js');

// 向 Worker 发送消息
worker.postMessage({ type: 'compute', data: 1000000 });

// 接收 Worker 返回的消息
worker.onmessage = (event) => {
  console.log('Worker 计算结果:', event.data);
  document.getElementById('result').textContent = event.data.result;
};

// 错误处理
worker.onerror = (error) => {
  console.error('Worker 错误:', error.message);
};

// 终止 Worker
// worker.terminate();

// ======== Worker 线程 worker.js ========
self.onmessage = (event) => {
  const { type, data } = event.data;

  if (type === 'compute') {
    // 耗时计算(不阻塞主线程 UI)
    let sum = 0;
    for (let i = 1; i <= data; i++) {
      sum += i;
    }
    // 返回结果
    self.postMessage({ result: sum, type: 'done' });
  }
};

// ======== Shared Worker(多标签页共享)========
// const shared = new SharedWorker('shared-worker.js');
// shared.port.postMessage('hello');
// shared.port.onmessage = (e) => console.log(e.data);

7.5 WebSocket API(双向实时通信)

javascript 复制代码
const ws = new WebSocket('wss://api.example.com/ws');

// 连接建立
ws.onopen = () => {
  console.log('WebSocket 已连接');
  ws.send(JSON.stringify({ type: 'auth', token: 'abc123' }));
};

// 接收消息
ws.onmessage = (event) => {
  const data = JSON.parse(event.data);
  console.log('收到消息:', data);
};

// 错误处理
ws.onerror = (error) => {
  console.error('WebSocket 错误:', error);
};

// 连接关闭
ws.onclose = (event) => {
  console.log(`连接关闭,代码: ${event.code},原因: ${event.reason}`);
  // 断线重连
  setTimeout(() => { /* 重新连接 */ }, 3000);
};

// 发送消息
ws.send(JSON.stringify({ type: 'message', text: '你好!' }));
ws.send(new Blob([data]));          // 发送二进制数据
ws.send(new ArrayBuffer(8));

// 检查连接状态
// ws.readyState: 0=CONNECTING 1=OPEN 2=CLOSING 3=CLOSED

// 关闭连接
ws.close(1000, '正常关闭');

7.6 拖拽 API(Drag & Drop)

html 复制代码
<!-- 可拖拽元素 -->
<div class="card" draggable="true" id="card1">
  可拖拽的卡片
</div>

<!-- 放置区域 -->
<div class="dropzone" id="zone1">放置区域</div>
javascript 复制代码
const cards = document.querySelectorAll('.card');
const zones = document.querySelectorAll('.dropzone');

// ===== 拖拽源事件 =====
cards.forEach(card => {
  card.addEventListener('dragstart', (e) => {
    e.dataTransfer.setData('text/plain', e.target.id);
    e.dataTransfer.effectAllowed = 'move';
    e.target.classList.add('dragging');
  });

  card.addEventListener('dragend', (e) => {
    e.target.classList.remove('dragging');
  });
});

// ===== 放置目标事件 =====
zones.forEach(zone => {
  zone.addEventListener('dragover', (e) => {
    e.preventDefault();  // 必须阻止默认行为才能放置
    e.dataTransfer.dropEffect = 'move';
    zone.classList.add('drag-over');
  });

  zone.addEventListener('dragleave', () => {
    zone.classList.remove('drag-over');
  });

  zone.addEventListener('drop', (e) => {
    e.preventDefault();
    const id = e.dataTransfer.getData('text/plain');
    const card = document.getElementById(id);
    zone.appendChild(card);
    zone.classList.remove('drag-over');
  });
});

7.7 Fetch API(网络请求)

javascript 复制代码
// GET 请求
const response = await fetch('https://api.example.com/data');
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();

// POST 请求(提交 JSON)
const result = await fetch('/api/users', {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    'Authorization': `Bearer ${token}`
  },
  body: JSON.stringify({ name: '张三', email: 'zhang@example.com' })
});

// 提交表单数据
const formData = new FormData(document.getElementById('myForm'));
await fetch('/upload', { method: 'POST', body: formData });

// 超时控制
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 5000);
try {
  const res = await fetch('/api/slow', { signal: controller.signal });
} finally {
  clearTimeout(timeout);
}

7.8 IntersectionObserver(懒加载 / 无限滚动)

javascript 复制代码
// 图片懒加载
const lazyImages = document.querySelectorAll('img[data-src]');

const imageObserver = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target;
      img.src = img.dataset.src;
      img.removeAttribute('data-src');
      imageObserver.unobserve(img);
    }
  });
}, {
  rootMargin: '200px 0px',   // 提前200px开始加载
  threshold: 0.01
});

lazyImages.forEach(img => imageObserver.observe(img));
html 复制代码
<!-- 懒加载图片 HTML -->
<img
  src="placeholder.svg"
  data-src="real-photo.jpg"
  alt="懒加载图片"
  width="800"
  height="600"
>

7.9 Web Audio API(音频处理)

javascript 复制代码
const audioCtx = new AudioContext();

// 播放音频文件
const response = await fetch('sound.mp3');
const buffer   = await response.arrayBuffer();
const decoded  = await audioCtx.decodeAudioData(buffer);

const source = audioCtx.createBufferSource();
source.buffer = decoded;

// 增益控制(音量)
const gainNode = audioCtx.createGain();
gainNode.gain.value = 0.5;

// 连接音频图:source → gain → output
source.connect(gainNode);
gainNode.connect(audioCtx.destination);

source.start(0);  // 立即播放

八、全局属性与数据属性

8.1 全局属性(所有 HTML 元素通用)

html 复制代码
<!-- id: 文档内唯一标识符 -->
<div id="unique-id"></div>

<!-- class: 样式/JavaScript 钩子 -->
<div class="card featured active"></div>

<!-- style: 内联样式(尽量避免) -->
<p style="color: red; font-size: 1.2rem;"></p>

<!-- title: 鼠标悬停提示(工具提示) -->
<abbr title="HyperText Markup Language">HTML</abbr>

<!-- lang: 元素语言(覆盖文档语言) -->
<p lang="en">This is English.</p>
<p lang="ja">日本語のテキスト</p>

<!-- dir: 文字方向 -->
<p dir="rtl">هذا نص عربي</p>   <!-- 从右到左 -->

<!-- tabindex: Tab 键顺序 -->
<div tabindex="0">可以通过 Tab 键聚焦的非交互元素</div>
<input tabindex="-1">          <!-- 从 Tab 顺序移除但可以编程聚焦 -->

<!-- hidden: 隐藏元素(等效 display:none,但语义更强) -->
<p hidden>此内容被隐藏</p>

<!-- contenteditable: 用户可直接编辑内容 -->
<div contenteditable="true">用户可以直接在这里编辑文本</div>

<!-- draggable: 是否可拖拽 -->
<img src="photo.jpg" draggable="true" alt="">

<!-- spellcheck: 拼写检查 -->
<textarea spellcheck="true"></textarea>

<!-- translate: 是否翻译 -->
<span translate="no">品牌名称不翻译</span>

<!-- accesskey: 键盘快捷键(Alt+key)-->
<button accesskey="s">保存 (Alt+S)</button>

<!-- inert: 禁用交互(HTML Living Standard 新增) -->
<div inert>
  此区域内所有元素都不可交互(用于模态框后的内容遮罩)
</div>

<!-- popover: 弹出层(HTML Living Standard 新增) -->
<button popovertarget="my-popover">打开弹出层</button>
<div id="my-popover" popover>弹出内容</div>

8.2 自定义数据属性(data-*)

html 复制代码
<!-- 在 HTML 中存储任意数据 -->
<article
  data-article-id="42"
  data-author="张三"
  data-publish-date="2025-03-30"
  data-tags="html,css,javascript"
  data-is-featured="true"
>
  <h2>文章标题</h2>
</article>
javascript 复制代码
const article = document.querySelector('article');

// 读取(dataset 会自动将 kebab-case 转为 camelCase)
console.log(article.dataset.articleId);   // "42"
console.log(article.dataset.author);      // "张三"
console.log(article.dataset.publishDate); // "2025-03-30"

// 写入
article.dataset.views = '1250';

// 删除
delete article.dataset.isFeatured;

// CSS 选择器
// article[data-is-featured="true"] { border: 2px solid gold; }

九、可访问性(ARIA)

9.1 ARIA 基础概念

ARIA(Accessible Rich Internet Applications):W3C 规范,为辅助技术(屏幕阅读器等)添加额外的语义信息。

黄金原则 :优先使用原生 HTML 语义元素,仅在不够用时才用 ARIA 增强。
<button> 永远优于 <div role="button">

9.2 ARIA 属性分类

① Landmark Roles(地标角色)

html 复制代码
<!-- 对应语义元素的角色(优先用语义元素) -->
<header  role="banner">...</header>         <!-- 或 role="banner" -->
<nav     role="navigation">...</nav>
<main    role="main">...</main>
<aside   role="complementary">...</aside>
<footer  role="contentinfo">...</footer>
<form    role="form">...</form>
<section role="region" aria-labelledby="sec1">...</section>
<search  role="search">...</search>

② aria-label 系列(命名)

html 复制代码
<!-- aria-label: 直接提供名称 -->
<button aria-label="关闭对话框">✕</button>
<nav    aria-label="主导航">...</nav>
<nav    aria-label="页脚导航">...</nav>

<!-- aria-labelledby: 引用已有文本作为名称 -->
<section aria-labelledby="products-heading">
  <h2 id="products-heading">产品列表</h2>
</section>

<!-- aria-describedby: 提供额外描述 -->
<input aria-describedby="email-hint email-error">
<span id="email-hint">请使用工作邮箱</span>
<span id="email-error" role="alert">邮箱格式错误</span>

③ 状态属性

html 复制代码
<!-- 表单验证状态 -->
<input aria-invalid="true">     <!-- 值无效 -->
<input aria-required="true">    <!-- 必填 -->
<input aria-readonly="true">    <!-- 只读 -->
<input aria-disabled="true">    <!-- 禁用 -->

<!-- 展开/折叠 -->
<button aria-expanded="false" aria-controls="menu">菜单</button>
<ul id="menu" hidden>...</ul>

<!-- 选中状态 -->
<button role="tab" aria-selected="true">标签1</button>
<button role="tab" aria-selected="false">标签2</button>

<li role="option" aria-selected="true">选中选项</li>

<!-- 按下状态 -->
<button aria-pressed="true">✔ 已收藏</button>

<!-- 当前状态 -->
<a href="/home" aria-current="page">首页</a>   <!-- 当前页面 -->
<li aria-current="step">步骤2</li>             <!-- 当前步骤 -->

<!-- 隐藏(对辅助技术不可见) -->
<span aria-hidden="true">★</span> 4.8分

<!-- 忙碌状态(加载中) -->
<div aria-busy="true" aria-live="polite">内容加载中...</div>

④ 实时区域(Live Regions)

html 复制代码
<!-- 礼貌性通知(读完当前内容后播报) -->
<div aria-live="polite" aria-atomic="true" id="notification"></div>

<!-- 紧急通知(立即中断播报) -->
<div aria-live="assertive" role="alert" id="error-msg"></div>

<!-- 状态通知 -->
<div role="status" aria-live="polite">已保存 3 个更改</div>
javascript 复制代码
// 动态注入通知内容,屏幕阅读器会自动播报
document.getElementById('notification').textContent = '表单提交成功!';

9.3 完整无障碍组件示例

html 复制代码
<!-- 无障碍下拉菜单 -->
<nav aria-label="用户菜单">
  <button
    id="user-menu-btn"
    aria-haspopup="true"
    aria-expanded="false"
    aria-controls="user-menu"
  >
    张三的账户 ▼
  </button>
  <ul
    id="user-menu"
    role="menu"
    aria-labelledby="user-menu-btn"
    hidden
  >
    <li role="none">
      <a href="/profile" role="menuitem">个人资料</a>
    </li>
    <li role="none">
      <a href="/settings" role="menuitem">设置</a>
    </li>
    <li role="none">
      <button role="menuitem" onclick="logout()">退出登录</button>
    </li>
  </ul>
</nav>

<!-- 标签页(Tab) -->
<div>
  <div role="tablist" aria-label="产品分类">
    <button role="tab" aria-selected="true"  id="tab-1" aria-controls="panel-1">手机</button>
    <button role="tab" aria-selected="false" id="tab-2" aria-controls="panel-2">电脑</button>
    <button role="tab" aria-selected="false" id="tab-3" aria-controls="panel-3">配件</button>
  </div>

  <div role="tabpanel" id="panel-1" aria-labelledby="tab-1">手机产品...</div>
  <div role="tabpanel" id="panel-2" aria-labelledby="tab-2" hidden>电脑产品...</div>
  <div role="tabpanel" id="panel-3" aria-labelledby="tab-3" hidden>配件产品...</div>
</div>

<!-- 进度条 -->
<div
  role="progressbar"
  aria-valuenow="65"
  aria-valuemin="0"
  aria-valuemax="100"
  aria-valuetext="上传进度 65%"
  style="width: 65%"
>
  <span>65%</span>
</div>

9.4 键盘导航最佳实践

javascript 复制代码
// Tab 键管理(焦点陷阱,用于模态框)
function trapFocus(modal) {
  const focusable = modal.querySelectorAll(
    'a, button, input, textarea, select, details, [tabindex]:not([tabindex="-1"])'
  );
  const first = focusable[0];
  const last  = focusable[focusable.length - 1];

  modal.addEventListener('keydown', (e) => {
    if (e.key !== 'Tab') return;
    if (e.shiftKey) {
      if (document.activeElement === first) {
        last.focus();
        e.preventDefault();
      }
    } else {
      if (document.activeElement === last) {
        first.focus();
        e.preventDefault();
      }
    }
  });

  first.focus();  // 打开模态框时聚焦第一个可聚焦元素
}

// Esc 键关闭弹窗
document.addEventListener('keydown', (e) => {
  if (e.key === 'Escape') closeModal();
});

十、SEO 与 Meta 标签

10.1 完整 SEO Meta 标签配置

html 复制代码
<head>
  <!-- ===== 基础必须项 ===== -->
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">

  <!-- ===== 核心 SEO 标签 ===== -->
  <!-- 标题:每页唯一,50-60字,包含主关键词 -->
  <title>页面关键词 - 网站名称 | 副标题</title>

  <!-- 描述:每页唯一,150-160字,包含关键词,吸引点击 -->
  <meta name="description" content="这是一段吸引人点击的页面摘要,包含主要关键词,让用户一眼看出页面价值。建议在150-160字之间。">

  <!-- 爬虫指令 -->
  <meta name="robots" content="index, follow">
  <!-- 不要索引或跟踪:<meta name="robots" content="noindex, nofollow"> -->

  <!-- 规范 URL(防止重复内容) -->
  <link rel="canonical" href="https://www.example.com/current-page/">

  <!-- 多语言替代版本 -->
  <link rel="alternate" hreflang="zh-CN" href="https://www.example.com/zh/page/">
  <link rel="alternate" hreflang="en"    href="https://www.example.com/en/page/">
  <link rel="alternate" hreflang="x-default" href="https://www.example.com/page/">

  <!-- ===== Open Graph(社交媒体分享)===== -->
  <meta property="og:type"        content="article">
  <meta property="og:title"       content="页面标题">
  <meta property="og:description" content="页面描述(约200字)">
  <meta property="og:image"       content="https://example.com/og-image.jpg">
  <!-- 推荐 1200×630 像素,≤8MB -->
  <meta property="og:image:width"  content="1200">
  <meta property="og:image:height" content="630">
  <meta property="og:image:alt"    content="图片描述">
  <meta property="og:url"         content="https://example.com/page/">
  <meta property="og:site_name"   content="网站名称">
  <meta property="og:locale"      content="zh_CN">

  <!-- 文章专用 Open Graph -->
  <meta property="article:published_time" content="2025-03-30T08:00:00+08:00">
  <meta property="article:modified_time"  content="2025-03-30T10:00:00+08:00">
  <meta property="article:author"         content="https://example.com/author/zhang/">
  <meta property="article:section"        content="技术">
  <meta property="article:tag"            content="HTML5">

  <!-- ===== Twitter Card ===== -->
  <meta name="twitter:card"        content="summary_large_image">
  <meta name="twitter:site"        content="@your_handle">
  <meta name="twitter:creator"     content="@author_handle">
  <meta name="twitter:title"       content="页面标题">
  <meta name="twitter:description" content="页面描述">
  <meta name="twitter:image"       content="https://example.com/twitter-image.jpg">
  <meta name="twitter:image:alt"   content="图片描述">

  <!-- ===== 结构化数据(JSON-LD)===== -->
  <!-- 文章 Schema -->
  <script type="application/ld+json">
  {
    "@context": "https://schema.org",
    "@type": "Article",
    "headline": "文章标题",
    "description": "文章描述",
    "image": ["https://example.com/photo1.jpg"],
    "datePublished": "2025-03-30T08:00:00+08:00",
    "dateModified": "2025-03-30T10:00:00+08:00",
    "author": [{
      "@type": "Person",
      "name": "张三",
      "url": "https://example.com/author/zhang"
    }],
    "publisher": {
      "@type": "Organization",
      "name": "网站名称",
      "logo": {
        "@type": "ImageObject",
        "url": "https://example.com/logo.png"
      }
    }
  }
  </script>

  <!-- FAQ Schema(可触发富文本搜索结果) -->
  <script type="application/ld+json">
  {
    "@context": "https://schema.org",
    "@type": "FAQPage",
    "mainEntity": [
      {
        "@type": "Question",
        "name": "HTML5 有哪些新特性?",
        "acceptedAnswer": {
          "@type": "Answer",
          "text": "HTML5 引入了语义元素、Canvas、音视频、Web Storage、Web Workers 等大量新特性。"
        }
      }
    ]
  }
  </script>

  <!-- 面包屑 Schema -->
  <script type="application/ld+json">
  {
    "@context": "https://schema.org",
    "@type": "BreadcrumbList",
    "itemListElement": [
      {"@type":"ListItem","position":1,"name":"首页","item":"https://example.com"},
      {"@type":"ListItem","position":2,"name":"技术","item":"https://example.com/tech"},
      {"@type":"ListItem","position":3,"name":"HTML5 指南"}
    ]
  }
  </script>

  <!-- ===== 其他常用 Meta ===== -->
  <meta name="author"    content="张三">
  <meta name="copyright" content="© 2025 公司名称">
  <meta name="theme-color" content="#3498db">  <!-- 浏览器 UI 主题色 -->
  <!-- PWA -->
  <link rel="manifest" href="/manifest.json">
  <meta name="mobile-web-app-capable" content="yes">
  <meta name="apple-mobile-web-app-capable" content="yes">
  <meta name="apple-mobile-web-app-status-bar-style" content="default">
  <meta name="apple-mobile-web-app-title" content="App 名称">

</head>

10.2 SEO 标题最佳实践

标题格式 示例
关键词在前 `HTML5 Canvas 教程 - 零基础学绘图
长度控制 桌面 ≤60字;移动端 ≤50字
每页唯一 禁止所有页面标题相同
品牌居后 `页面内容

十一、性能优化最佳实践

11.1 资源加载优化

html 复制代码
<head>
  <!-- 1. 预连接(DNS + TCP + TLS,用于已知第三方域名)-->
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>

  <!-- 2. DNS 预解析(仅 DNS,用于不确定是否用的域名)-->
  <link rel="dns-prefetch" href="//cdn.example.com">

  <!-- 3. 预加载关键资源(当前页面一定会用)-->
  <link rel="preload" href="hero.jpg"      as="image" fetchpriority="high">
  <link rel="preload" href="main.css"      as="style">
  <link rel="preload" href="Inter.woff2"   as="font" type="font/woff2" crossorigin>

  <!-- 4. 预取(下一页可能用到,空闲时加载)-->
  <link rel="prefetch" href="/next-page.html">
  <link rel="prefetch" href="next-page-image.jpg" as="image">

  <!-- 5. 样式表(阻塞渲染,最先加载)-->
  <link rel="stylesheet" href="critical.css">

  <!-- 6. 非关键 CSS 异步加载 -->
  <link rel="preload" href="non-critical.css" as="style"
        onload="this.onload=null;this.rel='stylesheet'">
  <noscript><link rel="stylesheet" href="non-critical.css"></noscript>
</head>

<body>
  <!-- 7. 首屏图片:不懒加载,高优先级 -->
  <img
    src="hero.jpg"
    alt="首屏主图"
    loading="eager"
    fetchpriority="high"
    width="1920"
    height="1080"
  >

  <!-- 8. 非首屏图片:懒加载 -->
  <img
    src="photo.jpg"
    alt="文章配图"
    loading="lazy"
    decoding="async"
    width="800"
    height="600"
  >

  <!-- 9. JavaScript:推迟执行(defer 保持顺序,async 不保证)-->
  <script src="analytics.js" async></script>
  <script src="app.js"       defer></script>

  <!-- 10. 关键 CSS 内联(首屏渲染无需等待外部文件)-->
  <style>
    /* 仅首屏关键样式,约 1-2KB -->
    body { margin: 0; font-family: system-ui; }
    header { ... }
    .hero { ... }
  </style>
</body>

11.2 响应式图片完整方案

html 复制代码
<!-- 场景1:同一图片,不同分辨率(retina 适配)-->
<img
  src="photo.jpg"
  srcset="photo.jpg 1x, photo@2x.jpg 2x, photo@3x.jpg 3x"
  alt="图片"
>

<!-- 场景2:同一图片,适配不同屏幕宽度 -->
<img
  src="photo-800.jpg"
  srcset="
    photo-400.jpg  400w,
    photo-800.jpg  800w,
    photo-1200.jpg 1200w,
    photo-2400.jpg 2400w
  "
  sizes="
    (max-width: 480px)  100vw,
    (max-width: 768px)  100vw,
    (max-width: 1200px) 800px,
    1200px
  "
  alt="响应式图片"
  width="800"
  height="600"
  loading="lazy"
>

<!-- 场景3:艺术方向 + 格式降级 + 懒加载 -->
<picture>
  <!-- 现代格式 AVIF(最小体积) -->
  <source
    type="image/avif"
    srcset="hero-480.avif 480w, hero-800.avif 800w, hero-1200.avif 1200w"
    sizes="(max-width: 768px) 100vw, 800px"
  >
  <!-- 次选格式 WebP -->
  <source
    type="image/webp"
    srcset="hero-480.webp 480w, hero-800.webp 800w, hero-1200.webp 1200w"
    sizes="(max-width: 768px) 100vw, 800px"
  >
  <!-- 最终备用 JPEG -->
  <img
    src="hero-800.jpg"
    alt="英雄图片"
    width="800"
    height="450"
    loading="lazy"
    decoding="async"
  >
</picture>

11.3 核心 Web 指标(Core Web Vitals)优化

指标 全称 目标值 HTML 优化手段
LCP Largest Contentful Paint < 2.5s 预加载英雄图、内联关键 CSS、fetchpriority="high"
CLS Cumulative Layout Shift < 0.1 图片/视频设 width+height、避免动态注入内容
INP Interaction to Next Paint < 200ms defer JavaScript、使用 Web Workers
FCP First Contentful Paint < 1.8s 减少渲染阻塞资源、关键 CSS 内联
TTFB Time to First Byte < 800ms 服务器优化、CDN、HTTP/2

11.4 HTML 代码规范与工具

html 复制代码
<!-- ✅ 良好习惯 -->

<!-- 1. 属性值始终使用双引号 -->
<img src="photo.jpg" alt="图片描述">

<!-- 2. 布尔属性无需赋值 -->
<input required disabled readonly>
<!-- 而非 <input required="required"> -->

<!-- 3. 空元素无需自闭合斜杠(HTML5 不需要) -->
<br>  <hr>  <img src="">  <input type="text">
<!-- 而非 <br /> <hr /> -->

<!-- 4. 小写标签和属性名 -->
<div class="container">  <!-- 不是 <DIV CLASS="container"> -->

<!-- 5. 用 HTML 表达结构,CSS 控制样式 -->
<strong>重要文字</strong>  <!-- 不要 <b style="font-weight:bold"> -->

验证工具:

  • W3C 验证器:validator.w3.org
  • Lighthouse(Chrome DevTools → Lighthouse 面板)
  • axe DevTools(无障碍检查)
  • PageSpeed Insights:pagespeed.web.dev

十二、完整页面模板

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN" dir="ltr">
<head>
  <!-- 基础 -->
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">

  <!-- 标题与描述 -->
  <title>完整 HTML5 页面模板 | 示例网站</title>
  <meta name="description" content="一个包含所有现代最佳实践的完整 HTML5 页面模板,涵盖语义结构、SEO、无障碍和性能优化。">

  <!-- 规范 URL -->
  <link rel="canonical" href="https://example.com/page/">

  <!-- Open Graph -->
  <meta property="og:type"        content="website">
  <meta property="og:title"       content="完整 HTML5 页面模板">
  <meta property="og:description" content="现代最佳实践的完整 HTML5 页面模板">
  <meta property="og:image"       content="https://example.com/og.jpg">
  <meta property="og:url"         content="https://example.com/page/">

  <!-- 爬虫 -->
  <meta name="robots" content="index, follow">

  <!-- 主题色 -->
  <meta name="theme-color" content="#2563eb">

  <!-- 图标 -->
  <link rel="icon"             href="/favicon.svg"         type="image/svg+xml">
  <link rel="icon"             href="/favicon-32x32.png"   sizes="32x32">
  <link rel="apple-touch-icon" href="/apple-touch-icon.png">
  <link rel="manifest"         href="/manifest.json">

  <!-- 预连接 -->
  <link rel="preconnect" href="https://fonts.googleapis.com">

  <!-- 预加载关键资源 -->
  <link rel="preload" href="/fonts/inter.woff2" as="font" type="font/woff2" crossorigin>

  <!-- 关键 CSS 内联 -->
  <style>
    *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
    html { scroll-behavior: smooth; }
    body { font-family: system-ui, -apple-system, sans-serif; line-height: 1.6; }
    /* 跳过导航链接(无障碍)*/
    .skip-link {
      position: absolute; top: -100%; left: 1rem;
      background: #000; color: #fff; padding: .5rem 1rem;
      z-index: 9999; border-radius: 0 0 4px 4px;
    }
    .skip-link:focus { top: 0; }
  </style>

  <!-- 外部 CSS -->
  <link rel="stylesheet" href="/styles/main.css">

  <!-- 结构化数据 -->
  <script type="application/ld+json">
  {
    "@context": "https://schema.org",
    "@type": "WebPage",
    "name": "完整 HTML5 页面模板",
    "url": "https://example.com/page/",
    "description": "现代最佳实践的完整 HTML5 页面模板",
    "publisher": {
      "@type": "Organization",
      "name": "示例网站",
      "logo": {"@type":"ImageObject","url":"https://example.com/logo.png"}
    }
  }
  </script>
</head>

<body>
  <!-- 跳过导航(屏幕阅读器和键盘用户的快捷入口)-->
  <a class="skip-link" href="#main-content">跳过导航,直接到主内容</a>

  <!-- 全局通知区域(无障碍)-->
  <div id="notifications" aria-live="polite" aria-atomic="true"></div>

  <!-- ===== 网站页眉 ===== -->
  <header role="banner">
    <div class="container">
      <a href="/" aria-label="示例网站首页" class="logo">
        <img src="/logo.svg" alt="示例网站" width="120" height="40">
      </a>

      <!-- 主导航 -->
      <nav aria-label="主导航">
        <button
          id="nav-toggle"
          aria-controls="main-nav"
          aria-expanded="false"
          aria-label="打开导航菜单"
          class="nav-toggle"
        >
          <span></span><span></span><span></span>
        </button>
        <ul id="main-nav" role="list">
          <li><a href="/"           aria-current="page">首页</a></li>
          <li><a href="/products">产品</a></li>
          <li><a href="/blog">博客</a></li>
          <li><a href="/about">关于</a></li>
          <li><a href="/contact">联系我们</a></li>
        </ul>
      </nav>

      <!-- 搜索 -->
      <search>
        <form action="/search" method="get" role="search">
          <label for="search-input" class="visually-hidden">搜索</label>
          <input
            type="search"
            id="search-input"
            name="q"
            placeholder="搜索..."
            autocomplete="off"
          >
          <button type="submit" aria-label="执行搜索">
            <svg aria-hidden="true" width="20" height="20"><!-- 搜索图标 --></svg>
          </button>
        </form>
      </search>
    </div>
  </header>

  <!-- ===== 面包屑 ===== -->
  <nav aria-label="面包屑导航">
    <ol class="breadcrumb">
      <li><a href="/">首页</a></li>
      <li><a href="/blog">博客</a></li>
      <li aria-current="page">HTML5 完全指南</li>
    </ol>
  </nav>

  <!-- ===== 主内容 ===== -->
  <main id="main-content" tabindex="-1">

    <!-- 英雄区域 -->
    <section aria-labelledby="hero-heading" class="hero">
      <div class="container">
        <h1 id="hero-heading">HTML5 完全学习指南</h1>
        <p class="hero-desc">从基础到进阶,掌握现代 Web 开发核心技术</p>
        <div class="hero-actions">
          <a href="#guide-start" class="btn btn-primary">开始学习</a>
          <a href="/download" class="btn btn-secondary">下载 PDF</a>
        </div>
      </div>
      <picture>
        <source srcset="hero.avif" type="image/avif">
        <source srcset="hero.webp" type="image/webp">
        <img
          src="hero.jpg"
          alt=""
          width="1920"
          height="600"
          loading="eager"
          fetchpriority="high"
          decoding="async"
        >
      </picture>
    </section>

    <!-- 主要内容区 -->
    <div class="content-layout container" id="guide-start">

      <!-- 文章内容 -->
      <article aria-labelledby="article-heading">
        <header>
          <h2 id="article-heading">HTML5 核心知识</h2>
          <p class="meta">
            发布于 <time datetime="2025-03-30">2025年3月30日</time>
            · 作者:<a href="/author/zhang" rel="author">张三</a>
            · 预计阅读 <span>20 分钟</span>
          </p>
        </header>

        <section aria-labelledby="sec-semantic">
          <h3 id="sec-semantic">语义化结构</h3>
          <p>HTML5 引入了大量语义化元素...</p>

          <figure>
            <img
              src="semantic-layout.png"
              alt="HTML5 页面典型语义化布局示意图,展示 header、nav、main、aside、footer 的位置关系"
              width="800"
              height="600"
              loading="lazy"
              decoding="async"
            >
            <figcaption>HTML5 页面语义化布局示意图</figcaption>
          </figure>
        </section>

        <section aria-labelledby="sec-apis">
          <h3 id="sec-apis">HTML5 API</h3>
          <p>Canvas、Web Storage、Geolocation 等强大 API...</p>

          <details>
            <summary>Canvas API 代码示例</summary>
            <pre><code>const ctx = canvas.getContext('2d');
ctx.fillStyle = 'blue';
ctx.fillRect(0, 0, 100, 100);</code></pre>
          </details>
        </section>

        <footer>
          <p>标签:
            <a href="/tag/html5" rel="tag">HTML5</a>、
            <a href="/tag/web" rel="tag">Web 开发</a>
          </p>
          <address>
            如有问题请联系:<a href="mailto:zhang@example.com">zhang@example.com</a>
          </address>
        </footer>
      </article>

      <!-- 侧边栏 -->
      <aside aria-label="相关内容">
        <section>
          <h2>目录</h2>
          <nav aria-label="文章目录">
            <ol>
              <li><a href="#sec-semantic">语义化结构</a></li>
              <li><a href="#sec-apis">HTML5 API</a></li>
            </ol>
          </nav>
        </section>

        <section>
          <h2>相关文章</h2>
          <ul>
            <li><a href="/css3-guide">CSS3 完全指南</a></li>
            <li><a href="/js-guide">JavaScript 深度解析</a></li>
          </ul>
        </section>
      </aside>
    </div>

  </main>

  <!-- ===== 网站页脚 ===== -->
  <footer role="contentinfo">
    <div class="container">
      <div class="footer-grid">
        <section>
          <h2>关于我们</h2>
          <p>致力于提供高质量的 Web 开发学习内容。</p>
        </section>
        <section>
          <h2>快速链接</h2>
          <nav aria-label="页脚导航">
            <ul>
              <li><a href="/privacy">隐私政策</a></li>
              <li><a href="/terms">使用条款</a></li>
              <li><a href="/sitemap.xml">网站地图</a></li>
              <li><a href="/rss.xml" type="application/rss+xml">RSS 订阅</a></li>
            </ul>
          </nav>
        </section>
        <section>
          <h2>社交媒体</h2>
          <ul>
            <li>
              <a href="https://github.com/example" rel="noopener noreferrer" target="_blank">
                GitHub <span class="visually-hidden">(在新标签页打开)</span>
              </a>
            </li>
          </ul>
        </section>
      </div>

      <p class="copyright">
        <small>© <time datetime="2025">2025</time> 示例网站. 保留所有权利.</small>
      </p>
    </div>
  </footer>

  <!-- 回到顶部按钮 -->
  <a
    href="#"
    id="back-to-top"
    aria-label="回到页面顶部"
    hidden
  >↑</a>

  <!-- JavaScript(底部,defer 或异步)-->
  <script src="/js/app.js" defer></script>
</body>
</html>

附录:重要参考资源

资源 URL 说明
HTML Living Standard html.spec.whatwg.org 官方最权威规范
MDN Web Docs developer.mozilla.org/zh-CN/docs/Web/HTML 最全面的参考文档
W3C WAI(无障碍) www.w3.org/WAI WCAG、ARIA 规范
Can I Use caniuse.com 浏览器兼容性查询
W3C 验证器 validator.w3.org HTML 语法验证
PageSpeed Insights pagespeed.web.dev Core Web Vitals 检测
Schema.org schema.org 结构化数据类型库
WebAIM webaim.org 无障碍实践指南
相关推荐
不超限2 小时前
InfoSuite AS部署Vue项目
前端·javascript·vue.js
程序员小寒2 小时前
JavaScript设计模式(五):装饰者模式实现与应用
前端·javascript·设计模式
wefly20172 小时前
零基础上手m3u8live.cn,免费无广告的M3U8在线播放器,电脑手机通用
前端·javascript·学习·电脑·m3u8·m3u8在线播放
晓13132 小时前
React篇——第四章 React Router基础
前端·javascript·react
Moment2 小时前
如果想转 AI 全栈?推荐你学一下 Langchain!
前端·后端·面试
cch89182 小时前
常见布局实现详解(Flex 实战版)
前端·javascript·css
啥都想学点2 小时前
从 Flutter 前端到 Spring Boot 后端:2026 年技术栈落地路线图(实战版)
前端·spring boot·flutter
telllong2 小时前
Chrome DevTools Protocol:浏览器自动化入门
前端·自动化·chrome devtools
吴声子夜歌2 小时前
Node.js——npm包管理器
前端·npm·node.js