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 无障碍实践指南
相关推荐
runnerdancer11 小时前
LLM是怎么处理messages数组的,提示词缓存又是什么
前端·agent
陈随易11 小时前
VSCode的Copilot扩展支持接入DeepSeek,Kimi了!
前端·后端·程序员
我不是外星人13 小时前
有了 Harness Engineering ,真的还需要研发工程师吗?
前端·后端·ai编程
IT_陈寒15 小时前
JavaScript的闭包把我坑惨了,说好的内存会自动回收呢?
前端·人工智能·后端
Jackson__16 小时前
分享一个横向滚动案例,带悬停暂停,通用性很强
前端
MariaH17 小时前
git rebase的使用
前端
_柳青杨17 小时前
深入理解 JavaScript 事件循环
前端·javascript
阡陌Jony17 小时前
关于前端性能优化的一些问题:
前端
用户6000718191018 小时前
【翻译】简化 TSRX
前端
IT乐手19 小时前
佛德角逼平西班牙,国足还有啥借口?
前端