{T}

HTML 全局属性完整指南

概述

HTML 全局属性是可以应用于所有 HTML 元素的通用属性,无论元素的类型如何。这些属性提供元素标识、样式控制、可访问性、国际化、交互行为等基础能力。理解并合理使用这些属性,可以显著提升页面的可维护性与用户体验。

核心特性

  • 通用性:适用于几乎所有 HTML 元素
  • 语义化:增强元素的含义和结构
  • 可访问性:支持屏幕阅读器和键盘导航
  • 国际化:支持多语言和多方向文本
  • 交互性:提供拖拽、编辑、焦点控制等功能

适用场景

场景推荐属性说明
元素标识id, classCSS 选择、JavaScript 操作、锚点
样式控制style, class内联样式、样式分组
可访问性tabindex, accesskey, lang, title键盘导航、屏幕阅读器支持
国际化lang, dir, translate多语言支持、RTL 文本
交互控制contenteditable, draggable, hidden编辑、拖拽、显示隐藏
数据存储data-*自定义数据、状态管理
移动端优化inputmode, enterkeyhint, autocapitalize虚拟键盘优化
Web Componentsslot, part, is组件开发、Shadow DOM

文档导航

按功能分类

快速跳转


术语说明

术语说明
全局属性可以出现在几乎所有 HTML 标签上的属性,用于控制通用行为
布尔属性存在即为 true,缺失为 false 的属性,例如 hiddenautofocus
可访问性(a11y)让使用屏幕阅读器、仅键盘操作等用户也能顺利使用页面的相关技术与规范
焦点(focus)当前接收键盘输入的元素,一般会有明显的焦点样式轮廓
虚拟键盘移动端或触屏设备上的软件键盘,会受 inputmodeenterkeyhint 等属性影响
国际化(i18n)让页面可以在多语言、多地区场景下正确显示和输入内容
Shadow DOMWeb Components 的一部分,用于创建封装的 DOM 子树
CSPContent Security Policy,内容安全策略,用于防止 XSS 攻击

全局属性速查表

下表总结了 HTML 规范中常见的全局属性,你可以把它当作"速查表"。后续章节会对重要属性展开说明和示例。

属性类型说明常见用途分类
id字符串文档中唯一标识元素CSS/JS 选择、锚点、表单关联核心
class空格分隔字符串为元素分配一个或多个类名样式分组、状态标记核心
styleCSS 声明字符串定义元素内联样式小范围覆盖、快速试验核心
title文本悬停时显示的提示文本补充说明、缩写解释核心
langBCP 47 语言代码指定元素内容语言多语言支持、拼写检查、SEO国际化
dirltr/rtl/auto指定文本方向RTL 语言支持国际化
tabindex整数控制键盘焦点顺序键盘导航、可访问性可访问性
accesskey字符串为元素分配访问快捷键高级用户键盘操作可访问性
hidden布尔元素当前不应显示条件显示、占位内容交互控制
contenteditable布尔/关键字元素内容是否可编辑富文本编辑器、可编辑区域交互控制
spellcheck布尔是否启用拼写检查表单、评论区、富文本编辑交互控制
draggable布尔/auto元素是否可拖拽拖放界面、文件拖放上传交互控制
data-*任意字符串自定义数据属性JS 交互、状态存储数据
translateyes/no是否允许自动翻译内容品牌名、代码片段保护国际化
autocapitalize关键字控制输入时自动大小写移动端输入体验优化移动端
autofocus布尔页面加载时自动聚焦表单初始输入框移动端
inputmode关键字建议虚拟键盘类型数字/邮箱/搜索输入优化移动端
enterkeyhint关键字指定回车键上的提示文案指示"搜索""发送"等操作移动端
is字符串绑定自定义元素构造器扩展内建元素(Web Components)Web Components
nonce随机字符串内容安全策略(CSP)使用的随机值安全地允许特定内联脚本/样式安全
inert布尔将一棵子树标记为不可交互模态框背景禁用、加载状态高级特性
popover关键字/布尔声明原生弹出层Tooltip、弹出菜单、信息卡片高级特性
slot字符串指定插槽名称Web Components 内容分发Web Components
part空格分隔字符串标记组件内部可样式化部件Shadow DOM 内部样式挂钩Web Components
exportparts映射列表将内部 part 暴露给外部组合组件样式透传Web Components
itemid多个属性定义微数据结构化信息SEO、搜索引擎富摘要微数据

<a id="核心属性"></a>

<h4>041-global-attributes-reference.html</h4>
html
<!-- 来源:7-全局属性.md - 全局属性速查表与交互演示 -->
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>HTML 全局属性速查表</title>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body {
      font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
      line-height: 1.6; color: #333; background: #f5f7fa; padding: 30px 20px;
    }
    .container { max-width: 1100px; margin: 0 auto; }
    h1 { text-align: center; color: #222; margin-bottom: 8px; font-size: 30px; }
    .subtitle { text-align: center; color: #666; margin-bottom: 28px; font-size: 14px; }

    /* 分类标签 */
    .category-tabs { display: flex; gap: 8px; margin-bottom: 24px; flex-wrap: wrap; justify-content: center; }
    .cat-tab {
      padding: 8px 18px; border-radius: 20px; border: none; cursor: pointer;
      font-size: 13px; font-weight: 600; transition: all 0.2s; background: white; color: #666;
    }
    .cat-tab.active { background: #0066cc; color: white; }
    .cat-tab:hover:not(.active) { background: #e8f4fd; }

    /* 属性卡片网格 */
    .attr-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); gap: 16px; }

    .attr-card {
      background: white; border-radius: 10px; padding: 20px;
      box-shadow: 0 2px 8px rgba(0,0,0,0.06); border-left: 4px solid #0066cc;
      transition: transform 0.2s;
    }
    .attr-card:hover { transform: translateY(-3px); box-shadow: 0 6px 20px rgba(0,0,0,0.1); }

    .attr-name {
      display: inline-block; background: #0066cc; color: white;
      padding: 3px 10px; border-radius: 4px; font-family: 'Monaco', monospace;
      font-size: 13px; font-weight: 600; margin-bottom: 8px;
    }
    .attr-type { float: right; font-size: 11px; color: #888; padding-top: 5px; }
    .attr-desc { color: #555; font-size: 13px; line-height: 1.7; margin-bottom: 12px; }
    .attr-example {
      background: #f8f9fa; padding: 10px 14px; border-radius: 6px;
      font-family: 'Monaco', monospace; font-size: 12px; overflow-x: auto;
      color: #333; border-left: 2px solid #ddd;
    }

    /* 互动演示区 */
    .demo-section {
      background: white; border-radius: 12px; padding: 28px;
      margin-top: 28px; box-shadow: 0 2px 12px rgba(0,0,0,0.06);
    }
    .demo-section h3 { color: #222; margin-bottom: 18px; font-size: 18px; }

    /* contenteditable 演示 */
    .editable-area {
      min-height: 100px; border: 2px dashed #ccc; border-radius: 8px;
      padding: 16px; outline: none; transition: border-color 0.2s;
      font-size: 15px; line-height: 1.7;
    }
    .editable-area:focus { border-color: #0066cc; background: #f0f7ff; }

    /* hidden 演示 */
    .hidden-toggle { margin: 10px 0; }
    [data-hidden] { opacity: 0.3; text-decoration: line-through; pointer-events: none; }

    /* draggable 演示 */
    .drag-list { list-style: none; display: flex; gap: 12px; flex-wrap: wrap; }
    .drag-item {
      padding: 12px 20px; background: linear-gradient(135deg, #667eea, #764ba2);
      color: white; border-radius: 8px; cursor: grab; user-select: none;
      transition: all 0.2s;
    }
    .drag-item:active { cursor: grabbing; transform: scale(1.05); }
    .drop-zone {
      min-height: 80px; border: 3px dashed #ccc; border-radius: 10px;
      display: flex; align-items: center; justify-content: center;
      margin-top: 16px; transition: all 0.2s; color: #888;
    }
    .drop-zone.drag-over { border-color: #28a745; background: #d4edda; color: #155724; }

    /* spellcheck 演示 */
    .spellcheck-area { width: 100%; height: 80px; padding: 12px; border: 1px solid #ddd; border-radius: 6px; resize: vertical; font-size: 14px; }
  </style>
</head>
<body>

<div class="container">
  <h1>🌐 HTML 全局属性</h1>
  <p class="subtitle">适用于所有 HTML 元素的通用属性 — 速查表 + 交互演示</p>

  <!-- 分类切换 -->
  <div class="category-tabs">
    <button class="cat-tab active" onclick="filterCards('all')">全部</button>
    <button class="cat-tab" onclick="filterCards('core')">核心属性</button>
    <button class="cat-tab" onclick="filterCards('i18n')">国际化</button>
    <button class="cat-tab" onclick="filterCards('a11y')">可访问性</button>
    <button class="cat-tab" onclick="filterCards('interaction')">交互控制</button>
    <button class="cat-tab" onclick="filterCards('mobile')">移动端</button>
  </div>

  <!-- 属性卡片 -->
  <div class="attr-grid" id="attrGrid">

    <!-- id -->
    <div class="attr-card" data-category="core">
      <span class="attr-name">id</span><span class="attr-type">字符串</span>
      <p class="attr-desc">文档内唯一标识符,用于 CSS 选择、JS 操作、锚点跳转和表单关联。</p>
      <div class="attr-example">&lt;header id="site-header"&gt;&lt;/header&gt;</div>
    </div>

    <!-- class -->
    <div class="attr-card" data-category="core">
      <span class="attr-name">class</span><span class="attr-type">空格分隔字符串</span>
      <p class="attr-desc">分配一个或多个类名,用于 CSS 样式分组和 JS 批量操作。</p>
      <div class="attr-example">&lt;button class="btn btn-primary"&gt;&lt;/button&gt;</div>
    </div>

    <!-- style -->
    <div class="attr-card" data-category="core">
      <span class="attr-name">style</span><span class="attr-type">CSS 声明</span>
      <p class="attr-desc">内联样式定义,优先级最高。建议仅在动态修改或覆盖时使用。</p>
      <div class="attr-example">&lt;div style="color:red;"&gt;&lt;/div&gt;</div>
    </div>

    <!-- title -->
    <div class="attr-card" data-category="core">
      <span class="attr-name">title</span><span class="attr-type">文本</span>
      <p class="attr-desc">鼠标悬停时显示的提示文本,用于补充说明或缩写解释。</p>
      <div class="attr-example">&lt;abbr title="HyperText Markup Language"&gt;HTML&lt;/abbr&gt;</div>
    </div>

    <!-- lang -->
    <div class="attr-card" data-category="i18n">
      <span class="attr-name">lang</span><span class="attr-type">BCP 47 语言代码</span>
      <p class="attr-desc">指定元素内容语言,影响拼写检查、搜索引擎、屏幕阅读器发音。</p>
      <div class="attr-example">&lt;html lang="zh-CN"&gt;&lt;p lang="en"&gt;Hello&lt;/p&gt;&lt;/html&gt;</div>
    </div>

    <!-- dir -->
    <div class="attr-card" data-category="i18n">
      <span class="attr-name">dir</span><span class="attr-type">ltr/rtl/auto</span>
      <p class="attr-desc">指定文本方向,支持从右到左(RTL)的语言如阿拉伯语、希伯来语。</p>
      <div class="attr-example">&lt;p dir="rtl"&gt;مرحبا بالعالم&lt;/p&gt;</div>
    </div>

    <!-- translate -->
    <div class="attr-card" data-category="i18n">
      <span class="attr-name">translate</span><span class="attr-type">yes/no</span>
      <p class="attr-desc">控制是否允许浏览器自动翻译该元素内容。品牌名/代码应设为 no。</p>
      <div class="attr-example">&lt;code translate="no"&gt;&amp;nbsp;&lt;/code&gt;</div>
    </div>

    <!-- tabindex -->
    <div class="attr-card" data-category="a11y">
      <span class="attr-name">tabindex</span><span class="attr-type">整数</span>
      <p class="attr-desc">控制键盘焦点顺序。0=按文档顺序,-1=仅编程聚焦,正数=自定义顺序。</p>
      <div class="attr-example">&lt;div tabindex="0"&gt;可通过Tab聚焦&lt;/div&gt;</div>
    </div>

    <!-- accesskey -->
    <div class="attr-card" data-category="a11y">
      <span class="attr-name">accesskey</span><span class="attr-type">字符</span>
      <p class="attr-desc">分配访问快捷键。Mac: Ctrl+Option+key,Win: Alt+key。</p>
      <div class="attr-example">&lt;button accesskey="s"&gt;保存(S)&lt;/button&gt;</div>
    </div>

    <!-- hidden -->
    <div class="attr-card" data-category="interaction">
      <span class="attr-name">hidden</span><span class="attr-type">布尔</span>
      <p class="attr-desc">隐藏元素,等价于 display:none 但语义更明确。可用于条件显示内容。</p>
      <div class="attr-example">&lt;div hidden&gt;加载中...&lt;/div&gt;</div>
    </div>

    <!-- contenteditable -->
    <div class="attr-card" data-category="interaction">
      <span class="attr-name">contenteditable</span><span class="attr-type">布尔/"plaintext-only"</span>
      <p class="attr-desc">使元素内容可编辑,用于构建富文本编辑器。值:true/false/"inherit"。</p>
      <div class="attr-example">&lt;div contenteditable="true"&gt;可编辑&lt;/div&gt;</div>
    </div>

    <!-- draggable -->
    <div class="attr-card" data-category="interaction">
      <span class="attr-name">draggable</span><span class="attr-type">布尔/auto</span>
      <p class="attr-desc">声明元素是否可拖拽。配合 Drag and Drop API 使用。</p>
      <div class="attr-example">&lt;div draggable="true"&gt;拖拽我&lt;/div&gt;</div>
    </div>

    <!-- spellcheck -->
    <div class="attr-card" data-category="interaction">
      <span class="attr-name">spellcheck</span><span class="attr-type">布尔</span>
      <p class="attr-desc">是否启用拼写检查。适合评论框、富文本编辑器等场景。</p>
      <div class="attr-example">&lt;textarea spellcheck="true"&gt;&lt;/textarea&gt;</div>
    </div>

    <!-- inputmode -->
    <div class="attr-card" data-category="mobile">
      <span class="attr-name">inputmode</span><span class="attr-type">关键字</span>
      <p class="attr-desc">建议移动端虚拟键盘类型:text/tel/email/numeric/decimal/search/url。</p>
      <div class="attr-example">&lt;input inputmode="numeric" /&gt; → 数字键盘</div>
    </div>

    <!-- enterkeyhint -->
    <div class="attr-card" data-category="mobile">
      <span class="attr-name">enterkeyhint</span><span class="attr-type">关键字</span>
      <p class="attr-desc">指定回车键提示文案:enter/go/done/search/next/send/previous。</p>
      <div class="attr-example">&lt;input enterkeyhint="search" /&gt; → "搜索"</div>
    </div>

    <!-- autocapitalize -->
    <div class="attr-card" data-category="mobile">
      <span class="attr-name">autocapitalize</span><span class="attr-type">关键字</span>
      <p class="attr-desc">控制移动端自动大小写:none/sentences/words/characters/off/on。</p>
      <div class="attr-example">&lt;input autocapitalize="words" /&gt; → 首字母大写</div>
    </div>

    <!-- autofocus -->
    <div class="attr-card" data-category="mobile">
      <span class="attr-name">autofocus</span><span class="attr-type">布尔</span>
      <p class="attr-desc">页面加载时自动获取焦点。每个页面只能有一个 autofocus 元素。</p>
      <div class="attr-example">&lt;input type="text" autofocus /&gt;</div>
    </div>

    <!-- inert -->
    <div class="attr-card" data-category="a11y">
      <span class="attr-name">inert</span><span class="attr-type">布尔</span>
      <p class="attr-desc">将子树标记为不可交互。用于模态框背景禁用、加载状态等场景。</p>
      <div class="attr-example">&lt;div inert&gt;禁用此区域所有交互&lt;/div&gt;</div>
    </div>

    <!-- data-* -->
    <div class="attr-card" data-category="core">
      <span class="attr-name">data-*</span><span class="attr-type">任意字符串</span>
      <p class="attr-desc">自定义数据属性,通过 JS dataset API 访问。不渲染但可存储状态。</p>
      <div class="attr-example">&lt;div data-user-id="123" data-role="admin"&gt;&lt;/div&gt;</div>
    </div>

  </div>


  <!-- 交互演示区域 -->
  <div class="demo-section">
    <h3>🎮 互动演示 — 尝试以下效果</h3>

    <div style="display:grid; grid-template-columns: repeat(auto-fit,minmax(280px,1fr)); gap:20px;">

      <!-- contenteditable 演示 -->
      <div>
        <h4 style="margin-bottom:8px;color:#555;">✏️ contenteditable — 点击编辑</h4>
        <div class="editable-area" contenteditable="true">👋 你可以直接在这里输入文字,试试加粗或斜体!</div>
      </div>

      <!-- hidden 演示 -->
      <div>
        <h4 style="margin-bottom:8px;color:#555;">👁️ hidden — 切换显示</h4>
        <button class="cat-tab" onclick="toggleHidden()">切换隐藏/显示</button>
        <div class="hidden-toggle">
          <p data-hidden>这是一段可以被隐藏的文字</p>
          <p>这段文字始终可见</p>
        </div>
      </div>

      <!-- draggable 演示 -->
      <div>
        <h4 style="margin-bottom:8px;color:#555;">🖱️ draggable — 拖拽到下方区域</h4>
        <ul class="drag-list">
          <li class="drag-item" draggable="true" id="item1">拖拽项 A</li>
          <li class="drag-item" draggable="true" id="item2">拖拽项 B</li>
          <li class="drag-item" draggable="true" id="item3">拖拽项 C</li>
        </ul>
        <div class="drop-zone" id="dropZone">📥 将上方项目拖到这里</div>
      </div>

      <!-- spellcheck 演示 -->
      <div>
        <h4 style="margin-bottom:8px;color:#555;">📝 spellcheck — 拼写检查</h4>
        <textarea class="spellcheck-area" spellcheck="true" placeholder="在此输入英文文字测试拼写检查..."></textarea>
        <small style="color:#888;font-size:11px;">注意:拼写错误会显示红色波浪线</small>
      </div>

    </div>
  </div>

</div>

<script>
  // 分类筛选
  function filterCards(category) {
    document.querySelectorAll('.cat-tab').forEach(t => t.classList.remove('active'))
    event.target.classList.add('active')

    const cards = document.querySelectorAll('.attr-card')
    cards.forEach(card => {
      if (category === 'all' || card.dataset.category === category) {
        card.style.display = ''
      } else {
        card.style.display = 'none'
      }
    })
  }

  // hidden 切换
  function toggleHidden() {
    const el = document.querySelector('[data-hidden]')
    el.hidden = !el.hidden
  }

  // 拖拽演示
  const dropZone = document.getElementById('dropZone')
  document.querySelectorAll('.drag-item').forEach(item => {
    item.addEventListener('dragstart', e => e.dataTransfer.setData('text/plain', item.id))
  })

  dropZone.addEventListener('dragover', e => {
    e.preventDefault()
    dropZone.classList.add('drag-over')
  })

  dropZone.addEventListener('dragleave', () => dropZone.classList.remove('drag-over'))

  dropZone.addEventListener('drop', e => {
    e.preventDefault()
    dropZone.classList.remove('drag-over')
    const itemId = e.dataTransfer.getData('text/plain')
    const item = document.getElementById(itemId)
    if (item) {
      dropZone.innerHTML = `✅ 已放置: ${item.textContent}`
      dropZone.style.background = '#d4edda'
      dropZone.style.color = '#155724'
    }
  })
</script>

</body>
</html>

核心属性

<h4>043-class-id-style-core.html</h4>
html
<!DOCTYPE html>
<!--
  来源:基础知识/7-全局属性.md
  演示:class / id / style 三大核心属性实战
-->
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>class/id/style 核心属性实战</title>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body { font-family: -apple-system, sans-serif; padding: 30px; background: #f0f2f5; }
    h1 { color: #1a1a1a; margin-bottom: 25px; }
    .demo-box { background: white; padding: 25px; margin-bottom: 20px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.08); }
    h2 { color: #444; margin-bottom: 15px; font-size: 17px; border-bottom: 2px solid #eee; padding-bottom: 8px; }

    /* class 演示样式 */
    .btn { padding: 10px 20px; border: none; border-radius: 6px; cursor: pointer; font-size: 14px; transition: all 0.2s; margin: 5px; }
    .btn-primary { background: #3498db; color: white; }
    .btn-primary:hover { background: #2980b9; }
    .btn-success { background: #27ae60; color: white; }
    .btn-success:hover { background: #219a52; }
    .btn-danger { background: #e74c3c; color: white; }
    .btn-danger:hover { background: #c0392b; }
    .btn-warning { background: #f39c12; color: white; }
    .btn-lg { padding: 14px 28px; font-size: 16px; }
    .btn-sm { padding: 6px 12px; font-size: 12px; }
    .btn-disabled { opacity: 0.5; cursor: not-allowed; }
    .active { box-shadow: 0 0 0 3px rgba(52,152,219,0.5); transform: scale(1.05); }

    .card { padding: 15px; border-radius: 8px; margin: 8px; display: inline-block; }
    .bg-blue { background: #ebf5fb; border: 1px solid #aed6f1; }
    .bg-green { background: #eafaf1; border: 1px solid #a9dfbf; }
    .bg-red { background: #fdedec; border: 1px solid #fadbd8; }
    .rounded { border-radius: 20px; }
    .shadow { box-shadow: 0 4px 12px rgba(0,0,0,0.15); }

    code { background: #f4f4f4; padding: 2px 6px; border-radius: 3px; font-size: 13px; color: #e74c3c; }
    pre { background: #282c34; color: #abb2bf; padding: 15px; border-radius: 6px; overflow-x: auto; font-size: 13px; line-height: 1.6; }
    .log { background: #f8f9fa; border-left: 3px solid #3498db; padding: 10px 15px; margin: 10px 0; font-family: monospace; font-size: 13px; }

    #unique-element { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; padding: 20px; border-radius: 10px; }
    #anchor-target { padding: 40px; background: #fff3cd; border: 2px dashed #ffc107; border-radius: 8px; margin: 15px 0; }
  </style>
</head>
<body>
  <h1>🎯 class / id / style 三大核心属性实战</h1>

  <!-- ==================== class 属性演示 ==================== -->
  <div class="demo-box">
    <h2>1. class 属性 — 多类名组合与动态切换</h2>

    <p><strong>多类名组合示例:</strong></p>
    <button class="btn btn-primary">主要按钮</button>
    <button class="btn btn-success">成功按钮</button>
    <button class="btn btn-danger">危险按钮</button>
    <button class="btn btn-warning btn-lg">大号警告</button>
    <button class="btn btn-primary btn-sm">小型按钮</button>
    <button class="btn btn-primary btn-disabled">禁用状态</button>

    <p style="margin-top:15px;"><strong>组合效果:</strong></p>
    <div class="card bg-blue">基础卡片</div>
    <div class="card bg-green rounded">圆角绿色卡片</div>
    <div class="card bg-red shadow">红色阴影卡片</div>

    <hr style="margin:15px 0;">
    <button class="btn btn-primary" onclick="toggleClass()">切换 active 类</button>
    <button class="btn btn-success" onclick="addClassListDemo()" classList 操作演示</button>
    <pre id="class-log" class="log">// 点击按钮查看 class 操作</pre>
  </div>

  <!-- ==================== id 属性演示 ==================== -->
  <div class="demo-box">
    <h2>2. id 属性 — 唯一标识与锚点导航</h2>

    <p><strong>锚点导航:</strong></p>
    <nav style="margin-bottom:15px;">
      <a href="#anchor-target" class="btn btn-primary btn-sm">跳转到目标区域 ↓</a>
      <a href="#unique-element" class="btn btn-success btn-sm">跳转到唯一元素 ↓</a>
    </nav>

    <div id="unique-element">
      <strong>id="unique-element"</strong><br>
      这是页面中唯一的元素,通过 getElementById 快速定位
    </div>

    <div id="anchor-target">
      <strong>id="anchor-target"</strong><br>
      你已经通过锚点链接跳转到了这里!<br>
      <small>URL 应该显示为 ...#anchor-target</small>
    </div>

    <hr style="margin:15px 0;">
    <button class="btn btn-primary" onclick="demoIdApi()">演示 ID API</button>
    <pre id="id-log" class="log">// 点击查看 ID 相关 API</pre>
  </div>

  <!-- ==================== style 属性演示 ==================== -->
  <div class="demo-box">
    <h2>3. style 内联样式 — 动态样式控制</h2>

    <div id="style-demo" style="padding:20px; background:#f8f9fa; border-radius:8px; text-align:center; transition:all 0.5s;">
      <p style="font-size:18px; margin-bottom:10px;">✨ 这段文字的样式可以通过下方按钮改变 ✨</p>
      <p id="style-info">当前样式将在这里显示</p>
    </div>

    <div style="margin-top:15px;">
      <button class="btn btn-primary" onclick="changeColor('#e74c3c')">红色</button>
      <button class="btn btn-success" onclick="changeColor('#27ae60')">绿色</button>
      <button class="btn" style="background:#9b59b6;color:white;" onclick="changeColor('#9b59b6')">紫色</button>
      <button class="btn" style="background:#f39c12;color:white;" onclick="changeColor('#f39c12')">橙色</button>
      <button class="btn btn-danger" onclick="changeSize(14)">缩小</button>
      <button class="btn btn-success" onclick="changeSize(24)">放大</button>
      <button class="btn btn-primary" onclick="toggleBorder()">切换边框</button>
      <button class="btn" style="background:#34495e;color:white;" onclick="resetStyle()">重置</button>
    </div>

    <pre id="style-log" class="log">// 样式变化日志</pre>
  </div>

  <!-- ==================== 三者对比 ==================== -->
  <div class="demo-box">
    <h2>4. class vs id vs style 使用场景对比</h2>
    <table style="width:100%;border-collapse:collapse;font-size:14px;">
      <thead>
        <tr style="background:#34495e;color:white;">
          <th style="padding:10px;text-align:left;">属性</th>
          <th style="padding:10px;text-align:left;">选择器语法</th>
          <th style="padding:10px;text-align:left;">唯一性</th>
          <th style="padding:10px;text-align:left;">典型用途</th>
        </tr>
      </thead>
      <tbody>
        <tr style="background:#ebf5fb;">
          <td style="padding:10px;"><code>.class</code></td>
          <td style="padding:10px;"><code>.classname</code></td>
          <td style="padding:10px;">可复用(多个元素)</td>
          <td style="padding:10px;">样式分组、状态标记、CSS 选择</td>
        </tr>
        <tr style="background:#fdedec;">
          <td style="padding:10px;"><code>#id</code></td>
          <td style="padding:10px;"><code>#idname</code></td>
          <td style="padding:10px;">唯一(单个元素)</td>
          <td style="padding:10px;">JS 定位、锚点跳转、表单关联</td>
        </tr>
        <tr style="background:#eafaf1;">
          <td style="padding:10px;"><code>style</code></td>
          <td style="padding:10px;">内联声明</td>
          <td style="padding:10px;">仅当前元素</td>
          <td style="padding:10px;">动态覆盖、临时调试、JS 控制</td>
        </tr>
      </tbody>
    </table>
  </div>

  <script>
    // ===== class 操作演示 =====
    function toggleClass() {
      const btn = event.target;
      btn.classList.toggle('active');
      const log = document.getElementById('class-log');
      log.textContent = `
// toggleClass 结果:
btn.classList.contains('active') = ${btn.classList.contains('active')}
btn.className = "${btn.className}"
btn.classList.toString() = "${btn.classList.toString()}"

// classList 的优势:
// - 自动处理空格分隔
// - 提供 add/remove/toggle/contains/replace 方法
// - 返回 DOMTokenList 对象(类数组)`;
    }

    function addClassListDemo() {
      const log = document.getElementById('class-log');
      const demoClasses = ['btn', 'primary', 'rounded', 'shadow', 'active'];
      log.textContent = `
// classList 完整 API 演示:

const list = element.classList;

// 基础方法
list.add('new-class')        // 添加类名
list.remove('old-class')     // 移除类名
list.toggle('active')        // 切换类名(返回是否添加)
list.contains('btn')         // 是否包含 → true/false
list.replace('a', 'b')       // 替换类名

// 实用方法
list.length                  // 当前类名数量
list.value                   // 字符串形式
list.entries()               // 迭代器
list.forEach(c => console.log(c))

// 当前示例中的类名组合:
${demoClasses.map((c,i) => `  [${i}] "${c}"`).join('\n')}

// 推荐用法:优先使用 classList 而非 className 字符串拼接`;
    }

    // ===== id 操作演示 =====
    function demoIdApi() {
      const log = document.getElementById('id-log');
      const el = document.getElementById('unique-element');

      log.textContent = `
// ===== ID 相关 API =====

// 1. 通过 ID 获取元素
const el = document.getElementById('unique-element');
// 返回:${el ? 'Element 对象 ✓' : 'null ✗'}

// 2. 通过 CSS 选择器获取
const el2 = document.querySelector('#unique-element');
// 返回:${el2 ? 'Element 对象 ✓' : 'null ✗'}

// 3. 获取/设置 ID
el.id                    // "${el.id}"
el.id = 'new-id';        // 修改 ID

// 4. 锚点跳转
location.hash = '#unique-element';
// 页面滚动到该元素位置

// 5. 表单关联(label for)
<label for="username">  ← 关联 →  <input id="username">

// ⚠️ 注意事项:
// - 同一文档中 ID 必须唯一
// - ID 大小写敏感
// - 不能以数字开头(CSS 选择器限制)
// - 避免使用纯数字作为 ID`;
    }

    // ===== style 操作演示 =====
    let hasBorder = false;
    function changeColor(color) {
      const el = document.getElementById('style-demo');
      el.style.color = color;
      updateStyleLog(`color: ${color}`);
    }

    function changeSize(size) {
      const el = document.getElementById('style-demo');
      const p = el.querySelector('p');
      p.style.fontSize = size + 'px';
      updateStyleLog(`fontSize: ${size}px`);
    }

    function toggleBorder() {
      const el = document.getElementById('style-demo');
      hasBorder = !hasBorder;
      el.style.border = hasBorder ? '3px dashed #3498db' : 'none';
      updateStyleLog(`border: ${hasBorder ? '3px dashed #3498db' : 'none'}`);
    }

    function resetStyle() {
      const el = document.getElementById('style-demo');
      el.removeAttribute('style');
      el.setAttribute('style', 'padding:20px; background:#f8f9fa; border-radius:8px; text-align:center; transition:all 0.5s;');
      el.querySelector('p').removeAttribute('style');
      hasBorder = false;
      updateStyleLog('样式已重置');
    }

    function updateStyleLog(action) {
      const log = document.getElementById('style-log');
      const now = new Date().toLocaleTimeString();
      log.textContent = `[${now}] 执行:element.style.${action}\n` +
                        '// 读取当前内联样式:\n' +
                        `element.style.cssText = "${document.getElementById('style-demo').style.cssText}"\n\n` +
                        "// 注意:style 属性只能操作内联样式\n" +
                        "// 要获取计算后的最终样式,请使用 getComputedStyle()";
    }
  </script>
</body>
</html>

id - 唯一标识符

id 属性为元素指定唯一的标识符。在整个文档中,每个 id 值应该是唯一的。

主要用途

  1. CSS 选择器:通过 #id 选择器定位元素
  2. JavaScript 操作:使用 document.getElementById() 获取元素
  3. 锚点链接:创建页面内跳转链接(如 #section1
  4. 表单关联<label>for 属性关联表单元素

规则与限制

规则说明示例
唯一性文档中必须唯一❌ 两个元素使用 id="header"
大小写敏感id="Header"id="header"id="mainHeader"
无空格不能包含空格字符id="main header"
字符限制几乎所有字符都允许,但建议只用字母、数字、连字符、下划线id="user-123"
不能以数字开头(CSS)虽然 HTML 允许,但 CSS 选择器需要转义⚠️ id="1header" 需写成 #\31 header

JavaScript API

javascript
// 获取元素
const element = document.getElementById('myId');

// 检查 ID 是否存在
if (document.getElementById('myId')) {
  console.log('元素存在');
}

// 设置 ID
element.id = 'newId';

// 通过 CSS 选择器获取
const element = document.querySelector('#myId');

使用示例

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>id 属性示例</title>
  <style>
    /* CSS 选择器 */
    #header {
      background-color: #333;
      color: white;
      padding: 20px;
    }
    
    #main-content {
      margin: 20px;
    }
  </style>
</head>
<body>
  <!-- 页面头部 -->
  <header id="header">
    <h1>网站标题</h1>
  </header>

  <!-- 主要内容 -->
  <main id="main-content">
    <h2 id="section1">第一章节</h2>
    <p>这是第一章节的内容。</p>
    
    <h2 id="section2">第二章节</h2>
    <p>这是第二章节的内容。</p>
  </main>

  <!-- 锚点导航 -->
  <nav id="navigation">
    <a href="#section1">跳转到第一章</a>
    <a href="#section2">跳转到第二章</a>
  </nav>

  <!-- 表单关联 -->
  <form>
    <label for="username">用户名:</label>
    <input type="text" id="username" name="username">
    
    <label for="email">邮箱:</label>
    <input type="email" id="email" name="email">
  </form>

  <script>
    // JavaScript 操作
    const header = document.getElementById('header');
    header.style.borderBottom = '2px solid #666';
    
    // 平滑滚动到锚点
    document.querySelectorAll('a[href^="#"]').forEach(anchor => {
      anchor.addEventListener('click', function (e) {
        e.preventDefault();
        const target = document.querySelector(this.getAttribute('href'));
        if (target) {
          target.scrollIntoView({ behavior: 'smooth' });
        }
      });
    });
  </script>
</body>
</html>

最佳实践

html
<!-- ✅ 推荐:语义化、有意义的 ID -->
<header id="site-header"></header>
<nav id="main-navigation"></nav>
<main id="content"></main>
<article id="article-123"></article>

<!-- ❌ 避免:无意义或自动生成的 ID -->
<div id="div1"></div>
<div id="container-wrapper-box"></div>
<div id="____"></div>

class - 样式类名

class 属性为元素分配一个或多个类名,用于样式分组和 JavaScript 批量操作。

主要用途

  1. CSS 样式分组:为多个元素应用相同的样式
  2. JavaScript 批量操作:使用 document.getElementsByClassName()querySelectorAll() 选择元素
  3. 语义化标记:表示元素的类型或状态

特点

特点说明
可重复同一个类名可用于多个元素
多类名一个元素可以有多个类名,用空格分隔
大小写敏感.Button.button
语义化建议使用有意义的名称(如 .error-message 而非 .red

JavaScript API

javascript
// 获取元素集合(HTMLCollection,动态)
const elements = document.getElementsByClassName('my-class');

// 获取元素集合(NodeList,静态)
const elements = document.querySelectorAll('.my-class');

// 操作类名
const element = document.querySelector('#myElement');

// 添加类名
element.classList.add('new-class');

// 移除类名
element.classList.remove('old-class');

// 切换类名
element.classList.toggle('active');

// 检查是否有某类名
if (element.classList.contains('active')) {
  console.log('元素处于激活状态');
}

// 替换类名
element.classList.replace('old-class', 'new-class');

// 获取类名数量
console.log(element.classList.length);

// 获取所有类名
console.log(element.className); // 字符串
console.log(element.classList); // DOMTokenList

命名规范

方法说明示例
BEMBlock__Element--Modifier.card__header--active
OOCSSObject-Oriented CSS.btn, .btn-primary
SMACSSScalable and Modular Architecture.l-grid, .is-active
Utility-First原子化 CSS.p-4, .text-center

使用示例

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>class 属性示例</title>
  <style>
    /* 基础样式 */
    .btn {
      padding: 10px 20px;
      border: none;
      border-radius: 4px;
      cursor: pointer;
      font-size: 14px;
    }
    
    /* 修饰符样式 */
    .btn-primary {
      background-color: #007bff;
      color: white;
    }
    
    .btn-secondary {
      background-color: #6c757d;
      color: white;
    }
    
    .btn-danger {
      background-color: #dc3545;
      color: white;
    }
    
    /* 状态样式 */
    .btn.disabled {
      opacity: 0.5;
      cursor: not-allowed;
    }
    
    .btn.loading {
      position: relative;
      color: transparent;
    }
    
    .btn.loading::after {
      content: '';
      position: absolute;
      width: 16px;
      height: 16px;
      border: 2px solid white;
      border-radius: 50%;
      border-top-color: transparent;
      animation: spin 1s linear infinite;
    }
    
    @keyframes spin {
      to { transform: rotate(360deg); }
    }
    
    /* 尺寸样式 */
    .btn-sm {
      padding: 5px 10px;
      font-size: 12px;
    }
    
    .btn-lg {
      padding: 15px 30px;
      font-size: 18px;
    }
  </style>
</head>
<body>
  <!-- 单个类名 -->
  <p class="highlight">高亮文本</p>
  
  <!-- 多个类名组合 -->
  <button class="btn btn-primary">主要按钮</button>
  <button class="btn btn-secondary">次要按钮</button>
  <button class="btn btn-danger">危险按钮</button>
  
  <!-- 状态类名 -->
  <button class="btn btn-primary disabled">禁用按钮</button>
  <button class="btn btn-primary loading">加载中</button>
  
  <!-- 尺寸类名 -->
  <button class="btn btn-primary btn-sm">小按钮</button>
  <button class="btn btn-primary btn-lg">大按钮</button>

  <script>
    // 切换按钮状态
    document.querySelectorAll('.btn').forEach(btn => {
      btn.addEventListener('click', function() {
        // 添加加载状态
        this.classList.add('loading');
        
        // 模拟异步操作
        setTimeout(() => {
          this.classList.remove('loading');
          this.classList.add('disabled');
        }, 2000);
      });
    });
    
    // 批量操作
    const allButtons = document.querySelectorAll('.btn');
    console.log(`共有 ${allButtons.length} 个按钮`);
  </script>
</body>
</html>

style - 内联样式

style 属性用于直接在元素上定义内联 CSS 样式。

主要用途

  1. 快速样式应用:为单个元素快速添加样式
  2. 动态样式:通过 JavaScript 动态修改样式
  3. 覆盖外部样式:内联样式优先级最高

注意事项

WARNING

不推荐大规模使用 style 属性,原因:

  1. 维护困难:样式分散在 HTML 中,难以统一管理
  2. 代码重复:相同样式需要在多个元素上重复定义
  3. 性能影响:内联样式无法被浏览器缓存
  4. 可访问性:不利于响应式设计和主题切换

最佳实践:将样式放在 <style> 标签或外部 CSS 文件中,仅在需要动态修改或覆盖特定样式时使用内联样式。

JavaScript API

javascript
const element = document.getElementById('myElement');

// 读取样式(注意:只能读取内联样式)
console.log(element.style.color);
console.log(element.style.backgroundColor);

// 设置样式
element.style.color = 'red';
element.style.backgroundColor = 'blue';
element.style.fontSize = '16px'; // 注意:驼峰命名

// 批量设置样式
element.style.cssText = 'color: red; background: blue; padding: 10px;';

// 清除样式
element.style.color = '';

// 获取计算后的样式
const computedStyle = window.getComputedStyle(element);
console.log(computedStyle.color);
console.log(computedStyle.backgroundColor);

使用示例

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>style 属性示例</title>
  <style>
    .box {
      width: 200px;
      height: 200px;
      background-color: #f0f0f0;
      transition: all 0.3s;
    }
  </style>
</head>
<body>
  <!-- 基本用法 -->
  <p style="color: red; font-size: 14px;">红色文字</p>

  <!-- 动态修改样式 -->
  <div id="box" class="box" style="border: 2px solid blue;">
    动态盒子
  </div>

  <button onclick="changeColor()">改变颜色</button>
  <button onclick="changeSize()">改变大小</button>
  <button onclick="resetStyle()">重置样式</button>

  <script>
    const box = document.getElementById('box');
    
    function changeColor() {
      // 随机颜色
      const randomColor = '#' + Math.floor(Math.random()*16777215).toString(16);
      box.style.backgroundColor = randomColor;
    }
    
    function changeSize() {
      // 动态修改尺寸
      const currentWidth = parseInt(box.style.width || '200');
      box.style.width = (currentWidth + 50) + 'px';
      box.style.height = (currentWidth + 50) + 'px';
    }
    
    function resetStyle() {
      // 重置样式
      box.style.cssText = 'border: 2px solid blue;';
    }
    
    // 鼠标悬停效果
    box.addEventListener('mouseenter', function() {
      this.style.transform = 'scale(1.1)';
    });
    
    box.addEventListener('mouseleave', function() {
      this.style.transform = 'scale(1)';
    });
  </script>
</body>
</html>

内联样式 vs CSS 类

场景推荐方式原因
静态样式CSS 类可复用、易维护
动态计算的样式内联样式值需要动态计算
用户自定义样式内联样式来自用户输入
主题切换CSS 变量便于全局控制
第三方组件覆盖内联样式优先级高

title - 提示文本

title 属性定义元素的额外信息,当用户鼠标悬停在元素上时会显示提示文本(tooltip)。

主要用途

  1. 提供补充信息:为缩写、术语等提供完整解释
  2. 增强用户体验:为图标按钮、链接等提供说明
  3. 可访问性:为屏幕阅读器用户提供额外上下文

注意事项

  • 提示文本会在鼠标悬停时显示,但延迟时间因浏览器而异
  • 移动设备上可能无法显示(没有鼠标悬停)
  • 不要将重要信息仅放在 title 中,因为某些用户可能无法看到
  • 对于链接,优先使用描述性的链接文本,而不是依赖 title

使用示例

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>title 属性示例</title>
  <style>
    abbr {
      text-decoration: underline dotted;
      cursor: help;
    }
    
    .icon-button {
      padding: 8px;
      background: none;
      border: 1px solid #ccc;
      cursor: pointer;
    }
  </style>
</head>
<body>
  <!-- 缩写解释 -->
  <p>
    <abbr title="HyperText Markup Language">HTML</abbr> 是构建网页的标准标记语言。
    <abbr title="Cascading Style Sheets">CSS</abbr> 用于控制网页的样式。
  </p>

  <!-- 链接提示 -->
  <p>
    访问我们的
    <a href="/about" title="了解更多关于我们的信息、公司历史和团队介绍">
      关于我们
    </a>
    页面。
  </p>

  <!-- 图片说明 -->
  <img src="photo.jpg" alt="照片" title="2023年拍摄于北京颐和园">

  <!-- 表单字段提示 -->
  <form>
    <label for="username">用户名:</label>
    <input type="text" id="username" name="username" 
           title="请输入您的用户名,3-20个字符,只能包含字母、数字和下划线">
    
    <label for="password">密码:</label>
    <input type="password" id="password" name="password"
           title="密码长度至少8位,必须包含大小写字母和数字">
  </form>

  <!-- 图标按钮提示 -->
  <button class="icon-button" title="保存当前更改(快捷键:Ctrl+S)">
    💾
  </button>
  
  <button class="icon-button" title="删除此项(不可恢复)">
    🗑️
  </button>

  <!-- 表格单元格提示 -->
  <table border="1">
    <tr>
      <th>产品</th>
      <th title="产品库存数量">库存</th>
      <th title="产品销售价格(人民币)">价格</th>
    </tr>
    <tr>
      <td>手机</td>
      <td title="库存充足">150</td>
      <td title="含税价格">¥2999</td>
    </tr>
  </table>
</body>
</html>

title vs aria-label

属性可见性用途屏幕阅读器
title鼠标悬停可见补充说明部分支持
aria-label不可见可访问性标签完全支持
alt图片不显示时可见图片替代文本完全支持
html
<!-- 推荐:同时使用 title 和 aria-label -->
<button title="保存更改" aria-label="保存更改">
  💾
</button>

<a id="国际化属性"></a>

国际化属性

lang - 语言标识

lang 属性用于指定元素内容的语言。它对于无障碍访问、搜索引擎优化(SEO)、拼写检查以及正确显示特殊字符等方面都非常重要。

主要用途

  1. 辅助技术支持:帮助屏幕阅读器等辅助设备正确发音和断句
  2. 搜索引擎优化:帮助搜索引擎理解页面内容的语言
  3. 拼写检查:浏览器可以根据语言设置启用正确的拼写检查
  4. 样式控制:CSS 可以基于语言应用不同的样式规则
  5. 字符处理:确保特殊字符和连字正确显示

常见语言代码

语言代码语言代码
简体中文zh-CN英语en
繁体中文zh-TW美式英语en-US
繁体中文(香港)zh-HK英式英语en-GB
日语ja法语fr
韩语ko德语de
西班牙语es俄语ru
阿拉伯语ar葡萄牙语pt

JavaScript API

javascript
// 获取语言设置
const lang = document.documentElement.lang;
console.log(lang); // 'zh-CN'

// 获取特定元素的语言
const element = document.getElementById('content');
const elementLang = element.lang;

// 动态设置语言
document.documentElement.lang = 'en';

// 获取浏览器语言
const browserLang = navigator.language;
console.log(browserLang); // 'zh-CN'

使用示例

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>
  <style>
    /* 根据语言应用不同样式 */
    [lang="ar"] {
      font-family: 'Traditional Arabic', serif;
      direction: rtl;
    }
    
    [lang="ja"] {
      font-family: 'Hiragino Sans', sans-serif;
    }
    
    [lang="en"] {
      font-family: 'Georgia', serif;
    }
    
    /* 语言标记 */
    .lang-tag {
      font-size: 0.8em;
      color: #666;
      margin-left: 5px;
    }
  </style>
</head>
<body>
  <header>
    <h1>多语言内容示例</h1>
    <nav>
      <a href="#" lang="en">Home</a> | 
      <a href="#" lang="zh-CN">首页</a> |
      <a href="#" lang="ja">ホーム</a> |
      <a href="#" lang="ar">الرئيسية</a>
    </nav>
  </header>

  <main>
    <!-- 英语部分 -->
    <article lang="en">
      <h2>English Article</h2>
      <p>This is an article written in English. The browser will use English pronunciation for screen readers.</p>
      <blockquote>
        "To be or not to be, that is the question."
        <footer>— William Shakespeare</footer>
      </blockquote>
    </article>

    <!-- 中文部分 -->
    <article lang="zh-CN">
      <h2>中文文章</h2>
      <p>这是一篇用简体中文写的文章。浏览器会使用中文语音进行朗读。</p>
      <blockquote>
        "学而不思则罔,思而不学则殆。"
        <footer>— 孔子</footer>
      </blockquote>
    </article>

    <!-- 日语部分 -->
    <article lang="ja">
      <h2>日本語の記事</h2>
      <p>これは日本語で書かれた記事です。ブラウザは日本語の発音を使用します。</p>
      <blockquote>
        "七転び八起き"
        <footer>— 日本のことわざ</footer>
      </blockquote>
    </article>

    <!-- 阿拉伯语部分(RTL) -->
    <article lang="ar" dir="rtl">
      <h2>مقال عربي</h2>
      <p>هذا مقال مكتوب باللغة العربية. سيستخدم المتصفح النطق العربي.</p>
    </article>

    <!-- 法语引用 -->
    <blockquote lang="fr">
      <p>"Ceci est une citation en français."</p>
      <footer>— Un auteur français</footer>
    </blockquote>
  </main>

  <footer>
    <p>&copy; 2023 多语言网站</p>
  </footer>

  <script>
    // 动态语言切换
    function changeLanguage(lang) {
      document.documentElement.lang = lang;
      
      // 更新页面内容(实际项目中会从服务器加载翻译)
      const translations = {
        'zh-CN': {
          title: '多语言页面示例',
          greeting: '欢迎访问'
        },
        'en': {
          title: 'Multilingual Page',
          greeting: 'Welcome'
        }
      };
      
      document.title = translations[lang]?.title || document.title;
    }

    // 语言选择器
    const langSelector = document.createElement('select');
    langSelector.innerHTML = `
      <option value="zh-CN">简体中文</option>
      <option value="en">English</option>
      <option value="ja">日本語</option>
    `;
    langSelector.addEventListener('change', (e) => {
      changeLanguage(e.target.value);
    });
    document.body.insertBefore(langSelector, document.body.firstChild);
  </script>
</body>
</html>

dir - 文本方向

dir 属性用于指定元素内容的文本方向(即文字的排列方向)。

取值说明

说明适用场景
ltr从左到右(默认)英语、中文等 LTR 语言
rtl从右到左阿拉伯语、希伯来语等 RTL 语言
auto自动判断方向用户输入内容、混合文本

JavaScript API

javascript
// 获取文本方向
const dir = element.dir;

// 设置文本方向
element.dir = 'rtl';

// 获取计算后的文本方向
const computedDir = window.getComputedStyle(element).direction;

使用示例

html
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
  <meta charset="UTF-8">
  <title>dir 属性示例</title>
  <style>
    .text-example {
      padding: 15px;
      margin: 10px 0;
      border: 1px solid #ddd;
      background: #f9f9f9;
    }
    
    [dir="rtl"] {
      text-align: right;
    }
    
    [dir="ltr"] {
      text-align: left;
    }
  </style>
</head>
<body>
  <h1>文本方向示例</h1>

  <!-- 从左到右 -->
  <div class="text-example" dir="ltr">
    <p>This text is written from left to right.</p>
    <p>这是从左到右书写的中文文本。</p>
  </div>

  <!-- 从右到左 -->
  <div class="text-example" dir="rtl">
    <p>هذا النص مكتوب من اليمين إلى اليسار</p>
    <p>זהו טקסט שנכתב מימין לשמאל</p>
  </div>

  <!-- 自动判断 -->
  <div class="text-example" dir="auto">
    <p>Hello العالم مرحبا</p>
  </div>

  <!-- 表单输入 -->
  <form>
    <label for="english-name">Name (English):</label>
    <input type="text" id="english-name" dir="ltr">
    
    <label for="arabic-name">الاسم (العربية):</label>
    <input type="text" id="arabic-name" dir="rtl">
  </form>

  <!-- 混合内容 -->
  <article>
    <h2 dir="rtl">عنوان عربي</h2>
    <p dir="rtl">
      هذه فقرة باللغة العربية.
      <span dir="ltr">This is English text embedded in Arabic.</span>
      وباقي النص بالعربية.
    </p>
  </article>

  <script>
    // 动态检测文本方向
    function detectDirection(text) {
      // 检测第一个强方向字符
      const rtlRegex = /[\u0591-\u07FF\uFB1D-\uFDFD\uFE70-\uFEFC]/;
      return rtlRegex.test(text) ? 'rtl' : 'ltr';
    }

    // 为用户输入自动设置方向
    document.querySelectorAll('input[type="text"]').forEach(input => {
      input.addEventListener('input', function() {
        this.dir = detectDirection(this.value);
      });
    });
  </script>
</body>
</html>

注意事项

  1. CSS 优先级:如果同时使用 CSS 的 direction 属性和 HTML 的 dir 属性,CSS 的优先级更高
  2. text-align 的区别dir 控制的是文本的整体流向,而 text-align 控制的是行内内容的对齐方式
  3. 表单方向:对于表单元素,特别是输入框,设置正确的 dir 值可以改善用户体验

translate - 翻译控制

translate 属性用于指定在浏览器或翻译工具执行自动翻译时,该元素的文本内容是否应该被翻译。

取值说明

说明适用场景
yes允许翻译(默认)需要翻译的内容
no不允许翻译品牌名、代码、专有名词

使用示例

html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>translate 属性示例</title>
  <style>
    nav ul {
      list-style: none;
      display: flex;
      gap: 20px;
      padding: 0;
    }
    
    nav li {
      padding: 10px 20px;
      background: #244ca5;
      color: white;
    }
    
    [translate="no"] {
      border-bottom: 2px solid #ff9800;
    }
  </style>
</head>
<body>
  <nav>
    <ul>
      <li>Home Page</li>
      <li translate="no">Python</li>
      <li>Course</li>
      <li>Book</li>
      <li>Resource Library</li>
      <li>Community</li>
      <li>Service</li>
      <li translate="no">App</li>
    </ul>
  </nav>

  <!-- 代码示例不应翻译 -->
  <article>
    <h2>Code Example</h2>
    <pre translate="no"><code>
function hello() {
  console.log("Hello, World!");
}
    </code></pre>
  </article>

  <!-- 品牌名不应翻译 -->
  <p>
    Welcome to <span translate="no">Microsoft</span>!
    Learn more about <span translate="no">Windows 11</span>.
  </p>

  <!-- 个人姓名通常不翻译 -->
  <p>
    Author: <span translate="no">张三</span>
  </p>
</body>
</html>

注意事项

  • 浏览器支持因内核和版本而异
  • 对于不需要翻译的内容(如品牌名、代码、专有名词),应设置为 translate="no"
  • 可以继承父元素的 translate 设置

<a id="可访问性属性"></a>

可访问性属性

tabindex - 焦点控制

tabindex 属性用于控制元素是否可以通过键盘的 Tab 键获得焦点,以及焦点的导航顺序。它是构建可访问 Web 应用程序的关键属性之一。

取值说明

行为适用场景推荐度
省略自然导航顺序大多数元素✅ 推荐
0按DOM顺序可聚焦自定义控件✅ 推荐
-1仅编程可聚焦模态框、动态焦点✅ 推荐
正整数自定义顺序不推荐❌ 避免

焦点管理流程

code
用户按 Tab 键
    ↓
检查 tabindex 值
    ↓
├─ 省略或 0 → 按DOM顺序导航
├─ -1 → 跳过(不参与Tab导航)
└─ 正整数 → 按数值从小到大导航(不推荐)
    ↓
聚焦到目标元素
    ↓
触发 focus 事件
    ↓
显示焦点样式(outline)

JavaScript API

javascript
const element = document.getElementById('myElement');

// 获取 tabindex
console.log(element.tabIndex); // -1, 0, 或正整数

// 设置 tabindex
element.tabIndex = 0;

// 编程聚焦
element.focus();

// 编程失焦
element.blur();

// 检查是否可聚焦
function isFocusable(element) {
  if (element.tabIndex < 0) return false;
  if (element.hasAttribute('disabled')) return false;
  if (element.hidden) return false;
  return true;
}

// 获取所有可聚焦元素
const focusableElements = document.querySelectorAll(
  'button, [href], input, select, textarea, [tabindex]:not([tabindex="-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>tabindex 示例</title>
  <style>
    /* 自定义焦点样式 */
    :focus {
      outline: 2px solid #0066ff;
      outline-offset: 2px;
    }
    
    .custom-control {
      padding: 10px;
      margin: 5px;
      border: 1px solid #ccc;
      cursor: pointer;
      display: inline-block;
    }
    
    .custom-control:hover {
      background-color: #f0f0f0;
    }
    
    /* 模态框样式 */
    #modal {
      position: fixed;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      padding: 20px;
      background: white;
      border: 1px solid #ccc;
      box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
      z-index: 1000;
    }
    
    .overlay {
      position: fixed;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      background: rgba(0, 0, 0, 0.5);
      z-index: 999;
    }
    
    /* 焦点陷阱提示 */
    .focus-trap-info {
      background: #fff3cd;
      padding: 10px;
      margin: 10px 0;
    }
  </style>
</head>
<body>
  <h1>tabindex 完整示例</h1>

  <h2>自然焦点顺序</h2>
  <p>使用 Tab 键测试这些按钮的焦点顺序:</p>
  <button>按钮1</button>
  <button>按钮2</button>
  <button>按钮3</button>

  <h2>自定义控件(tabindex="0")</h2>
  <p>使用 Tab 键可以聚焦这些自定义元素:</p>
  <div class="custom-control" tabindex="0" role="button">
    自定义控件1
  </div>
  <div class="custom-control" tabindex="0" role="button">
    自定义控件2
  </div>
  <div class="custom-control" tabindex="0" role="button">
    自定义控件3
  </div>

  <h2>避免使用正整数 tabindex</h2>
  <p class="focus-trap-info">
    ⚠️ 以下示例使用正整数 tabindex,这会破坏自然的 DOM 顺序,不推荐使用!
  </p>
  <button>按钮A</button>
  <div class="custom-control" tabindex="3">不应该这样(tabindex="3")</div>
  <div class="custom-control" tabindex="1">第一个获得焦点(tabindex="1")</div>
  <div class="custom-control" tabindex="2">第二个获得焦点(tabindex="2")</div>
  <button>按钮B</button>

  <h2>编程控制焦点(tabindex="-1")</h2>
  <button id="open-modal">打开模态框</button>
  
  <div id="overlay" class="overlay" tabindex="-1" style="display:none;"></div>
  <div id="modal" tabindex="-1" style="display:none;">
    <h3>模态对话框</h3>
    <p>这是一个模态框。按 Esc 键关闭。</p>
    <input type="text" placeholder="输入内容">
    <button id="close-modal">关闭</button>
  </div>

  <h2>焦点陷阱(Focus Trap)</h2>
  <div class="focus-trap-info">
    模态框打开后,焦点会被限制在模态框内循环,不会回到背景内容。
  </div>

  <script>
    // 为自定义控件添加键盘支持
    document.querySelectorAll('.custom-control').forEach(control => {
      control.addEventListener('keydown', function(e) {
        if (e.key === 'Enter' || e.key === ' ') {
          e.preventDefault();
          this.click();
        }
      });
      
      control.addEventListener('click', function() {
        alert(`你点击了: ${this.textContent}`);
      });
    });

    // 模态框管理
    const openBtn = document.getElementById('open-modal');
    const closeBtn = document.getElementById('close-modal');
    const modal = document.getElementById('modal');
    const overlay = document.getElementById('overlay');
    
    // 保存打开模态框前的焦点元素
    let previouslyFocused = null;

    openBtn.addEventListener('click', function() {
      previouslyFocused = document.activeElement;
      
      overlay.style.display = 'block';
      modal.style.display = 'block';
      
      // 聚焦到模态框
      modal.focus();
    });

    closeBtn.addEventListener('click', closeModal);
    overlay.addEventListener('click', closeModal);

    function closeModal() {
      overlay.style.display = 'none';
      modal.style.display = 'none';
      
      // 恢复之前的焦点
      if (previouslyFocused) {
        previouslyFocused.focus();
      }
    }

    // 键盘事件处理
    document.addEventListener('keydown', function(e) {
      if (e.key === 'Escape' && modal.style.display === 'block') {
        closeModal();
      }
      
      // Tab 键焦点陷阱
      if (e.key === 'Tab' && modal.style.display === 'block') {
        const focusableElements = modal.querySelectorAll(
          'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
        );
        
        const firstElement = focusableElements[0];
        const lastElement = focusableElements[focusableElements.length - 1];
        
        if (e.shiftKey) {
          // Shift + Tab
          if (document.activeElement === firstElement) {
            e.preventDefault();
            lastElement.focus();
          }
        } else {
          // Tab
          if (document.activeElement === lastElement) {
            e.preventDefault();
            firstElement.focus();
          }
        }
      }
    });

    // 焦点管理工具函数
    class FocusManager {
      constructor() {
        this.focusableSelectors = [
          'button:not([disabled])',
          '[href]',
          'input:not([disabled])',
          'select:not([disabled])',
          'textarea:not([disabled])',
          '[tabindex]:not([tabindex="-1"]):not([disabled])'
        ].join(', ');
      }
      
      getFocusableElements(container = document) {
        return Array.from(container.querySelectorAll(this.focusableSelectors))
          .filter(el => !el.hidden && el.offsetParent !== null);
      }
      
      trapFocus(container) {
        const focusableElements = this.getFocusableElements(container);
        const firstElement = focusableElements[0];
        const lastElement = focusableElements[focusableElements.length - 1];
        
        container.addEventListener('keydown', (e) => {
          if (e.key !== 'Tab') return;
          
          if (e.shiftKey) {
            if (document.activeElement === firstElement) {
              e.preventDefault();
              lastElement.focus();
            }
          } else {
            if (document.activeElement === lastElement) {
              e.preventDefault();
              firstElement.focus();
            }
          }
        });
      }
    }

    // 使用焦点管理器
    const focusManager = new FocusManager();
    focusManager.trapFocus(modal);
  </script>
</body>
</html>

accesskey - 快捷键

accesskey 属性用于为元素分配快捷键,使用户可以通过键盘快速访问该元素。

浏览器支持差异

浏览器/操作系统触发方式
Windows (Chrome/Edge/Firefox)Alt + accesskey
Mac (Safari)Control + Option + accesskey
Mac (Chrome/Firefox)Control + Option + accesskey
LinuxAlt + Shift + accesskey

使用示例

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>accesskey 示例</title>
  <style>
    .shortcut-hint {
      font-size: 0.8em;
      color: #666;
      margin-left: 5px;
    }
    
    kbd {
      background: #f0f0f0;
      border: 1px solid #ccc;
      border-radius: 3px;
      padding: 2px 6px;
      font-family: monospace;
    }
  </style>
</head>
<body>
  <header>
    <h1>网站导航(带快捷键)</h1>
    <nav>
      <ul>
        <li><a href="/" accesskey="h">首页 <kbd>Alt+H</kbd></a></li>
        <li><a href="/products" accesskey="p">产品 <kbd>Alt+P</kbd></a></li>
        <li><a href="/services" accesskey="s">服务 <kbd>Alt+S</kbd></a></li>
        <li><a href="/contact" accesskey="c">联系我们 <kbd>Alt+C</kbd></a></li>
      </ul>
    </nav>
  </header>

  <main>
    <section>
      <h2>搜索 <kbd>Alt+F</kbd></h2>
      <form action="/search" method="get">
        <label for="search-input">搜索内容:</label>
        <input type="text" id="search-input" name="q" accesskey="f">
        <button type="submit" accesskey="g">搜索 <kbd>Alt+G</kbd></button>
      </form>
    </section>

    <section>
      <h2>快捷键列表</h2>
      <table>
        <thead>
          <tr>
            <th>快捷键</th>
            <th>功能</th>
          </tr>
        </thead>
        <tbody>
          <tr><td><kbd>Alt+H</kbd></td><td>返回首页</td></tr>
          <tr><td><kbd>Alt+P</kbd></td><td>产品页面</td></tr>
          <tr><td><kbd>Alt+S</kbd></td><td>服务页面</td></tr>
          <tr><td><kbd>Alt+C</kbd></td><td>联系我们</td></tr>
          <tr><td><kbd>Alt+F</kbd></td><td>聚焦搜索框</td></tr>
          <tr><td><kbd>Alt+G</kbd></td><td>执行搜索</td></tr>
        </tbody>
      </table>
    </section>
  </main>

  <footer>
    <p>&copy; 2023 我的网站</p>
    <button id="back-to-top" accesskey="t">返回顶部 <kbd>Alt+T</kbd></button>
  </footer>

  <script>
    // 返回顶部功能
    document.getElementById('back-to-top').addEventListener('click', () => {
      window.scrollTo({ top: 0, behavior: 'smooth' });
    });

    // 显示快捷键提示
    document.addEventListener('keydown', (e) => {
      if (e.altKey) {
        const key = e.key.toLowerCase();
        const element = document.querySelector(`[accesskey="${key}"]`);
        if (element) {
          console.log(`快捷键激活: ${element.textContent}`);
        }
      }
    });
  </script>
</body>
</html>

最佳实践

  1. 避免冲突:不要与浏览器或操作系统快捷键冲突
  2. 提供提示:在界面上显示快捷键提示
  3. 一致性:在整个网站中使用一致的快捷键
  4. 可访问性:不要仅依赖快捷键,确保所有功能都有其他访问方式

<a id="交互控制属性"></a>

交互控制属性

hidden - 元素隐藏

hidden 属性用于指示元素当前不应该被显示。这个属性简单但功能强大,是控制元素可见性的重要工具。

与 CSS display: none 的区别

特性hidden 属性display: none
语义表示元素当前不可见(有语义含义)纯粹的样式控制
可访问性屏幕阅读器通常会忽略取决于实现,可能被忽略
JavaScript 操作直接修改属性需要修改样式
性能更高效(直接属性操作)需要样式计算

JavaScript API

javascript
const element = document.getElementById('myElement');

// 获取 hidden 状态
console.log(element.hidden); // true 或 false

// 设置 hidden
element.hidden = true; // 隐藏
element.hidden = false; // 显示

// 切换显示状态
element.hidden = !element.hidden;

// 检查元素是否隐藏
if (element.hidden) {
  console.log('元素已隐藏');
}

使用示例

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>hidden 属性示例</title>
  <style>
    .container {
      margin: 20px;
      padding: 20px;
      border: 1px solid #ddd;
    }
  </style>
</head>
<body>
  <h1>hidden 属性完整示例</h1>

  <!-- 基本用法 -->
  <div class="container">
    <h2>基本显示/隐藏</h2>
    <button id="toggleButton">切换隐藏状态</button>
    <div id="content">
      <p>这段内容可以通过 JavaScript 控制显示/隐藏。</p>
    </div>
  </div>

  <!-- 条件性显示 -->
  <div class="container">
    <h2>条件性显示</h2>
    <div hidden id="adminPanel">
      <h3>管理面板</h3>
      <ul>
        <li><a href="/users">用户管理</a></li>
        <li><a href="/settings">系统设置</a></li>
      </ul>
    </div>
    <button id="toggleAdmin">切换管理面板</button>
  </div>

  <!-- 加载状态 -->
  <div class="container">
    <h2>加载状态</h2>
    <div id="loading" hidden>
      <p>加载中,请稍候...</p>
    </div>
    <div id="content-loaded">
      <p>这是加载完成后的内容。</p>
    </div>
    <button id="simulateLoad">模拟加载</button>
  </div>

  <!-- 表单隐藏字段 -->
  <div class="container">
    <h2>表单隐藏字段</h2>
    <form action="/submit" method="post">
      <label for="username">用户名:</label>
      <input type="text" id="username" name="username">
      
      <!-- 隐藏的 CSRF 令牌 -->
      <input type="hidden" name="csrf_token" value="abc123xyz">
      
      <!-- 隐藏的用户 ID(用于编辑现有记录) -->
      <input type="hidden" name="user_id" value="42">
      
      <button type="submit">提交</button>
    </form>
  </div>

  <script>
    // 基本切换
    const toggleButton = document.getElementById('toggleButton');
    const content = document.getElementById('content');
    
    toggleButton.addEventListener('click', () => {
      content.hidden = !content.hidden;
      toggleButton.textContent = content.hidden ? '显示内容' : '隐藏内容';
    });

    // 管理面板
    const toggleAdmin = document.getElementById('toggleAdmin');
    const adminPanel = document.getElementById('adminPanel');
    
    // 模拟检查用户权限
    const userRole = 'user'; // 'admin' 或 'user'
    adminPanel.hidden = userRole !== 'admin';
    
    toggleAdmin.addEventListener('click', () => {
      adminPanel.hidden = !adminPanel.hidden;
    });

    // 加载状态
    const loading = document.getElementById('loading');
    const contentLoaded = document.getElementById('content-loaded');
    const simulateLoad = document.getElementById('simulateLoad');
    
    simulateLoad.addEventListener('click', () => {
      loading.hidden = false;
      contentLoaded.hidden = true;
      
      // 模拟异步加载
      setTimeout(() => {
        loading.hidden = true;
        contentLoaded.hidden = false;
      }, 2000);
    });

    // 初始状态
    contentLoaded.hidden = false;
  </script>
</body>
</html>

contenteditable - 可编辑内容

contenteditable 属性允许用户直接在网页上编辑元素内容。

取值说明

含义说明
true 或空字符串可编辑元素及其内容可编辑
false不可编辑元素不可编辑
plaintext-only仅纯文本仅允许纯文本编辑(不支持富文本格式)

JavaScript API

javascript
const element = document.getElementById('myElement');

// 获取 contenteditable 状态
console.log(element.contentEditable); // 'true', 'false', 或 'inherit'
console.log(element.isContentEditable); // true 或 false

// 设置 contenteditable
element.contentEditable = 'true';

// 监听内容变化
element.addEventListener('input', function() {
  console.log('内容已修改:', this.innerHTML);
});

// 监听粘贴事件
element.addEventListener('paste', function(e) {
  e.preventDefault();
  // 只粘贴纯文本
  const text = e.clipboardData.getData('text/plain');
  document.execCommand('insertText', false, text);
});

使用示例

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>contenteditable 示例</title>
  <style>
    .editor-container {
      border: 1px solid #ccc;
      padding: 10px;
      margin: 20px;
      min-height: 200px;
      width: 600px;
    }
    
    .toolbar button {
      margin-right: 5px;
      padding: 5px 10px;
    }
    
    [contenteditable="true"]:focus {
      outline: 2px solid #0066ff;
    }
  </style>
</head>
<body>
  <h1>contenteditable 示例</h1>

  <h2>基础示例</h2>
  <div contenteditable="true" style="border:1px solid #ccc; padding:10px; min-height:100px;">
    点击这里编辑这段文字。你可以添加、删除或修改任何内容。
  </div>

  <h2>富文本编辑器</h2>
  <div class="toolbar">
    <button onclick="formatText('bold')"><b>B</b></button>
    <button onclick="formatText('italic')"><i>I</i></button>
    <button onclick="formatText('underline')"><u>U</u></button>
    <button onclick="formatText('strikeThrough')"><s>S</s></button>
    <button onclick="formatText('insertUnorderedList')">• 列表</button>
    <button onclick="formatText('insertOrderedList')">1. 列表</button>
    <button onclick="insertLink()">链接</button>
    <button onclick="formatText('removeFormat')">清除格式</button>
  </div>
  
  <div id="editor" class="editor-container" contenteditable="true">
    <h2>这是一个可编辑的标题</h2>
    <p>在这里输入内容,使用上方工具栏格式化文本。</p>
    <ul>
      <li>支持列表</li>
      <li>支持多种格式</li>
    </ul>
  </div>

  <div>
    <button onclick="getHTML()">获取HTML</button>
    <button onclick="getText()">获取纯文本</button>
    <button onclick="clearContent()">清空内容</button>
  </div>

  <div id="output" style="margin-top:20px; padding:10px; background:#f0f0f0;"></div>

  <h2>嵌套编辑区域</h2>
  <div contenteditable="true" style="border:1px solid #ccc; padding:10px;">
    <p>这是可编辑的段落。</p>
    <div contenteditable="false" style="background:#f9f9f9; padding:10px;">
      <p>这段文字被明确标记为不可编辑,即使父元素可编辑。</p>
    </div>
    <p>回到可编辑区域。</p>
  </div>

  <h2>仅纯文本编辑</h2>
  <div contenteditable="plaintext-only" style="border:1px solid #ccc; padding:10px; min-height:100px;">
    这个区域只能输入纯文本,不支持富文本格式。
  </div>

  <script>
    // 格式化文本
    function formatText(command, value = null) {
      document.execCommand(command, false, value);
      document.getElementById('editor').focus();
    }

    // 插入链接
    function insertLink() {
      const url = prompt('请输入链接URL:');
      if (url) {
        formatText('createLink', url);
      }
    }

    // 获取HTML
    function getHTML() {
      const editor = document.getElementById('editor');
      document.getElementById('output').innerHTML = 
        '<strong>HTML内容:</strong><pre>' + 
        escapeHtml(editor.innerHTML) + 
        '</pre>';
    }

    // 获取纯文本
    function getText() {
      const editor = document.getElementById('editor');
      document.getElementById('output').innerHTML = 
        '<strong>纯文本内容:</strong><pre>' + 
        escapeHtml(editor.textContent) + 
        '</pre>';
    }

    // 清空内容
    function clearContent() {
      if (confirm('确定要清空内容吗?')) {
        document.getElementById('editor').innerHTML = '';
      }
    }

    // 转义HTML
    function escapeHtml(text) {
      const div = document.createElement('div');
      div.textContent = text;
      return div.innerHTML;
    }

    // 监听编辑器内容变化
    const editor = document.getElementById('editor');
    editor.addEventListener('input', function() {
      console.log('内容已修改');
    });

    // 保存内容(Ctrl+S)
    document.addEventListener('keydown', function(e) {
      if (e.ctrlKey && e.key === 's') {
        e.preventDefault();
        const content = editor.innerHTML;
        console.log('保存的内容:', content);
        alert('内容已保存!\n' + content.substring(0, 100) + '...');
      }
    });
  </script>
</body>
</html>

spellcheck - 拼写检查

spellcheck 属性用于控制元素内容是否应该被拼写检查。

取值说明

说明
true启用拼写检查
false禁用拼写检查
省略浏览器默认行为

JavaScript API

javascript
const element = document.getElementById('myInput');

// 获取拼写检查状态
console.log(element.spellcheck); // true 或 false

// 设置拼写检查
element.spellcheck = true;

使用示例

html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>spellcheck 示例</title>
  <style>
    .container {
      margin: 20px;
      padding: 20px;
      border: 1px solid #ddd;
    }
    
    textarea {
      width: 100%;
      height: 100px;
    }
  </style>
</head>
<body>
  <h1>拼写检查示例</h1>

  <div class="container">
    <h2>启用拼写检查</h2>
    <textarea spellcheck="true">
This is a smple sentnce with sme erors.
You will see red underlines for misspelled words.
    </textarea>
  </div>

  <div class="container">
    <h2>禁用拼写检查</h2>
    <textarea spellcheck="false">
This is a smple sentnce with sme erors.
No red underlines will appear.
    </textarea>
  </div>

  <div class="container">
    <h2>可编辑区域中的拼写检查</h2>
    <div contenteditable="true" spellcheck="true" 
         style="border:1px solid #ccc; padding:10px; min-height:100px;">
      这是一个可编辑区域,启用拼写检查。Try typing some misspelled words.
    </div>
  </div>

  <div class="container">
    <h2>动态控制拼写检查</h2>
    <div id="editable" contenteditable="true" spellcheck="true" 
         style="border:1px solid #ccc; padding:10px; min-height:100px;">
      尝试在这里输入一些拼写错误的单词。
    </div>
    <div>
      <button onclick="toggleSpellcheck(true)">启用拼写检查</button>
      <button onclick="toggleSpellcheck(false)">禁用拼写检查</button>
      <button onclick="checkCurrentState()">检查当前状态</button>
    </div>
  </div>

  <script>
    function toggleSpellcheck(enable) {
      const editor = document.getElementById('editable');
      editor.spellcheck = enable;
      alert(`拼写检查已${enable ? '启用' : '禁用'}`);
    }

    function checkCurrentState() {
      const editor = document.getElementById('editable');
      alert(`当前拼写检查状态: ${editor.spellcheck ? '启用' : '禁用'}`);
    }
  </script>
</body>
</html>

draggable - 拖拽控制

draggable 属性用于控制元素是否可以被拖拽。

取值说明

说明
true元素可拖拽
false元素不可拖拽
auto浏览器决定(图片、链接默认可拖拽)

拖拽事件流程

code
拖拽元素                     放置区域
    |                           |
    | dragstart                 |
    |-------------------------->|
    |                           |
    | drag                      |
    |-------------------------->|
    |                           | dragenter
    |                           |---------->|
    |                           |
    |                           | dragover
    |                           |---------->|
    |                           |
    |                           | dragleave
    |                           |---------->|
    |                           |
    | dragend                   |
    |-------------------------->|
    |                           | drop
    |                           |---------->|

JavaScript API

javascript
const element = document.getElementById('myElement');

// 获取 draggable 状态
console.log(element.draggable); // true 或 false

// 设置 draggable
element.draggable = true;

// 拖拽事件
element.addEventListener('dragstart', function(e) {
  e.dataTransfer.setData('text/plain', this.id);
});

element.addEventListener('dragend', function(e) {
  console.log('拖拽结束');
});

使用示例

详细示例请参见原文档中的拖拽相关章节,包括:

  • 基本可拖拽元素
  • 图像拖拽
  • 列表项拖拽排序
  • 文件拖放上传

<a id="数据属性"></a>

数据属性

data-* - 自定义数据

data-* 属性是 HTML5 引入的一种自定义数据属性,允许开发者在 HTML 元素上存储额外的信息。

命名规则

规则说明示例
必须以 data- 开头前缀不可省略data-user-id
只能包含小写字母、数字、连字符、下划线不支持大写字母data-UserId
连字符会转为驼峰JavaScript 中访问时data-user-iddataset.userId

JavaScript API

javascript
const element = document.getElementById('myElement');

// 获取数据(dataset API)
console.log(element.dataset.userId); // '12345'
console.log(element.dataset.userName); // '张三'

// 设置数据
element.dataset.userId = '67890';
element.dataset.newAttr = '新属性';

// 删除数据
delete element.dataset.userId;

// 传统 API
console.log(element.getAttribute('data-user-id'));
element.setAttribute('data-user-id', 'new-value');
element.removeAttribute('data-user-id');

使用示例

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>data-* 属性示例</title>
  <style>
    /* CSS 中使用 data-* 属性 */
    [data-status="active"] {
      background-color: #4caf50;
      color: white;
    }
    
    [data-status="inactive"] {
      background-color: #f44336;
      color: white;
    }
    
    /* 使用 attr() 显示 data-* 值 */
    .show-data::after {
      content: attr(data-tooltip);
      display: block;
      font-size: 12px;
      color: #666;
      margin-top: 5px;
    }
    
    .user-card {
      border: 1px solid #ddd;
      padding: 15px;
      margin: 10px 0;
      display: flex;
      align-items: center;
      gap: 15px;
    }
    
    .user-card img {
      width: 50px;
      height: 50px;
      border-radius: 50%;
    }
    
    .user-info {
      flex: 1;
    }
  </style>
</head>
<body>
  <h1>data-* 属性完整示例</h1>

  <h2>基础用法</h2>
  <div id="user"
       data-user-id="12345"
       data-username="张三"
       data-role="admin"
       data-last-login="2023-05-15T08:30:00Z">
    用户信息
  </div>

  <h2>用于样式控制</h2>
  <div class="status-indicator" data-status="active">系统状态: 正常</div>
  <div class="status-indicator" data-status="inactive">系统状态: 维护中</div>

  <h2>用于工具提示</h2>
  <button class="show-data" data-tooltip="点击保存更改">保存</button>
  <button class="show-data" data-tooltip="放弃所有未保存的更改">取消</button>

  <h2>用户卡片列表</h2>
  <div id="user-list">
    <!-- 用户卡片将通过 JavaScript 动态生成 -->
  </div>

  <h2>交互式列表</h2>
  <ul id="todo-list">
    <li data-id="1" data-completed="false" data-priority="high">
      <span class="todo-text">完成项目报告</span>
      <button onclick="toggleTodo(this)">完成</button>
      <button onclick="deleteTodo(this)">删除</button>
    </li>
    <li data-id="2" data-completed="true" data-priority="medium">
      <span class="todo-text" style="text-decoration: line-through;">回复邮件</span>
      <button onclick="toggleTodo(this)">撤销</button>
      <button onclick="deleteTodo(this)">删除</button>
    </li>
  </ul>

  <h2>数据筛选</h2>
  <div>
    <button onclick="filterByPriority('all')">全部</button>
    <button onclick="filterByPriority('high')">高优先级</button>
    <button onclick="filterByPriority('medium')">中优先级</button>
    <button onclick="filterByPriority('low')">低优先级</button>
  </div>

  <script>
    // 获取元素
    const userElement = document.getElementById('user');
    
    // 访问 data-* 属性
    console.log('用户ID:', userElement.dataset.userId); // 注意: 连字符转为驼峰命名
    console.log('用户名:', userElement.dataset.username);
    console.log('角色:', userElement.dataset.role);
    console.log('最后登录:', userElement.dataset.lastLogin);
    
    // 修改 data-* 属性
    userElement.dataset.role = 'editor';
    console.log('修改后的角色:', userElement.dataset.role);
    
    // 动态生成用户卡片
    const users = [
      { id: 1, name: '张三', role: 'admin', status: 'active' },
      { id: 2, name: '李四', role: 'user', status: 'active' },
      { id: 3, name: '王五', role: 'user', status: 'inactive' }
    ];
    
    const userList = document.getElementById('user-list');
    users.forEach(user => {
      const card = document.createElement('div');
      card.className = 'user-card';
      card.dataset.userId = user.id;
      card.dataset.userName = user.name;
      card.dataset.userRole = user.role;
      card.dataset.userStatus = user.status;
      
      card.innerHTML = `
        <img src="https://via.placeholder.com/50" alt="${user.name}">
        <div class="user-info">
          <strong>${user.name}</strong>
          <p>角色: ${user.role}</p>
          <p>状态: <span data-status="${user.status}">${user.status === 'active' ? '活跃' : '未激活'}</span></p>
        </div>
        <button onclick="editUser(${user.id})">编辑</button>
        <button onclick="deleteUser(${user.id})">删除</button>
      `;
      
      userList.appendChild(card);
    });
    
    // 用户操作
    function editUser(id) {
      const card = document.querySelector(`[data-user-id="${id}"]`);
      console.log('编辑用户:', card.dataset.userName);
    }
    
    function deleteUser(id) {
      if (confirm('确定要删除这个用户吗?')) {
        const card = document.querySelector(`[data-user-id="${id}"]`);
        card.remove();
      }
    }
    
    // Todo 操作
    function toggleTodo(button) {
      const li = button.parentElement;
      const completed = li.dataset.completed === 'true';
      li.dataset.completed = !completed;
      
      const textSpan = li.querySelector('.todo-text');
      textSpan.style.textDecoration = completed ? 'none' : 'line-through';
      button.textContent = completed ? '完成' : '撤销';
    }
    
    function deleteTodo(button) {
      const li = button.parentElement;
      const id = li.dataset.id;
      if (confirm('确定要删除这个任务吗?')) {
        li.remove();
        console.log('删除任务 ID:', id);
      }
    }
    
    // 筛选功能
    function filterByPriority(priority) {
      const todos = document.querySelectorAll('#todo-list li');
      todos.forEach(todo => {
        if (priority === 'all' || todo.dataset.priority === priority) {
          todo.style.display = '';
        } else {
          todo.style.display = 'none';
        }
      });
    }
  </script>
</body>
</html>

<a id="移动端优化属性"></a>

移动端优化属性

inputmode - 虚拟键盘类型

inputmode 用于向浏览器提示当前输入区域期望的输入类型,从而选择更合适的虚拟键盘布局。

取值说明

键盘类型适用场景
text普通文本键盘(默认)一般文本输入
numeric纯数字键盘数字验证码、PIN码
decimal数字+小数点键盘金额输入
tel电话号码键盘手机号、座机号
email邮箱键盘(带@符号)邮箱地址
urlURL键盘(带/和.com)网址输入
search搜索键盘(回车键显示"搜索")搜索框

使用示例

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>inputmode 示例</title>
  <style>
    form {
      max-width: 500px;
      margin: 20px;
    }
    
    label {
      display: block;
      margin: 15px 0 5px;
      font-weight: bold;
    }
    
    input {
      width: 100%;
      padding: 10px;
      font-size: 16px;
      border: 1px solid #ddd;
      border-radius: 4px;
    }
    
    .hint {
      font-size: 12px;
      color: #666;
      margin-top: 5px;
    }
  </style>
</head>
<body>
  <h1>inputmode 虚拟键盘优化</h1>
  
  <form>
    <label for="amount">金额(decimal)</label>
    <input type="text" id="amount" name="amount" 
           inputmode="decimal" placeholder="请输入金额">
    <p class="hint">显示数字键盘+小数点</p>
    
    <label for="phone">手机号(tel)</label>
    <input type="text" id="phone" name="phone" 
           inputmode="tel" placeholder="请输入手机号">
    <p class="hint">显示电话号码键盘</p>
    
    <label for="email">邮箱(email)</label>
    <input type="text" id="email" name="email" 
           inputmode="email" placeholder="请输入邮箱">
    <p class="hint">显示带@符号的键盘</p>
    
    <label for="website">网址(url)</label>
    <input type="text" id="website" name="website" 
           inputmode="url" placeholder="请输入网址">
    <p class="hint">显示带/和.com的键盘</p>
    
    <label for="search">搜索(search)</label>
    <input type="text" id="search" name="search" 
           inputmode="search" placeholder="搜索内容">
    <p class="hint">回车键显示"搜索"</p>
    
    <label for="pin">验证码(numeric)</label>
    <input type="text" id="pin" name="pin" 
           inputmode="numeric" maxlength="6" placeholder="请输入6位验证码">
    <p class="hint">显示纯数字键盘</p>
    
    <label for="quantity">数量(numeric)</label>
    <input type="text" id="quantity" name="quantity" 
           inputmode="numeric" placeholder="请输入数量">
    <p class="hint">显示纯数字键盘</p>
  </form>

  <script>
    // 监听输入事件
    document.querySelectorAll('input').forEach(input => {
      input.addEventListener('input', function() {
        console.log(`输入内容: ${this.value}, inputmode: ${this.inputMode}`);
      });
    });
  </script>
</body>
</html>

enterkeyhint - 回车键提示

enterkeyhint 用于为虚拟键盘上的"回车/确认"键指定提示文案。

取值说明

显示文本适用场景
enter回车/换行多行文本
done完成完成输入
go前往导航、跳转
next下一步表单多步填写
previous上一步表单多步填写
search搜索搜索框
send发送消息发送

使用示例

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>enterkeyhint 示例</title>
  <style>
    form {
      max-width: 500px;
      margin: 20px;
    }
    
    label {
      display: block;
      margin: 15px 0 5px;
      font-weight: bold;
    }
    
    input {
      width: 100%;
      padding: 10px;
      font-size: 16px;
      border: 1px solid #ddd;
      border-radius: 4px;
    }
  </style>
</head>
<body>
  <h1>enterkeyhint 回车键提示</h1>
  
  <form>
    <label for="search">搜索框</label>
    <input type="search" id="search" name="q" 
           enterkeyhint="search" placeholder="搜索内容">
    
    <label for="message">消息发送</label>
    <input type="text" id="message" name="message" 
           enterkeyhint="send" placeholder="输入消息">
    
    <label for="url">网址输入</label>
    <input type="url" id="url" name="url" 
           enterkeyhint="go" placeholder="输入网址">
    
    <label for="name">多步表单</label>
    <input type="text" id="name" name="name" 
           enterkeyhint="next" placeholder="第一步:姓名">
    
    <label for="comment">评论输入</label>
    <input type="text" id="comment" name="comment" 
           enterkeyhint="done" placeholder="输入评论">
  </form>

  <script>
    // 监听回车事件
    document.querySelectorAll('input').forEach(input => {
      input.addEventListener('keypress', function(e) {
        if (e.key === 'Enter') {
          e.preventDefault();
          console.log(`回车键按下,提示类型: ${this.enterKeyHint}`);
          
          // 根据提示类型执行不同操作
          switch(this.enterKeyHint) {
            case 'search':
              console.log('执行搜索');
              break;
            case 'send':
              console.log('发送消息');
              break;
            case 'next':
              console.log('跳转到下一个字段');
              break;
            case 'done':
              console.log('完成输入');
              break;
          }
        }
      });
    });
  </script>
</body>
</html>

autocapitalize - 自动大写

autocapitalize 主要用于控制在虚拟键盘输入时,浏览器是否自动将输入内容的首字母大写。

取值说明

说明适用场景
off / none不自动大写账号、邮箱、验证码
on按平台默认策略默认行为
sentences每句首字母大写段落文本
words每个单词首字母大写姓名、标题
characters所有字符大写验证码、ID

使用示例

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>autocapitalize 示例</title>
  <style>
    form {
      max-width: 500px;
      margin: 20px;
    }
    
    label {
      display: block;
      margin: 15px 0 5px;
      font-weight: bold;
    }
    
    input {
      width: 100%;
      padding: 10px;
      font-size: 16px;
      border: 1px solid #ddd;
      border-radius: 4px;
    }
  </style>
</head>
<body>
  <h1>autocapitalize 自动大写控制</h1>
  
  <form>
    <label for="username">用户名(无大写)</label>
    <input type="text" id="username" name="username" 
           autocapitalize="none" placeholder="输入用户名">
    
    <label for="email">邮箱(无大写)</label>
    <input type="email" id="email" name="email" 
           autocapitalize="none" placeholder="输入邮箱">
    
    <label for="fullName">姓名(单词首字母大写)</label>
    <input type="text" id="fullName" name="fullName" 
           autocapitalize="words" placeholder="输入姓名">
    
    <label for="title">标题(句子首字母大写)</label>
    <input type="text" id="title" name="title" 
           autocapitalize="sentences" placeholder="输入标题">
    
    <label for="code">验证码(全大写)</label>
    <input type="text" id="code" name="code" 
           autocapitalize="characters" maxlength="6" placeholder="输入验证码">
  </form>
</body>
</html>

autofocus - 自动聚焦

autofocus 是一个布尔全局属性,用于在页面加载完成后自动将键盘焦点移动到指定元素上。

使用示例

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>autofocus 示例</title>
</head>
<body>
  <h1>登录页面</h1>
  
  <form>
    <!-- 页面加载后自动聚焦到用户名输入框 -->
    <label for="username">用户名:</label>
    <input type="text" id="username" name="username" autofocus>
    
    <label for="password">密码:</label>
    <input type="password" id="password" name="password">
    
    <button type="submit">登录</button>
  </form>

  <script>
    // 检查 autofocus 支持
    const autofocusSupported = 'autofocus' in document.createElement('input');
    console.log('autofocus 支持:', autofocusSupported);
    
    // 如果不支持,手动聚焦
    if (!autofocusSupported) {
      document.getElementById('username').focus();
    }
  </script>
</body>
</html>

注意事项

  • 每个文档中只应有一个带 autofocus 的元素
  • 在复杂单页应用中,推荐使用 JavaScript 明确控制焦点
  • 对屏幕阅读器用户来说,自动聚焦可能打断阅读流程

<a id="web-components-属性"></a>

Web Components 属性

slot - 插槽

slot 用于指定传入的内容要插入到哪个插槽中。

part - 样式部件

part 用于为 Shadow DOM 内部元素打上"部件名称",以便宿主页面通过 ::part() 伪元素选择器进行样式覆盖。

exportparts - 部件导出

exportparts 用于在组件嵌套时,把内部组件的 part 名称重新映射并暴露出去。

is - 自定义元素

is 属性用于"自定义内建元素"(Customized built-in elements),让内建标签使用自定义元素的逻辑。

完整示例

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>Web Components 属性示例</title>
  <style>
    /* 自定义危险按钮 */
    button[is="danger-button"] {
      background-color: #e02424;
      color: white;
      border: none;
      padding: 10px 20px;
      border-radius: 4px;
      cursor: pointer;
    }
    
    button[is="danger-button"]:hover {
      background-color: #c81e1e;
    }
  </style>
</head>
<body>
  <h1>Web Components 属性示例</h1>

  <h2>自定义内建元素(is 属性)</h2>
  <button is="danger-button">删除</button>
  
  <script>
    // 定义自定义元素
    class DangerButton extends HTMLButtonElement {
      connectedCallback() {
        this.addEventListener('click', () => {
          if (confirm('确定要删除吗?')) {
            console.log('删除操作已执行');
          }
        });
      }
    }
    
    customElements.define('danger-button', DangerButton, { extends: 'button' });
  </script>

  <h2>插槽示例(slot 属性)</h2>
  <template id="card-template">
    <style>
      .card {
        border: 1px solid #ddd;
        padding: 20px;
        border-radius: 8px;
        box-shadow: 0 2px 4px rgba(0,0,0,0.1);
      }
      .card-header {
        font-size: 1.2em;
        font-weight: bold;
        margin-bottom: 10px;
      }
      .card-body {
        color: #666;
      }
      .card-footer {
        margin-top: 10px;
        padding-top: 10px;
        border-top: 1px solid #eee;
      }
    </style>
    <div class="card">
      <div class="card-header">
        <slot name="title"></slot>
      </div>
      <div class="card-body">
        <slot name="content"></slot>
      </div>
      <div class="card-footer">
        <slot name="footer"></slot>
      </div>
    </div>
  </template>
  
  <my-card>
    <h3 slot="title">卡片标题</h3>
    <p slot="content">这是卡片的内容部分。</p>
    <button slot="footer">操作按钮</button>
  </my-card>

  <script>
    class MyCard extends HTMLElement {
      constructor() {
        super();
        const template = document.getElementById('card-template');
        const shadowRoot = this.attachShadow({ mode: 'open' });
        shadowRoot.appendChild(template.content.cloneNode(true));
      }
    }
    
    customElements.define('my-card', MyCard);
  </script>
</body>
</html>

<a id="高级特性属性"></a>

高级特性属性

<h4>047-inert-microdata.html</h4>
html
<!DOCTYPE html>
<!--
  来源:基础知识/7-全局属性.md
  演示:inert 属性(禁用区域内所有交互)、微数据 itemscope/itemprop/itemtype 结构化数据
-->
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>inert 属性与微数据演示</title>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body { font-family: -apple-system, sans-serif; padding: 30px; background: #f0f2f5; }
    h1 { color: #1a1a1a; margin-bottom: 25px; }
    .section { background: white; padding: 25px; margin-bottom: 25px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.08); }
    h2 { color: #333; margin-bottom: 15px; font-size: 17px; border-bottom: 2px solid #eee; padding-bottom: 8px; }

    /* inert 演示 */
    .modal-overlay {
      display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%;
      background: rgba(0,0,0,0.6); z-index: 1000; justify-content: center; align-items: center;
    }
    .modal-overlay.show { display: flex; }
    .modal-content {
      background: white; padding: 35px; border-radius: 14px; max-width: 480px; width: 90%;
      box-shadow: 0 20px 60px rgba(0,0,0,0.3); text-align: center;
    }
    .modal-content h2 { border: none; font-size: 22px; margin-bottom: 15px; }

    .page-content { transition: filter 0.3s; }
    .page-content.blurred { filter: blur(3px); pointer-events: none; }

    .interactive-card {
      padding: 20px; margin: 10px 0; background: #f8f9fa; border: 2px solid #dee2e6;
      border-radius: 8px; cursor: pointer; transition: all 0.2s;
    }
    .interactive-card:hover { border-color: #3498db; background: #ebf5fb; }
    .interactive-card[inert], [inert] .interactive-card {
      opacity: 0.4; cursor: not-allowed; filter: grayscale(50%);
    }
    .interactive-card[inert]:hover, [inert] .interactive-card:hover {
      border-color: #dee2e6; background: #f8f9fa;
    }

    button {
      padding: 10px 20px; border: none; border-radius: 6px; cursor: pointer;
      font-size: 14px; transition: all 0.2s;
    }
    .btn-primary { background: #3498db; color: white; }
    .btn-primary:hover { background: #2980b9; }
    .btn-success { background: #27ae60; color: white; }
    .btn-success:hover { background: #219a52; }
    .btn-danger { background: #e74c3c; color: white; }
    .btn-danger:hover { background: #c0392b; }

    /* 微数据演示 */
    .microdata-product {
      display: inline-block; width: 200px; margin: 10px; padding: 15px;
      background: white; border: 1px solid #e0e0e0; border-radius: 10px;
      text-align: center; transition: transform 0.2s, box-shadow 0.2s;
    }
    .microdata-product:hover { transform: translateY(-5px); box-shadow: 0 8px 24px rgba(0,0,0,0.12); }
    .microdata-product img { width: 100%; height: 140px; object-fit: cover; border-radius: 8px; background: #f0f0f0; }
    .microdata-product .name { font-weight: bold; margin: 10px 0 5px; font-size: 15px; }
    .microdata-product .price { color: #e74c3c; font-size: 18px; font-weight: bold; }
    .microdata-product .rating { color: #f39c12; font-size: 14px; }

    .event-card {
      padding: 20px; background: #fff8e1; border: 1px solid #ffecb3; border-radius: 10px;
      margin: 10px 0;
    }

    code { background: #f4f4f4; padding: 2px 6px; border-radius: 3px; font-size: 13px; color: #e74c3c; }
    pre { background: #1e1e1e; color: #d4d4d4; padding: 15px; border-radius: 6px; overflow-x: auto; font-size: 12px; line-height: 1.6; }
    table { width: 100%; border-collapse: collapse; font-size: 13px; margin: 15px 0; }
    th, td { padding: 10px; text-align: left; border: 1px solid #dee2e6; }
    th { background: #34495e; color: white; }

    .badge { display: inline-block; padding: 3px 8px; border-radius: 12px; font-size: 11px; font-weight: bold; }
    .badge-inert { background: #fee; color: #c00; }
    .badge-active { background: #efe; color: #060; }
  </style>
</head>
<body>
  <h1>🚫 inert 属性 & 微数据结构化</h1>

  <!-- ========== 1. inert 属性演示 ========== -->
  <div class="section">
    <h2>1. inert 属性 — 禁用子树的所有交互</h2>
    <p><code>inert</code> 是布尔属性,使元素及其所有后代不可交互、不可聚焦、被屏幕阅读器忽略。</p>

    <div style="margin:15px 0;">
      <button class="btn-primary" onclick="openInertModal()">打开模态框(背景 inert)</button>
      <button class="btn-success" onclick="toggleSectionInert()">切换下方区域的 inert 状态</button>
    </div>

    <div id="inert-target-section">
      <p style="margin-bottom:10px;font-size:13px;color:#666;">
        ▼ 这个区域可以通过上方按钮切换 inert 状态:
        <span id="inert-badge" class="badge badge-active">活跃</span>
      </p>

      <div class="interactive-card" onclick="alert('你点击了卡片 A!')">
        <strong>📇 卡片 A</strong> — 点击弹出提示<br>
        <small>当父容器 inert 时,这个点击不会触发</small>
      </div>
      <div class="interactive-card">
        <strong>✏️ 卡片 B</strong> — 包含可聚焦元素<br>
        <input type="text" placeholder="可输入文字" style="margin-top:8px;padding:6px 10px;border:1px solid #ccc;border-radius:4px;">
      </div>
      <div class="interactive-card">
        <strong>🔗 卡片 C</strong> — 包含链接<br>
        <a href="#" style="color:#3498db;margin-top:8px;display:inline-block;">这是一个链接</a>
      </div>
    </div>

    <pre id="inert-log" style="margin-top:15px;background:#f8f9fa;color:#333;padding:12px;border-radius:6px;font-size:12px;">// inert 状态日志</pre>
  </div>

  <!-- inert 模态框 -->
  <div class="modal-overlay" id="inert-modal">
    <div class="modal-content" role="dialog" aria-modal="true">
      <h2>🔒 模态框开启中</h2>
      <p style="color:#666;margin:15px 0;line-height:1.6;">
        此时背景页面已被设置为 <code>inert</code>。<br>
        尝试以下操作:<br>
        ❌ 无法点击背景任何元素<br>
        ❌ Tab 键无法聚焦到背景<br>
        ❌ 屏幕阅读器忽略背景内容<br>
        ✅ 只能与此模态框交互
      </p>
      <button class="btn-danger" onclick="closeInertModal()">关闭模态框</button>
    </div>
  </div>

  <!-- ========== 2. 微数据 itemscope/itemprop/itemtype ========== -->
  <div class="section">
    <h2>2. 微数据 (Microdata) — 结构化数据</h2>
    <p>微数据为搜索引擎提供机器可读的结构化信息,用于<strong>富摘要(Rich Snippets)</strong>显示。</p>

    <h3 style="margin:20px 0 15px;">🛒 产品微数据 (schema.org/Product)</h3>
    <div itemscope itemtype="https://schema.org/Product" class="microdata-product">
      <img itemprop="image" src="" alt="智能手机" style="background:linear-gradient(135deg,#667eea,#764ba2);display:flex;align-items:center;justify-content:center;color:white;font-size:40px;">
      <div class="name" itemprop="name">旗舰智能手机 Pro Max</div>
      <div itemprop="description" style="font-size:12px;color:#666;margin:5px 0;">6.7英寸 OLED 屏幕,512GB 存储</div>
      <div class="price" itemprop="price" content="6999.00">¥6,999</div>
      <div itemprop="priceCurrency" content="CNY" style="display:none;"></div>
      <div class="rating" itemprop="aggregateRating" itemscope itemtype="https://schema.org/AggregateRating">
        ⭐ <span itemprop="ratingValue">4.8</span>
        (<span itemprop="reviewCount">2,580</span> 条评价)
      </div>
      <link itemprop="availability" href="https://schema.org/InStock">
      <span style="font-size:11px;color:#27ae60;">● 有货</span>
    </div>

    <div itemscope itemtype="https://schema.org/Product" class="microdata-product">
      <img itemprop="image" src="" alt="无线耳机" style="background:linear-gradient(135deg,#f093fb,#f5576c);display:flex;align-items:center;justify-content:center;color:white;font-size:40px;">
      <div class="name" itemprop="name">降噪无线耳机</div>
      <div itemprop="description" style="font-size:12px;color:#666;margin:5px 0;">主动降噪,40小时续航</div>
      <div class="price" itemprop="price" content="1299.00">¥1,299</div>
      <div itemprop="priceCurrency" content="CNY" style="display:none;"></div>
      <div class="rating" itemprop="aggregateRating" itemscope itemtype="https://schema.org/AggregateRating">
        ⭐ <span itemprop="ratingValue">4.6</span>
        (<span itemprop="reviewCount">1,890</span> 条评价)
      </div>
      <link itemprop="availability" href="https://schema.org/InStock">
      <span style="font-size:11px;color:#27ae60;">● 有货</span>
    </div>

    <h3 style="margin:20px 0 15px;">📅 活动微数据 (schema.org/Event)</h3>
    <div itemscope itemtype="https://schema.org/Event" class="event-card">
      <h4 itemprop="name" style="margin-bottom:8px;">🎵 2024 前端技术大会</h4>
      <p><time itemprop="startDate" datetime="2024-06-15T09:00:00+08:00">2024年6月15日 09:00</time>
         ~ <time itemprop="endDate" datetime="2024-06-15T18:00:00+08:00">18:00</time></p>
      <p itemprop="location" itemscope itemtype="https://schema.org/Place">
        📍 <span itemprop="name">北京国际会议中心</span>,
        <span itemprop="address" itemscope itemtype="https://schema.org/PostalAddress">
          <span itemprop="addressLocality">北京</span>
        </span>
      </p>
      <p style="margin-top:8px;">
        <span itemprop="offers" itemscope itemtype="https://schema.org/Offer">
          💰 价格: <span itemprop="price" content="299">¥299</span>
          <span itemprop="priceCurrency" content="CNY"></span>
          <link itemprop="availability" href="https://schema.org/InStock">
        </span>
        |
        🔢 剩余: <span itemprop="remainingAttendeeCapacity">150</span> 位
      </p>
    </div>

    <h3 style="margin:20px 0 15px;">👤 人物微数据 (schema.org/Person)</h3>
    <div itemscope itemtype="https://schema.org/Person" style="display:flex;gap:20px;align-items:center;padding:20px;background:#f0f4ff;border-radius:10px;">
      <div itemprop="image" style="width:80px;height:80px;border-radius:50%;background:linear-gradient(135deg,#667eea,#764ba2);display:flex;align-items:center;justify-content:center;color:white;font-size:30px;">👤</div>
      <div>
        <h4 itemprop="name" style="margin-bottom:5px;">张三 (前端工程师)</h4>
        <p itemprop="jobTitle" style="color:#666;">高级前端开发工程师 @ 科技公司</p>
        <p style="font-size:13px;color:#888;">
          📧 <span itemprop="email">zhangsan@example.com</span> |
          🌐 <span itemprop="url">https://zhangsan.dev</span>
        </p>
        <p itemprop="knowsAbout" style="font-size:12px;color:#666;margin-top:5px;">
          技能: JavaScript, React, Vue, Node.js
        </p>
      </div>
    </div>
  </div>

  <!-- ========== 3. 微数据检测 ========== -->
  <div class="section">
    <h2>3. 微数据检测与提取</h2>
    <button class="btn-primary" onclick="extractMicrodata()">🔍 提取页面微数据</button>
    <button class="btn-success" onclick="showStructuredData()">📋 查看 JSON-LD 格式</button>
    <pre id="microdata-output" style="max-height:350px;margin-top:15px;">// 微数据提取结果将显示在这里...</pre>
  </div>

  <script>
    // ===== inert 模态框 =====
    function openInertModal() {
      document.getElementById('inert-modal').classList.add('show');
      // 将主内容设为 inert
      document.querySelectorAll('.section').forEach(s => s.inert = true);
      document.querySelector('h1').inert = true;
      logInert('模态框打开:背景内容已设置为 inert');
    }

    function closeInertModal() {
      document.getElementById('inert-modal').classList.remove('show');
      // 移除 inert
      document.querySelectorAll('.section').forEach(s => s.inert = false);
      document.querySelector('h1').inert = false;
      logInert('模态框关闭:背景内容已移除 inert');
    }

    function toggleSectionInert() {
      const section = document.getElementById('inert-target-section');
      const wasInert = section.inert;
      section.inert = !wasInert;
      const badge = document.getElementById('inert-badge');
      if (section.inert) {
        badge.textContent = 'INERT';
        badge.className = 'badge badge-inert';
      } else {
        badge.textContent = '活跃';
        badge.className = 'badge badge-active';
      }
      logInert(`#inert-target-section inert → ${section.inert} (之前: ${wasInert})`);
    }

    function logInert(msg) {
      const log = document.getElementById('inert-log');
      const time = new Date().toLocaleTimeString();
      log.textContent = `[${time}] ${msg}\n\n// inert 属性效果:\n` +
                        `- 禁止所有鼠标点击事件\n` +
                        `- Tab 键无法聚焦到 inert 子树内的元素\n` +
                        `- 屏幕阅读器忽略 inert 区域\n` +
                        `- 搜索引擎可能降低 inert 内容权重`;
    }

    // ===== 微数据提取 =====
    function extractMicrodata() {
      const items = document.getItems ? document.getItems() : [];
      const output = document.getElementById('microdata-output');

      // 手动提取 itemscope 元素
      const scopes = document.querySelectorAll('[itemscope]');
      let result = `// 页面中共找到 ${scopes.length} 个微数据项 (itemscope)\n\n`;

      scopes.forEach((scope, index) => {
        const itemType = scope.getAttribute('itemtype') || '(未指定类型)';
        result += `【项目 ${index + 1}】itemtype: ${itemType}\n`;

        // 提取所有 itemprop
        const props = scope.querySelectorAll('[itemprop]');
        props.forEach(prop => {
          const propName = prop.getAttribute('itemprop');
          let propValue = '';
          if (prop.tagName === 'META') {
            propValue = prop.getAttribute('content');
          } else if (prop.tagName === 'TIME') {
            propValue = prop.getAttribute('datetime') || prop.textContent.trim();
          } else if (prop.tagName === 'LINK') {
            propValue = prop.getAttribute('href');
          } else if (prop.hasAttribute('content')) {
            propValue = prop.getAttribute('content');
          } else {
            propValue = prop.textContent.trim().substring(0, 50);
          }
          if (propValue) {
            result += `  └─ ${propName}: ${propValue}\n`;
          }
        });
        result += '\n';
      });

      output.textContent = result;
    }

    function showStructuredData() {
      const output = document.getElementById('microdata-output');
      output.textContent = `// 微数据对应的 JSON-LD 格式示例(供搜索引擎解析):

{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": "旗舰智能手机 Pro Max",
  "description": "6.7英寸 OLED 屏幕,512GB 存储",
  "image": "手机图片URL",
  "offers": {
    "@type": "Offer",
    "price": "6999.00",
    "priceCurrency": "CNY",
    "availability": "https://schema.org/InStock"
  },
  "aggregateRating": {
    "@type": "AggregateRating",
    "ratingValue": "4.8",
    "reviewCount": "2580"
  }
}

// 使用 Google 结构化数据测试工具验证:
// https://search.google.com/test/rich-results`;
    }

    // ESC 关闭模态框
    document.addEventListener('keydown', (e) => {
      if (e.key === 'Escape') closeInertModal();
    });
  </script>
</body>
</html>

inert - 交互禁用

inert 属性用于使元素及其子元素变为"非交互"状态。

使用示例

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>inert 属性示例</title>
  <style>
    :inert {
      opacity: 0.5;
      pointer-events: none;
    }
    
    .modal {
      position: fixed;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      background: white;
      padding: 20px;
      border: 1px solid #ccc;
      z-index: 1000;
      box-shadow: 0 2px 10px rgba(0,0,0,0.2);
    }
    
    .overlay {
      position: fixed;
      top: 0;
      left: 0;
      right: 0;
      bottom: 0;
      background: rgba(0,0,0,0.5);
      z-index: 999;
    }
  </style>
</head>
<body>
  <h1>inert 属性示例</h1>
  
  <main id="main-content">
    <p>这是主要内容区域。</p>
    <button>可点击的按钮</button>
    <input type="text" placeholder="可输入的文本框">
  </main>

  <button id="open-modal">打开模态框</button>
  
  <div id="overlay" class="overlay" style="display:none;"></div>
  <div id="modal" class="modal" style="display:none;">
    <h2>模态对话框</h2>
    <p>这是模态框内容。背景内容已被禁用(inert)。</p>
    <button id="close-modal">关闭</button>
  </div>

  <script>
    const openBtn = document.getElementById('open-modal');
    const closeBtn = document.getElementById('close-modal');
    const modal = document.getElementById('modal');
    const overlay = document.getElementById('overlay');
    const mainContent = document.getElementById('main-content');

    openBtn.addEventListener('click', () => {
      overlay.style.display = 'block';
      modal.style.display = 'block';
      
      // 使背景内容变为非交互状态
      mainContent.inert = true;
    });

    closeBtn.addEventListener('click', () => {
      overlay.style.display = 'none';
      modal.style.display = 'none';
      
      // 恢复背景内容的交互状态
      mainContent.inert = false;
    });
  </script>
</body>
</html>

popover - 弹出层

popover 属性用于创建原生的弹出层元素,无需 JavaScript 即可实现显示/隐藏功能。

使用示例

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>popover 属性示例</title>
  <style>
    [popover] {
      padding: 20px;
      border: 1px solid #ccc;
      background: white;
      box-shadow: 0 2px 10px rgba(0,0,0,0.1);
      max-width: 300px;
    }
    
    button {
      padding: 10px 20px;
      margin: 10px;
    }
  </style>
</head>
<body>
  <h1>popover 属性示例</h1>

  <!-- 基本用法 -->
  <button popovertarget="info-popover">显示信息</button>
  <div id="info-popover" popover>
    <h3>提示信息</h3>
    <p>这是一个使用 popover 属性创建的弹出层。</p>
  </div>

  <!-- 手动控制 -->
  <button popovertarget="tooltip-popover" popovertargetaction="toggle">
    切换提示
  </button>
  <div id="tooltip-popover" popover="manual">
    <p>这是一个工具提示,点击按钮切换显示/隐藏。</p>
  </div>

  <script>
    // JavaScript 控制
    const popover = document.getElementById('info-popover');
    
    // 显示弹出层
    // popover.showPopover();
    
    // 隐藏弹出层
    // popover.hidePopover();
    
    // 切换显示状态
    // popover.togglePopover();
    
    // 监听事件
    popover.addEventListener('toggle', (e) => {
      console.log('弹出层状态:', e.newState); // 'open' 或 'closed'
    });
  </script>
</body>
</html>

nonce - 安全随机数

nonce 是配合内容安全策略(CSP)使用的全局属性,用于安全地允许特定内联脚本或样式执行。

作用机制

当服务器返回 CSP 响应头时:

http
Content-Security-Policy: script-src 'self' 'nonce-rAnd0m123'

只有带有匹配 nonce 值的内联脚本才会被执行:

html
<script nonce="rAnd0m123">
  console.log("这段脚本通过 CSP 校验");
</script>

安全注意事项

  • nonce 值应由服务器端为每个响应随机生成
  • 不要在前端代码中硬编码固定的 nonce 字符串
  • 需要配合 HTTPS 使用

<a id="微数据属性"></a>

微数据属性

HTML 微数据(Microdata)是一组全局属性,用于为搜索引擎等消费方提供结构化数据。

核心属性

属性说明
itemscope声明一个"项目"(item)的作用域
itemtype指定项目的类型,一般为 schema.org 等命名空间的 URL
itemprop声明该项目的一个属性
itemid为项目指定全局唯一标识符
itemref引用文档中其他位置的属性节点

使用示例

html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>微数据示例</title>
</head>
<body>
  <article itemscope itemtype="https://schema.org/Article">
    <h1 itemprop="headline">HTML 全局属性详解</h1>
    <p>
      作者:
      <span itemprop="author" itemscope itemtype="https://schema.org/Person">
        <span itemprop="name">张三</span>
      </span>
    </p>
    <time itemprop="datePublished" datetime="2025-01-01">2025-01-01</time>
  </article>
</body>
</html>

使用建议

  • 结构化数据推荐使用 JSON-LD
  • 如果已有大量微数据标注,可以继续沿用
  • 尽量在关键内容上使用少量语义清晰的属性

<a id="属性对比"></a>

属性对比

id vs class

特性idclass
唯一性文档中唯一可重复使用
数量限制每个元素只能有一个可以有多个
CSS 选择器#id.class
JS 获取getElementById()getElementsByClassName()
优先级更高较低
用途唯一标识、锚点样式分组、批量操作

hidden vs display:none

特性hiddendisplay: none
语义表示元素当前不可见纯粹的样式控制
可访问性屏幕阅读器忽略取决于实现
JS 操作element.hiddenelement.style.display
性能更高效需要样式计算

tabindex 值对比

Tab 导航编程聚焦推荐度
省略自然顺序✅ 推荐
0自然顺序✅ 推荐
-1不参与✅ 推荐
正整数自定义顺序❌ 不推荐

<a id="javascript-api-参考"></a>

JavaScript API 参考

通用 API

javascript
// 获取属性值
element.getAttribute('data-user-id');

// 设置属性值
element.setAttribute('data-user-id', '12345');

// 移除属性
element.removeAttribute('data-user-id');

// 检查属性是否存在
element.hasAttribute('data-user-id');

// 获取所有属性
element.attributes;

特定属性 API

javascript
// id
element.id = 'newId';

// class
element.className = 'class1 class2';
element.classList.add('class3');
element.classList.remove('class1');
element.classList.toggle('active');
element.classList.contains('active');

// style
element.style.color = 'red';
element.style.cssText = 'color: red; background: blue;';
window.getComputedStyle(element);

// hidden
element.hidden = true;

// contenteditable
element.contentEditable = 'true';
element.isContentEditable;

// draggable
element.draggable = true;

// tabindex
element.tabIndex = 0;

// data-*
element.dataset.userId = '12345';
delete element.dataset.userId;

// lang
element.lang = 'zh-CN';

// dir
element.dir = 'rtl';

// spellcheck
element.spellcheck = true;

// translate
element.translate = false;

// inert
element.inert = true;

<a id="最佳实践"></a>

最佳实践

1. 属性选择建议

场景推荐属性原因
唯一标识元素idCSS/JS 选择、锚点跳转
样式分组class可复用、易维护
动态样式style(谨慎使用)仅用于动态计算的值
存储数据data-*标准化、易于访问
隐藏元素hidden语义清晰、性能好

2. 可访问性清单

  • 为交互元素提供 titlearia-label
  • 使用 lang 属性指定内容语言
  • 使用 tabindex 管理焦点顺序(优先使用 0-1
  • 使用 hidden 而不是 style="display:none" 隐藏内容
  • 为自定义控件添加键盘支持

3. 性能优化

  • 避免大量使用内联 style
  • 使用 data-* 存储数据,而不是自定义属性
  • 合理使用 contenteditable,避免在大型文档上使用
  • 使用 hidden 属性而不是 CSS 隐藏元素

4. 国际化

  • 始终在 <html> 标签上设置 lang 属性
  • 为不同语言的内容区域设置相应的 lang
  • 使用 dir 属性支持 RTL 语言
  • 使用 translate 属性控制翻译行为

5. 移动端优化

  • 使用 inputmode 优化虚拟键盘
  • 使用 enterkeyhint 提升用户体验
  • 使用 autocapitalize 控制大小写
  • 谨慎使用 autofocus

6. 代码组织

html
<!-- ✅ 推荐 -->
<div
  id="main-container"
  class="container primary"
  lang="zh-CN"
  data-theme="dark"
  data-user-id="12345">
  <h1 title="主标题">欢迎</h1>
</div>

<!-- ❌ 避免 -->
<div
  id="main-container"
  class="container primary"
  style="color: red; font-size: 16px;"
  data-style-color="red"
  data-style-font-size="16px">
  <!-- 样式应该放在 CSS 中,而不是 data-* 属性中 -->
</div>

<a id="常见问题"></a>

常见问题

1. id 重复会怎样?

问题:文档中有两个相同的 id 值。

影响

  • getElementById() 只返回第一个元素
  • 锚点链接可能跳转到错误的元素
  • CSS 选择器可能应用到错误的元素

解决方案

javascript
// 检查 ID 是否重复
function checkDuplicateIds() {
  const ids = document.querySelectorAll('[id]');
  const idMap = {};
  
  ids.forEach(element => {
    const id = element.id;
    if (idMap[id]) {
      console.warn(`重复的 ID: ${id}`, element);
    } else {
      idMap[id] = element;
    }
  });
}

checkDuplicateIds();

2. class 名冲突怎么办?

问题:不同组件使用了相同的 class 名。

解决方案

html
<!-- 使用命名空间 -->
<div class="component-name__element--modifier"></div>

<!-- 使用 BEM 方法 -->
<div class="card">
  <div class="card__header"></div>
  <div class="card__body"></div>
</div>

<!-- 使用 CSS Modules(构建工具支持) -->
<div class="${styles.container}"></div>

3. tabindex 使用误区

问题:使用正整数 tabindex。

影响

  • 破坏自然的 DOM 导航顺序
  • 导致可访问性问题
  • 维护困难

解决方案

html
<!-- ❌ 避免 -->
<div tabindex="1">第一个</div>
<div tabindex="3">第三个</div>
<div tabindex="2">第二个</div>

<!-- ✅ 推荐:调整 DOM 结构 -->
<div>第一个</div>
<div>第二个</div>
<div>第三个</div>

<!-- ✅ 推荐:使用 tabindex="0" -->
<div tabindex="0">自定义控件</div>

4. data-* 命名转换问题

问题:JavaScript 中访问 data-* 属性时名称转换。

解决方案

html
<div id="example" data-user-id="123" data-userName="张三"></div>

<script>
const element = document.getElementById('example');

// ✅ 正确:连字符转为驼峰
console.log(element.dataset.userId); // '123'

// ❌ 错误:直接使用原名称
// console.log(element.dataset.user-id); // 错误

// ⚠️ 注意:HTML 中的大小写会被转为小写
// HTML: data-userName
// JS: dataset.username(不是 userName)
console.log(element.dataset.username); // '张三'
</script>

5. hidden vs display:none 选择

问题:何时使用 hidden,何时使用 display: none

决策指南

code
是否需要语义表示"当前不可见"?
├─ 是 → 使用 hidden 属性
└─ 否 → 使用 display: none
    ↓
是否需要动画过渡?
├─ 是 → 使用 CSS(配合 visibility 或 opacity)
└─ 否 → 使用 display: none

6. tabindex=-1 不生效

问题:设置了 tabindex="-1" 但仍能通过 Tab 聚焦。

原因

  • 元素本身是可聚焦元素(如 <input><button>
  • 浏览器默认行为

解决方案

html
<!-- ❌ 不会阻止 Tab 聚焦 -->
<input type="text" tabindex="-1">

<!-- ✅ 需要同时设置 disabled -->
<input type="text" tabindex="-1" disabled>

<!-- ✅ 或使用 hidden -->
<input type="text" hidden>

<a id="浏览器兼容性"></a>

浏览器兼容性

属性ChromeFirefoxSafariEdge备注
id, class, style完全支持
data-*完全支持
hidden完全支持
contenteditable完全支持
draggable完全支持
tabindex完全支持
translate⚠️部分支持
inert⚠️较新特性
popover⚠️较新特性
inputmode移动端支持好
enterkeyhint⚠️移动端支持好
slot, partWeb Components

查询工具


参考资料

官方文档

相关规范


更新日志

版本日期更新内容
2.02026-02-12重构文档结构,添加分类导航、对比表格、JavaScript API、最佳实践、常见问题
1.02023-05-23初始版本

补充示例

<h4>042-data-custom-attributes.html</h4>
html
<!DOCTYPE html>
<!--
  来源:基础知识/7-全局属性.md
  演示:data-* 自定义数据属性(dataset API 读写、CSS attr() 引用)
-->
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>data-* 自定义数据属性演示</title>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; padding: 30px; background: #f5f5f5; }
    h1 { color: #333; margin-bottom: 20px; }
    .section { background: white; padding: 20px; margin-bottom: 20px; border-radius: 8px; box-shadow: 0 2px 8px rgba(0,0,0,0.1); }
    h2 { color: #555; margin-bottom: 15px; font-size: 18px; }

    /* 使用 CSS attr() 函数引用 data 属性 */
    .card {
      padding: 15px;
      margin: 10px 0;
      border-left: 4px solid;
      position: relative;
    }
    .card::after {
      content: attr(data-tooltip);
      display: none;
      position: absolute;
      bottom: 100%;
      left: 50%;
      transform: translateX(-50%);
      background: #333;
      color: white;
      padding: 5px 10px;
      border-radius: 4px;
      font-size: 12px;
      white-space: nowrap;
      margin-bottom: 5px;
    }
    .card:hover::after { display: block; }
    .card[data-priority="high"] { border-color: #e74c3c; background: #fef5f5; }
    .card[data-priority="medium"] { border-color: #f39c12; background: #fef9f0; }
    .card[data-priority="low"] { border-color: #27ae60; background: #f0fdf4; }

    .product {
      display: inline-block;
      width: 120px;
      text-align: center;
      padding: 10px;
      margin: 5px;
      background: #fff;
      border: 2px solid #ddd;
      border-radius: 8px;
      cursor: pointer;
      transition: all 0.3s;
    }
    .product:hover { transform: translateY(-3px); box-shadow: 0 4px 12px rgba(0,0,0,0.15); }
    /* 通过 data-price 显示价格 */
    .product .price-tag::before {
      content: '¥' attr(data-price);
      font-weight: bold;
      color: #e74c3c;
      font-size: 18px;
    }

    code { background: #f4f4f4; padding: 2px 6px; border-radius: 3px; font-size: 13px; }
    pre { background: #2d2d2d; color: #f8f8f2; padding: 15px; border-radius: 6px; overflow-x: auto; font-size: 13px; line-height: 1.5; }
    .output { background: #e8f5e9; padding: 10px; border-radius: 4px; margin-top: 10px; font-family: monospace; }
    button { padding: 8px 16px; margin: 5px; border: none; border-radius: 4px; cursor: pointer; background: #3498db; color: white; }
    button:hover { background: #2980b9; }
  </style>
</head>
<body>
  <h1>📦 data-* 自定义数据属性演示</h1>

  <!-- 示例1:CSS attr() 引用 -->
  <div class="section">
    <h2>1. CSS attr() 函数引用 data 属性</h2>
    <p>鼠标悬停查看 tooltip(通过 <code>data-tooltip</code> 属性):</p>
    <div class="card" data-priority="high" data-tooltip="紧急任务!需要立即处理">
      <strong>高优先级任务</strong> - 系统升级维护
    </div>
    <div class="card" data-priority="medium" data-tooltip="本周内完成即可">
      <strong>中优先级任务</strong> - 文档更新
    </div>
    <div class="card" data-priority="low" data-tooltip="有空再做">
      <strong>低优先级任务</strong> - 整理代码注释
    </div>
  </div>

  <!-- 示例2:商品价格展示 -->
  <div class="section">
    <h2>2. 商品卡片(data-price + CSS attr)</h2>
    <div class="product" data-id="001" data-category="electronics" data-price="2999">
      <div>智能手机</div>
      <div class="price-tag"></div>
    </div>
    <div class="product" data-id="002" data-category="electronics" data-price="4999">
      <div>笔记本电脑</div>
      <div class="price-tag"></div>
    </div>
    <div class="product" data-id="003" data-category="accessories" data-price="199">
      <div>无线耳机</div>
      <div class="price-tag"></div>
    </div>
  </div>

  <!-- 示例3:JavaScript dataset API -->
  <div class="section">
    <h2>3. JavaScript dataset API 读写操作</h2>
    <div id="user-card"
         data-user-id="10086"
         data-user-name="张三"
         data-user-email="zhangsan@example.com"
         data-user-role="admin"
         data-login-count="42"
         data-preferences='{"theme":"dark","lang":"zh-CN"}'
         style="padding:15px; background:#f0f4ff; border-radius:8px;">
      <strong>用户信息卡</strong>
    </div>
    <br>
    <button onclick="readDataset()">读取所有 data 属性</button>
    <button onclick="modifyDataset()">修改数据</button>
    <button onclick="addNewData()">添加新属性</button>
    <button onclick="deleteData()">删除属性</button>
    <pre id="dataset-output">// 点击按钮查看结果</pre>
  </div>

  <!-- 示例4:动态样式控制 -->
  <div class="section">
    <h2>4. 通过 data 属性动态控制样式</h2>
    <div id="theme-demo" data-theme="light" data-font-size="16" style="padding:20px; border-radius:8px; transition:all 0.3s;">
      <p>这是一段可自定义样式的文本。</p>
      <p>通过修改 data 属性来改变外观。</p>
    </div>
    <br>
    <button onclick="changeTheme('light')">浅色主题</button>
    <button onclick="changeTheme('dark')">深色主题</button>
    <button onclick="changeTheme('blue')">蓝色主题</button>
    <button onclick="changeFontSize(14)">小字体</button>
    <button onclick="changeFontSize(18)">大字体</button>
  </div>

  <script>
    // 读取 dataset
    function readDataset() {
      const el = document.getElementById('user-card');
      const output = document.getElementById('dataset-output');
      output.textContent = `
// dataset 对象(自动转换命名规则)
const data = el.dataset;

// 读取结果:
userId     = "${el.dataset.userId}"        // data-user-id → userId
userName   = "${el.dataset.userName}"      // data-user-name → userName
userEmail  = "${el.dataset.userEmail}"     // data-user-email → userEmail
userRole   = "${el.dataset.userRole}"      // data-user-role → role
loginCount = "${el.dataset.loginCount}"    // data-login-count → loginCount (数字字符串)
preferences= ${el.dataset.preferences}     // JSON 字符串

// 原始 attributes 方式访问:
el.getAttribute('data-user-id') = "${el.getAttribute('data-user-id')}"
`;
    }

    // 修改 dataset
    function modifyDataset() {
      const el = document.getElementById('user-card');
      el.dataset.userName = '李四';
      el.dataset.userEmail = 'lisi@example.com';
      el.dataset.loginCount = '100';
      el.dataset.userRole = 'super-admin';

      const output = document.getElementById('dataset-output');
      output.textContent = `// 修改后的值:
el.dataset.userName   = "${el.dataset.userName}"
el.dataset.userEmail  = "${el.dataset.userEmail}"
el.dataset.loginCount = "${el.dataset.loginCount}"
el.dataset.userRole   = "${el.dataset.userRole}"

// 注意:DOM 属性会同步更新:
el.getAttribute('data-user-name') = "${el.getAttribute('data-user-name')}"
el.getAttribute('data-user-role') = "${el.getAttribute('data-user-role')}"`
    }

    // 添加新属性
    function addNewData() {
      const el = document.getElementById('user-card');
      el.dataset.lastLogin = '2024-01-15T08:30:00';
      el.dataset.isActive = 'true';
      el.dataset.score = '98.5';

      const output = document.getElementById('dataset-output');
      output.textContent = `// 添加的新属性:
el.dataset.lastLogin = "${el.dataset.lastLogin}"
el.dataset.isActive  = "${el.dataset.isActive}"
el.dataset.score     = "${el.dataset.score}"

// HTML 中已添加:
// data-last-login="2024-01-15T08:30:00"
// data-is-active="true"
// data-score="98.5"`
    }

    // 删除属性
    function deleteData() {
      const el = document.getElementById('user-card');
      delete el.dataset.userRole;
      delete el.dataset.preferences;

      const output = document.getElementById('dataset-output');
      output.textContent = `// 删除后:
el.dataset.userRole    = ${el.dataset.userRole}  // undefined
el.dataset.preferences = ${el.dataset.preferences}  // undefined

el.hasAttribute('data-user-role') = ${el.hasAttribute('data-user-role')}
el.hasAttribute('data-preferences') = ${el.hasAttribute('data-preferences')}`;
    }

    // 动态主题切换
    function changeTheme(theme) {
      const el = document.getElementById('theme-demo');
      el.dataset.theme = theme;
      const themes = {
        light: { bg: '#ffffff', color: '#333', border: '#ddd' },
        dark:  { bg: '#1a1a2e', color: '#eee', border: '#16213e' },
        blue:  { bg: '#e3f2fd', color: '#1565c0', border: '#90caf9' }
      };
      const t = themes[theme];
      el.style.background = t.bg;
      el.style.color = t.color;
      el.style.border = `2px solid ${t.border}`;
    }

    function changeFontSize(size) {
      const el = document.getElementById('theme-demo');
      el.dataset.fontSize = size;
      el.style.fontSize = size + 'px';
    }
  </script>
</body>
</html>
<h4>044-title-tabindex-accesskey.html</h4>
html
<!DOCTYPE html>
<!--
  来源:基础知识/7-全局属性.md
  演示:title 属性悬停提示(tooltip 效果对比原生 title)、tabindex 键盘导航
-->
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>title 与 tabindex 属性演示</title>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body { font-family: -apple-system, sans-serif; padding: 30px; background: #f5f7fa; }
    h1 { color: #2c3e50; margin-bottom: 25px; }
    .section { background: white; padding: 25px; margin-bottom: 25px; border-radius: 10px; box-shadow: 0 2px 12px rgba(0,0,0,0.08); }
    h2 { color: #34495e; margin-bottom: 15px; font-size: 17px; padding-bottom: 8px; border-bottom: 2px solid #ecf0f1; }

    /* Tooltip 样式 */
    .tooltip-wrap { position: relative; display: inline-block; margin: 10px; }
    .tooltip-wrap .custom-tooltip {
      visibility: hidden;
      position: absolute;
      bottom: 125%;
      left: 50%;
      transform: translateX(-50%);
      background: #2c3e50;
      color: white;
      padding: 8px 14px;
      border-radius: 6px;
      font-size: 13px;
      white-space: nowrap;
      opacity: 0;
      transition: opacity 0.3s, visibility 0.3s;
      z-index: 100;
      pointer-events: none;
    }
    .tooltip-wrap .custom-tooltip::after {
      content: '';
      position: absolute;
      top: 100%;
      left: 50%;
      margin-left: -6px;
      border-width: 6px;
      border-style: solid;
      border-color: #2c3e50 transparent transparent transparent;
    }
    .tooltip-wrap:hover .custom-tooltip,
    .tooltip-wrap:focus .custom-tooltip {
      visibility: visible;
      opacity: 1;
    }

    .native-title { padding: 12px 20px; background: #ecf0f1; border-radius: 6px; cursor: help; }
    .custom-title { padding: 12px 20px; background: #3498db; color: white; border-radius: 6px; cursor: pointer; border: none; }

    /* tabindex 演示 */
    .tab-item {
      display: inline-block;
      padding: 15px 25px;
      margin: 8px;
      background: white;
      border: 2px solid #bdc3c7;
      border-radius: 8px;
      cursor: pointer;
      transition: all 0.2s;
      font-size: 14px;
    }
    .tab-item:focus {
      outline: 3px solid #3498db;
      outline-offset: 2px;
      border-color: #3498db;
      background: #ebf5fb;
      transform: scale(1.05);
    }
    .tab-item.tab-focusable { border-color: #27ae60; }
    .tab-item.tab-negative { border-color: #e74c3c; opacity: 0.7; }
    .tab-item.tab-positive { border-color: #f39c12; }

    code { background: #f4f4f4; padding: 2px 6px; border-radius: 3px; font-size: 13px; }
    pre { background: #282c34; color: #abb2bf; padding: 15px; border-radius: 6px; overflow-x: auto; font-size: 13px; line-height: 1.5; }
    table { width: 100%; border-collapse: collapse; margin: 15px 0; font-size: 14px; }
    th, td { padding: 10px 12px; text-align: left; border: 1px solid #ddd; }
    th { background: #34495e; color: white; }
    tr:nth-child(even) { background: #f9f9f9; }

    .focus-indicator {
      position: fixed;
      top: 10px; right: 10px;
      background: #2c3e50;
      color: white;
      padding: 10px 15px;
      border-radius: 6px;
      font-size: 13px;
      z-index: 999;
    }
  </style>
</head>
<body>
  <h1>💡 title 提示 & tabindex 键盘导航</h1>

  <div class="focus-indicator" id="focus-indicator">当前焦点:无</div>

  <!-- ========== title 属性演示 ========== -->
  <div class="section">
    <h2>1. title 属性 — 原生 vs 自定义 Tooltip 对比</h2>

    <p><strong>原生 title 属性(鼠标悬停查看,样式由浏览器决定):</strong></p>
    <div style="display:flex; gap:15px; flex-wrap:wrap; margin:15px 0;">
      <span class="native-title" title="这是浏览器原生的提示框,样式不可自定义,延迟约1-2秒才显示">
        🖱️ 悬停我(原生 title)
      </span>
      <span class="native-title" title="支持多行文本&#10;第二行内容&#10;第三行内容">
        📝 多行提示(原生)
      </span>
      <input type="text" placeholder="输入框的 title" title="请输入您的用户名,长度3-20个字符" style="padding:10px;border-radius:6px;border:1px solid #ccc;">
    </div>

    <p><strong>自定义 Tooltip(基于 title 数据 + CSS/JS 实现):</strong></p>
    <div style="display:flex; gap:15px; flex-wrap:wrap; margin:15px 0;">
      <div class="tooltip-wrap">
        <button class="custom-title" data-tooltip="✨ 自定义样式!立即显示、支持富文本、完全可控">
          🎨 悬停我(自定义)
        </button>
        <span class="custom-tooltip">✨ 自定义样式!立即显示、支持富文本、完全可控</span>
      </div>
      <div class="tooltip-wrap">
        <button class="custom-title" data-tooltip="⚠️ 警告:此操作不可撤销!" style="background:#e74c3c;">
          ⚠️ 危险操作
        </button>
        <span class="custom-tooltip" style="background:#c0392b;">⚠️ 警告:此操作不可撤销!</span>
      </div>
      <div class="tooltip-wrap">
        <span style="padding:12px 20px;background:#9b59b6;color:white;border-radius:6px;cursor:pointer;display:inline-block;"
              data-tooltip="ℹ️ 信息提示:支持 HTML 内容">
          ℹ️ 信息图标
        </span>
        <span class="custom-tooltip" style="background:#8e44ad;">ℹ️ 信息提示:支持 HTML 内容</span>
      </div>
    </div>

    <table>
      <tr><th>特性</th><th>原生 title</th><th>自定义 Tooltip</th></tr>
      <tr><td>显示延迟</td><td>~1-2 秒</td><td>即时(CSS transition)</td></tr>
      <tr><td>样式控制</td><td>❌ 浏览器决定</td><td>✅ 完全自定义</td></tr>
      <tr><td>富文本支持</td><td>⚠️ 仅纯文本</td><td>✅ 支持 HTML</td></tr>
      <tr><td>无障碍</td><td>✅ 屏幕阅读器识别</td><td>需额外添加 aria-label</td></tr>
      <tr><td>移动端</td><td>❌ 不触发(无 hover)</td><td>✅ 可适配 click/tap</td></tr>
      <tr><td>实现复杂度</td><td>零</td><td>需 CSS + JS</td></tr>
    </table>
  </div>

  <!-- ========== tabindex 演示 ========== -->
  <div class="section">
    <h2>2. tabindex 属性 — 键盘导航顺序控制</h2>

    <p><strong>按 Tab 键测试焦点顺序(注意观察焦点框和右上角指示器):</strong></p>

    <div style="margin:20px 0;">
      <p style="margin-bottom:10px;color:#666;">▼ 正序 tabindex(1, 2, 3...)— 不推荐,会打乱自然顺序:</p>
      <div class="tab-item tab-positive" tabindex="3">第三位 (tabindex=3)</div>
      <div class="tab-item tab-positive" tabindex="1">第一位 (tabindex=1)</div>
      <div class="tab-item tab-positive" tabindex="2">第二位 (tabindex=2)</div>
    </div>

    <div style="margin:20px 0;">
      <p style="margin-bottom:10px;color:#666;">▼ tabindex="0" — 加入正常 Tab 流(推荐):</p>
      <div class="tab-item tab-focusable" tabindex="0">普通可聚焦元素 A</div>
      <div class="tab-item tab-focusable" tabindex="0">普通可聚焦元素 B</div>
      <div class="tab-item tab-focusable" tabindex="0">普通可聚焦元素 C</div>
    </div>

    <div style="margin:20px 0;">
      <p style="margin-bottom:10px;color:#666;">▼ tabindex="-1" — 只能编程聚焦,不参与 Tab 导航:</p>
      <div class="tab-item tab-negative" tabindex="-1" id="special-focus">特殊元素(Tab 跳过)</div>
      <button class="tab-item" style="background:#3498db;color:white;border:none;" onclick="focusSpecial()">
        👆 点击让我聚焦到上面的特殊元素
      </button>
    </div>

    <table>
      <tr><th>tabindex 值</th><th>行为</th><th>使用场景</th><th>推荐度</th></tr>
      <tr><td><code>正数 (1,2,3...)</code></td><td>按数值从小到大聚焦</td><td> legacy 代码兼容</td><td>❌ 不推荐</td></tr>
      <tr><td><code>0</code></td><td>加入文档顺序的 Tab 流</td><td>使 div/span 可聚焦</td><td>✅ 推荐</td></tr>
      <tr><td><code>-1</code></td><td>不参与 Tab,仅编程聚焦</td><td>模态框、跳转目标</td><td>✅ 特定场景</td></tr>
      <tr><td><code>(不设置)</code></td><td>默认行为</td><td>交互元素天然可聚焦</td><td>✅ 默认</td></tr>
    </table>
  </div>

  <!-- ========== accesskey 演示 ========== -->
  <div class="section">
    <h2>3. accesskey 快捷键属性</h2>
    <p><strong>快捷键说明:</strong>不同操作系统组合键不同(Windows: Alt+key, Mac: Ctrl+Option+key)</p>
    <div style="margin:15px 0;">
      <button accesskey="s" class="tab-item tab-focusable" style="border-color:#3498db;">💾 保存 (Alt+S)</button>
      <button accesskey="n" class="tab-item tab-focusable" style="border-color:#27ae60;">➕ 新建 (Alt+N)</button>
      <button accesskey="h" class="tab-item tab-focusable" style="border-color:#e74c3c;">❓ 帮助 (Alt+H)</button>
      <a href="#" accesskey="t" class="tab-item tab-focusable" style="border-color:#9b59b6;text-decoration:none;">⬆️ 返回顶部 (Alt+T)</a>
    </div>
    <pre style="font-size:12px;">// 浏览器通常会在菜单或工具栏显示可用快捷键列表
// 实际使用率较低,但有助于无障碍访问</pre>
  </div>

  <script>
    // 焦点跟踪
    document.addEventListener('focusin', (e) => {
      const indicator = document.getElementById('focus-indicator');
      const tag = e.target.tagName.toLowerCase();
      const id = e.target.id ? `#${e.target.id}` : '';
      const cls = e.target.className ? `.${e.target.className.split(' ')[0]}` : '';
      const tabIndex = e.target.getAttribute('tabindex');
      indicator.textContent = `当前焦点:<${tag}${id}${cls}> tabindex=${tabIndex ?? '默认'}`;

      // 高亮当前元素
      e.target.style.transition = 'box-shadow 0.2s';
    });

    // 编程聚焦到 tabindex="-1" 元素
    function focusSpecial() {
      const el = document.getElementById('special-focus');
      el.focus();
      el.scrollIntoView({ behavior: 'smooth', block: 'center' });
    }
  </script>
</body>
</html>
<h4>045-aria-accessibility.html</h4>
html
<!DOCTYPE html>
<!--
  来源:基础知识/7-全局属性.md
  演示:role ARIA 角色属性、aria-label / aria-labelledby / aria-describedby 无障碍属性
-->
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>ARIA 无障碍属性演示</title>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body { font-family: -apple-system, sans-serif; padding: 30px; background: #fafafa; }
    h1 { color: #1a1a1a; margin-bottom: 25px; }
    .section { background: white; padding: 25px; margin-bottom: 25px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.06); }
    h2 { color: #333; margin-bottom: 15px; font-size: 17px; border-bottom: 2px solid #eee; padding-bottom: 8px; }

    /* ARIA 角色演示样式 */
    [role="navigation"] { background: #2c3e50; color: white; padding: 15px 20px; border-radius: 8px; }
    [role="navigation"] a { color: #3498db; margin-right: 20px; text-decoration: none; }
    [role="navigation"] a:hover { text-decoration: underline; }

    [role="main"] { min-height: 200px; background: #f8f9fa; padding: 20px; border-radius: 8px; border: 2px dashed #dee2e6; }

    [role="banner"] { background: linear-gradient(135deg, #667eea, #764ba2); color: white; padding: 30px; border-radius: 10px; text-align: center; }

    [role="complementary"] { background: #fff3cd; padding: 20px; border-radius: 8px; border-left: 4px solid #ffc107; }

    [role="contentinfo"] { background: #343a40; color: #adb5bd; padding: 20px; border-radius: 8px; text-align: center; }

    [role="search"] { display: flex; gap: 10px; max-width: 500px; }
    [role="search"] input { flex: 1; padding: 10px 15px; border: 2px solid #dee2e6; border-radius: 6px; font-size: 14px; }
    [role="search"] button { padding: 10px 20px; background: #007bff; color: white; border: none; border-radius: 6px; cursor: pointer; }

    [role="button"] { display: inline-flex; align-items: center; gap: 8px; padding: 10px 20px; background: #28a745; color: white; border: none; border-radius: 6px; cursor: pointer; font-size: 14px; }
    [role="button"]:hover { background: #218838; }
    [role="button"]:focus { outline: 3px solid #28a745; outline-offset: 2px; }

    [role="alert"] { background: #f8d7da; color: #721c24; padding: 15px; border-radius: 6px; border-left: 4px solid #dc3545; margin: 10px 0; }
    [role="status"] { background: #d4edda; color: #155724; padding: 15px; border-radius: 6px; border-left: 4px solid #28a745; margin: 10px 0; }
    [role="progressbar"] { width: 100%; height: 20px; background: #e9ecef; border-radius: 10px; overflow: hidden; }
    [role="progressbar"] .bar { height: 100%; background: linear-gradient(90deg, #28a745, #20c997); width: 0%; transition: width 1s; }

    [role="dialog"] { display: none; position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); background: white; padding: 30px; border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.3); z-index: 1000; min-width: 400px; }
    [role="dialog"].show { display: block; }
    [role="dialog"] [role="document"] { outline: none; }

    code { background: #f4f4f4; padding: 2px 6px; border-radius: 3px; font-size: 13px; color: #d63384; }
    pre { background: #1e1e1e; color: #d4d4d4; padding: 15px; border-radius: 6px; overflow-x: auto; font-size: 12px; line-height: 1.6; }

    .sr-only {
      position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
      overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
    }

    table { width: 100%; border-collapse: collapse; font-size: 13px; margin: 15px 0; }
    th, td { padding: 10px; text-align: left; border: 1px solid #dee2e6; }
    th { background: #f8f9fa; font-weight: 600; }

    button { padding: 8px 16px; margin: 5px; border: none; border-radius: 6px; cursor: pointer; background: #6c757d; color: white; font-size: 13px; }
    button:hover { opacity: 0.85; }
  </style>
</head>
<body>
  <h1>♿ ARIA 无障碍属性完整演示</h1>

  <!-- ========== 1. 语义化角色 landmarks ========== -->
  <div class="section">
    <h2>1. ARIA Landmark 角色(页面结构语义化)</h2>

    <div role="banner">
      <h1>🏛️ role="banner" — 页面横幅/头部</h1>
      <p>屏幕阅读器用户可快速导航到此区域</p>
    </div>

    <div role="navigation" aria-label="主导航" style="margin:15px 0;">
      <a href="#">首页</a>
      <a href="#">产品</a>
      <a href="#">关于</a>
      <a href="#">联系</a>
      <span style="float:right;color:#95a5a6;">role="navigation" + aria-label="主导航"</span>
    </div>

    <div style="display:flex;gap:20px;margin:15px 0;">
      <div role="main" style="flex:1;">
        <h2>📄 role="main" — 主要内容区</h2>
        <p>页面的核心内容放在这里。每个页面只能有一个 main 角色。</p>
        <p>屏幕阅读器提供了直接跳转到主内容的快捷键(如 NVDA 的 M 键)。</p>
      </div>
      <div role="complementary" style="width:250px;">
        <h3>📎 role="complementary"</h3>
        <p>辅助内容区域,如侧边栏、广告位等。</p>
      </div>
    </div>

    <div role="contentinfo">
      <p>© 2024 role="contentinfo" — 页脚/版权信息</p>
    </div>
  </div>

  <!-- ========== 2. 交互组件角色 ========== -->
  <div class="section">
    <h2>2. 组件级 ARIA 角色</h2>

    <div style="display:flex; flex-wrap:wrap; gap:15px; align-items:flex-start;">
      <!-- 按钮 -->
      <div>
        <p style="margin-bottom:8px;font-size:13px;color:#666;">role="button"(让 div 表现得像按钮):</p>
        <div role="button" tabindex="0" aria-label="点击下载文件" onclick="alert('下载!')" onkeydown="if(event.key==='Enter'||event.key===' ')this.click()">
          📥 下载文件
        </div>
      </div>

      <!-- 搜索 -->
      <div>
        <p style="margin-bottom:8px;font-size:13px;color:#666;">role="search":</p>
        <div role="search" aria-label="站内搜索">
          <input type="text" aria-label="搜索关键词" placeholder="搜索...">
          <button>搜索</button>
        </div>
      </div>

      <!-- 提示 -->
      <div style="flex:1;min-width:300px;">
        <p style="margin-bottom:8px;font-size:13px;color:#666;">实时反馈角色:</p>
        <div role="alert" aria-live="assertive" id="alert-box">
          ⚠️ 这是一个 alert 提示(会被朗读)
        </div>
        <div role="status" aria-live="polite" id="status-box">
          ✅ 这是一个 status 状态(空闲时朗读)
        </div>
        <div style="margin-top:10px;">
          <label>进度:</label>
          <div role="progressbar" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100" aria-label="加载进度" id="progress-bar">
            <div class="bar"></div>
          </div>
        </div>
        <button onclick="simulateProgress()" style="margin-top:8px;background:#007bff;">模拟进度更新</button>
      </div>
    </div>
  </div>

  <!-- ========== 3. aria-label / labelledby / describedby ========== -->
  <div class="section">
    <h2>3. 无障碍标签属性对比</h2>

    <div style="display:flex;gap:20px;flex-wrap:wrap;">
      <!-- aria-label -->
      <div style="flex:1;min-width:280px;padding:15px;background:#e3f2fd;border-radius:8px;">
        <h3 style="color:#1565c0;margin-bottom:10px;">aria-label</h3>
        <p style="font-size:13px;margin-bottom:10px;">直接提供标签文本(不可见):</p>
        <button aria-label="关闭对话框" style="padding:15px 20px;font-size:20px;background:#ef5350;color:white;border:none;border-radius:6px;cursor:pointer;">×</button>
        <pre style="margin-top:10px;font-size:11px;">&lt;button aria-label="关闭对话框"&gt;
  ×
&lt;/button&gt;
// 屏幕阅读器读作:"关闭对话框 按钮"</pre>
      </div>

      <!-- aria-labelledby -->
      <div style="flex:1;min-width:280px;padding:15px;background:#fce4ec;border-radius:8px;">
        <h3 style="color:#c62828;margin-bottom:10px;">aria-labelledby</h3>
        <p style="font-size:13px;margin-bottom:10px;">引用其他元素的文本作为标签:</p>
        <h3 id="form-title" style="font-size:14px;">用户注册表单</h3>
        <form aria-labelledby="form-title" style="background:white;padding:15px;border-radius:6px;">
          <input type="text" placeholder="用户名" style="padding:8px;width:100%;margin-bottom:8px;border:1px solid #ddd;border-radius:4px;">
        </form>
        <pre style="margin-top:10px;font-size:11px;">&lt;h3 id="form-title"&gt;用户注册表单&lt;/h3&gt;
&lt;form aria-labelledby="form-title"&gt;
// 屏幕阅读器:"用户注册表单 表单"</pre>
      </div>

      <!-- aria-describedby -->
      <div style="flex:1;min-width:280px;padding:15px;background:#e8f5e9;border-radius:8px;">
        <h3 style="color:#2e7d32;margin-bottom:10px;">aria-describedby</h3>
        <p style="font-size:13px;margin-bottom:10px;">引用描述文本(补充说明):</p>
        <label for="pwd" style="font-size:13px;">密码:</label><br>
        <input type="password" id="pwd" aria-describedby="pwd-help" style="padding:8px;width:100%;margin-top:5px;border:1px solid #ddd;border-radius:4px;">
        <small id="pwd-help" style="color:#666;font-size:12px;">密码长度至少8位,需包含字母和数字</small>
        <pre style="margin-top:10px;font-size:11px;">&lt;input aria-describedby="pwd-help"&gt;
&lt;small id="pwd-help"&gt;描述文字&lt;/small&gt;
// 读作:"密码 编辑文本 密码长度至少..."</pre>
      </div>
    </div>

    <table style="margin-top:20px;">
      <tr><th>属性</th><th>作用</th><th>何时使用</th></tr>
      <tr><td><code>aria-label</code></td><td>直接设置标签文本</td><td>没有可见标签时;图标按钮</td></tr>
      <tr><td><code>aria-labelledby</code></td><td>引用可见元素的文本</td><td>已有标题/标签元素时</td></tr>
      <tr><td><code>aria-describedby</code></td><td>引用描述性文本</td><td>需要额外说明/帮助文本时</td></tr>
    </table>
  </div>

  <!-- ========== 4. 对话框与焦点陷阱 ========== -->
  <div class="section">
    <h2>4. role="dialog" 模态对话框</h2>
    <button onclick="openDialog()" style="background:#6f42c1;">打开对话框</button>

    <div role="dialog" aria-modal="true" aria-labelledby="dialog-title" id="my-dialog">
      <div role="document">
        <h2 id="dialog-title">确认删除</h2>
        <p id="dialog-desc">此操作无法撤销,确定要继续吗?</p>
        <div style="margin-top:20px;display:flex;justify-content:flex-end;gap:10px;">
          <button onclick="closeDialog()" style="background:#6c757d;">取消</button>
          <button onclick="closeDialog()" style="background:#dc3545;">确认删除</button>
        </div>
      </div>
    </div>

    <div id="dialog-overlay" style="display:none;position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.5);z-index:999;"></div>
  </div>

  <!-- ========== 5. 屏幕阅读器检测面板 ========== -->
  <div class="section">
    <h2>5. ARIA 属性检测器</h2>
    <p>点击下方按钮扫描页面中的 ARIA 属性:</p>
    <button onclick="scanAriaAttributes()" style="background:#17a2b8;">🔍 扫描 ARIA 属性</button>
    <pre id="aria-scan-result" style="max-height:300px;margin-top:10px;">// 扫描结果将显示在这里...</pre>
  </div>

  <script>
    // 对话框控制
    function openDialog() {
      document.getElementById('my-dialog').classList.add('show');
      document.getElementById('dialog-overlay').style.display = 'block';
      document.getElementById('my-dialog').querySelector('[role="document"]').focus();
    }

    function closeDialog() {
      document.getElementById('my-dialog').classList.remove('show');
      document.getElementById('dialog-overlay').style.display = 'none';
    }

    // 进度条模拟
    function simulateProgress() {
      const bar = document.querySelector('.bar');
      const progress = document.getElementById('progress-bar');
      let val = 0;
      bar.style.width = '0%';
      progress.setAttribute('aria-valuenow', 0);

      const timer = setInterval(() => {
        val += Math.random() * 15;
        if (val >= 100) { val = 100; clearInterval(timer); }
        bar.style.width = val + '%';
        progress.setAttribute('aria-valuenow', Math.round(val));
        document.getElementById('status-box').textContent = `✅ 加载进度:${Math.round(val)}%`;
      }, 300);

      document.getElementById('alert-box').textContent = '⚠️ 开始加载...';
      setTimeout(() => {
        document.getElementById('alert-box').textContent = '✅ 加载完成!';
      }, 2500);
    }

    // 扫描 ARIA 属性
    function scanAriaAttributes() {
      const allElements = document.querySelectorAll('*');
      const results = [];

      allElements.forEach(el => {
        const attrs = [];
        // 检查 role
        if (el.getAttribute('role')) attrs.push(`role="${el.getAttribute('role')}"`);
        // 检查 aria-* 属性
        Array.from(el.attributes).forEach(attr => {
          if (attr.name.startsWith('aria-')) {
            attrs.push(`${attr.name}="${attr.value}"`);
          }
        });
        if (attrs.length > 0) {
          const tag = el.tagName.toLowerCase();
          const id = el.id ? `#${el.id}` : '';
          const cls = el.className && typeof el.className === 'string' ? `.${el.className.split(' ')[0]}` : '';
          results.push(`<${tag}${id}${cls} ${attrs.join(' ')}>`);
        }
      });

      const output = document.getElementById('aria-scan-result');
      output.innerHTML = `// 共发现 ${results.length} 个带 ARIA 属性的元素\n\n` +
                         results.map((r, i) => `[${i + 1}] ${r}`).join('\n') +
                         '\n\n// 这些属性对屏幕阅读器用户至关重要!';
    }

    // ESC 关闭对话框
    document.addEventListener('keydown', (e) => {
      if (e.key === 'Escape') closeDialog();
    });
  </script>
</body>
</html>
<h4>046-hidden-editable-draggable.html</h4>
html
<!DOCTYPE html>
<!--
  来源:基础知识/7-全局属性.md
  演示:hidden / contenteditable / draggable / spellcheck / translate / dir / lang 等实用全局属性
-->
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>实用全局属性综合演示</title>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body { font-family: -apple-system, sans-serif; padding: 30px; background: #f8f9fa; }
    h1 { color: #2c3e50; margin-bottom: 25px; }
    .section { background: white; padding: 25px; margin-bottom: 25px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.06); }
    h2 { color: #34495e; margin-bottom: 15px; font-size: 17px; border-bottom: 2px solid #ecf0f1; padding-bottom: 8px; }

    /* hidden 演示 */
    [hidden] { display: none !important; }
    .hidden-demo { padding: 15px; margin: 8px 0; border-radius: 6px; border: 2px solid transparent; transition: all 0.3s; }
    .hidden-demo.visible { border-color: #27ae60; background: #eafaf1; }
    .hidden-demo.hidden-state { border-color: #e74c3c; background: #fdedec; opacity: 0.5; }

    /* contenteditable 演示 */
    .editor-area {
      min-height: 150px; padding: 15px; border: 2px solid #bdc3c7; border-radius: 8px;
      background: #fff; line-height: 1.6; font-size: 14px;
    }
    .editor-area:focus { outline: none; border-color: #3498db; box-shadow: 0 0 0 3px rgba(52,152,219,0.2); }
    .toolbar { display: flex; gap: 8px; margin-bottom: 10px; flex-wrap: wrap; }
    .toolbar button { padding: 6px 12px; border: 1px solid #ddd; background: white; border-radius: 4px; cursor: pointer; font-size: 13px; }
    .toolbar button:hover { background: #f0f0f0; }

    /* draggable 演示 */
    .drag-item {
      padding: 15px 20px; margin: 8px; background: linear-gradient(135deg, #667eea, #764ba2);
      color: white; border-radius: 8px; cursor: grab; user-select: none;
      display: inline-block; transition: transform 0.2s, box-shadow 0.2s;
    }
    .drag-item:active { cursor: grabbing; transform: scale(1.05); box-shadow: 0 8px 20px rgba(0,0,0,0.3); }
    .drop-zone {
      min-height: 120px; padding: 20px; border: 3px dashed #bdc3c7; border-radius: 10px;
      display: flex; flex-wrap: wrap; gap: 10px; align-content: flex-start; transition: all 0.3s;
    }
    .drop-zone.drag-over { border-color: #3498db; background: #ebf5fb; }

    /* spellcheck 演示 */
    .spellcheck-on { padding: 15px; border: 2px solid #27ae60; border-radius: 8px; min-height: 80px; }
    .spellcheck-off { padding: 15px; border: 2px solid #e74c3c; border-radius: 8px; min-height: 80px; }

    /* translate / dir / lang */
    .rtl-demo { direction: rtl; text-align: right; background: #fff3cd; padding: 15px; border-radius: 8px; margin: 10px 0; }
    .ltr-demo { direction: ltr; text-align: left; background: #d1ecf1; padding: 15px; border-radius: 8px; margin: 10px 0; }

    code { background: #f4f4f4; padding: 2px 6px; border-radius: 3px; font-size: 13px; color: #e74c3c; }
    pre { background: #282c34; color: #abb2bf; padding: 15px; border-radius: 6px; overflow-x: auto; font-size: 12px; line-height: 1.5; }
    button { padding: 8px 16px; margin: 5px; border: none; border-radius: 6px; cursor: pointer; background: #3498db; color: white; font-size: 13px; }
    button:hover { background: #2980b9; }

    table { width: 100%; border-collapse: collapse; font-size: 13px; margin: 10px 0; }
    th, td { padding: 8px 12px; text-align: left; border: 1px solid #dee2e6; }
    th { background: #f8f9fa; }
  </style>
</head>
<body>
  <h1>🔧 实用全局属性综合演示</h1>

  <!-- ========== 1. hidden 属性 ========== -->
  <div class="section">
    <h2>1. hidden 属性 — 条件显示/隐藏</h2>
    <p><code>hidden</code> 是布尔属性,存在即隐藏。比 <code>display:none</code> 更语义化。</p>

    <div id="hidden-el-1" class="hidden-demo visible">
      <strong>元素 A</strong> — 当前可见(点击按钮切换)
    </div>
    <div id="hidden-el-2" class="hidden-demo visible">
      <strong>元素 B</strong> — 当前可见(点击按钮切换)
    </div>
    <div id="hidden-el-3" class="hidden-demo hidden-state" hidden>
      <strong>元素 C</strong> — 初始隐藏(HTML 中自带 hidden 属性)
    </div>

    <div style="margin-top:15px;">
      <button onclick="toggleHidden('hidden-el-1')">切换 A</button>
      <button onclick="toggleHidden('hidden-el-2')">切换 B</button>
      <button onclick="toggleHidden('hidden-el-3')">切换 C</button>
      <button onclick="showAll()">全部显示</button>
      <button onclick="hideAll()">全部隐藏</button>
    </div>

    <pre id="hidden-log" class="log" style="background:#f8f9fa;padding:10px;border-radius:4px;margin-top:10px;font-family:monospace;font-size:12px;">// 操作日志</pre>
  </div>

  <!-- ========== 2. contenteditable 富文本编辑 ========== -->
  <div class="section">
    <h2>2. contenteditable — 富文本编辑器</h2>
    <p>使任意元素变为可编辑区域,无需 textarea/input。</p>

    <div class="toolbar">
      <button onclick="execCmd('bold')"><b>B</b> 加粗</button>
      <button onclick="execCmd('italic')"><i>I</i> 斜体</button>
      <button onclick="execCmd('underline')"><u>U</u> 下划线</button>
      <button onclick="execCmd('strikeThrough')"><s>S</s> 删除线</button>
      <span style="width:1px;background:#ddd;"></span>
      <button onclick="execCmd('insertUnorderedList')">• 列表</button>
      <button onclick="execCmd('insertOrderedList')">1. 有序列表</button>
      <span style="width:1px;background:#ddd;"></span>
      <button onclick="execCmd('justifyLeft')">左对齐</button>
      <button onclick="execCmd('justifyCenter')">居中</button>
      <button onclick="execCmd('justifyRight')">右对齐</button>
      <span style="width:1px;background:#ddd;"></span>
      <button onclick="execCmd('undo') ↩ 撤销</button>
      <button onclick="execCmd('redo') ↪ 重做</button>
      <span style="width:1px;background:#ddd;"></span>
      <button onclick="getHTML()" style="background:#27ae60;">获取 HTML</button>
      <button onclick="getText()" style="background:#e67e22;">获取纯文本</button>
    </div>

    <div contenteditable="true" id="rich-editor" class="editor-area" spellcheck="true">
      <p>这是一个 <b>可编辑</b> 的区域,试试:</p>
      <ul>
        <li>选中文字后点击工具栏按钮</li>
        <li>直接输入和删除</li>
        <li>粘贴富文本内容</li>
      </ul>
    </div>

    <pre id="editor-output" style="margin-top:10px;max-height:150px;">// 输出将显示在此处...</pre>
  </div>

  <!-- ========== 3. draggable 拖拽 ========== -->
  <div class="section">
    <h2>3. draggable 属性 — 拖拽功能开关</h2>

    <div style="margin-bottom:15px;">
      <strong>拖拽源(draggable="true"):</strong>
      <div draggable="true" id="drag-1" class="drag-item" data-value="苹果">🍎 苹果</div>
      <div draggable="true" id="drag-2" class="drag-item" data-value="香蕉">🍌 香蕉</div>
      <div draggable="true" id="drag-3" class="drag-item" data-value="葡萄">🍇 葡萄</div>
      <div draggable="false" id="drag-4" class="drag-item" style="background:linear-gradient(135deg,#95a5a6,#7f8c8d);opacity:0.6;cursor:not-allowed;">🚫 不可拖拽 (draggable=false)</div>
    </div>

    <strong>放置目标:</strong>
    <div class="drop-zone" id="drop-zone">
      <span style="color:#999;width:100%;text-align:center;padding:20px;">将水果拖到这里</span>
    </div>

    <p id="drag-log" style="margin-top:10px;font-size:13px;color:#666;">拖拽事件日志将显示在这里...</p>
  </div>

  <!-- ========== 4. spellcheck 拼写检查 ========== -->
  <div class="section">
    <h2>4. spellcheck 属性 — 拼写检查控制</h2>
    <div style="display:flex;gap:20px;flex-wrap:wrap;">
      <div style="flex:1;min-width:280px;">
        <p style="margin-bottom:8px;"><code>spellcheck="true"</code> ✅ 启用拼写检查(会有红色波浪线):</p>
        <div contenteditable="true" spellcheck="true" class="spellcheck-on">
          Thiss is a testt with speling errrors. 请尝试输入错误单词。
        </div>
      </div>
      <div style="flex:1;min-width:280px;">
        <p style="margin-bottom:8px;"><code>spellcheck="false"</code> ❌ 禁用拼写检查:</p>
        <div contenteditable="true" spellcheck="false" class="spellcheck-off">
          Thiss is a testt with speling errrors. 即使有错误也不会标红。
        </div>
      </div>
    </div>
    <p style="margin-top:10px;font-size:13px;color:#666;">💡 提示:在支持的浏览器中,启用拼写检查的文字下方会出现波浪虚线。</p>
  </div>

  <!-- ========== 5. translate / dir / lang 国际化 ========== -->
  <div class="section">
    <h2>5. translate / dir / lang — 国际化属性</h2>

    <h3 style="margin:15px 0 10px;">translate 属性(控制翻译)</h3>
    <div style="display:flex;gap:15px;flex-wrap:wrap;">
      <div style="flex:1;padding:15px;background:#e8f5e9;border-radius:8px;">
        <p><code>translate="yes"</code> (默认,允许翻译):</p>
        <p translate="yes">Hello World — 这段文字可以被翻译工具翻译</p>
      </div>
      <div style="flex:1;padding:15px;background:#ffebee;border-radius:8px;">
        <p><code>translate="no"</code> (禁止翻译):</p>
        <p translate="no">HTML5 — 品牌名称、代码、专有名词应禁止自动翻译</p>
      </div>
    </div>

    <h3 style="margin:15px 0 10px;">dir 文本方向</h3>
    <div class="ltr-demo">
      <strong>dir="ltr" (从左到右):</strong> Hello World! 你好世界!这是正常的从左到右排列。
    </div>
    <div class="rtl-demo">
      <strong>dir="rtl" (从右到左):</strong> مرحبا بالعالم! 这是阿拉伯语/希伯来语等 RTL 语言的书写方向。
    </div>

    <h3 style="margin:15px 0 10px;">lang 语言属性</h3>
    <table>
      <tr><th>元素</th><th>lang 值</th><th>作用</th></tr>
      <tr><td><code>&lt;html lang="zh-CN"&gt;</code></td><td>中文-中国大陆</td><td>告诉浏览器/搜索引擎页面语言</td></tr>
      <tr><td><code>&lt;p lang="en"&gt;Hello&lt;/p&gt;</code></td><td>英语段落</td><td>混合语言场景下的语言标注</td></tr>
      <tr><td><code>&lt;abbr lang="fr" title="République Française"&gt;RF&lt;/abbr&gt;</code></td><td>法语缩写</td><td>正确发音和翻译</td></tr>
    </table>
  </div>

  <script>
    // ===== hidden 操作 =====
    function toggleHidden(id) {
      const el = document.getElementById(id);
      const isHidden = el.hidden;
      el.hidden = !isHidden;
      el.classList.toggle('visible', isHidden);
      el.classList.toggle('hidden-state', !isHidden);
      logHidden(`"${id}" hidden → ${!isHidden}`);
    }

    function showAll() {
      ['hidden-el-1','hidden-el-2','hidden-el-3'].forEach(id => {
        const el = document.getElementById(id);
        el.hidden = false;
        el.classList.add('visible');
        el.classList.remove('hidden-state');
      });
      logHidden('全部设为可见');
    }

    function hideAll() {
      ['hidden-el-1','hidden-el-2','hidden-el-3'].forEach(id => {
        const el = document.getElementById(id);
        el.hidden = true;
        el.classList.remove('visible');
        el.classList.add('hidden-state');
      });
      logHidden('全部设为隐藏');
    }

    function logHidden(msg) {
      const log = document.getElementById('hidden-log');
      const time = new Date().toLocaleTimeString();
      log.textContent = `[${time}] ${msg}\nel.hidden = ${document.getElementById('hidden-el-1').hidden}, ${
        document.getElementById('hidden-el-2').hidden}, ${document.getElementById('hidden-el-3').hidden}`;
    }

    // ===== contenteditable 操作 =====
    function execCmd(cmd) {
      document.execCommand(cmd, false, null);
      document.getElementById('rich-editor').focus();
    }

    function getHTML() {
      const html = document.getElementById('rich-editor').innerHTML;
      document.getElementById('editor-output').textContent = `// innerHTML:\n${html}`;
    }

    function getText() {
      const text = document.getElementById('rich-editor').innerText;
      document.getElementById('editor-output').textContent = `// innerText (纯文本):\n${text}`;
    }

    // ===== draggable 操作 =====
    const dragItems = document.querySelectorAll('.drag-item[draggable="true"]');
    const dropZone = document.getElementById('drop-zone');
    const dragLog = document.getElementById('drag-log');

    dragItems.forEach(item => {
      item.addEventListener('dragstart', (e) => {
        e.dataTransfer.setData('text/plain', item.dataset.value);
        e.dataTransfer.effectAllowed = 'move';
        item.style.opacity = '0.5';
        dragLog.textContent = `🖱️ 开始拖拽: ${item.dataset.value}`;
      });

      item.addEventListener('dragend', () => {
        item.style.opacity = '1';
        dropZone.classList.remove('drag-over');
      });
    });

    dropZone.addEventListener('dragover', (e) => {
      e.preventDefault();
      e.dataTransfer.dropEffect = 'move';
      dropZone.classList.add('drag-over');
      dragLog.textContent = '📍 拖拽经过放置区域...';
    });

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

    dropZone.addEventListener('drop', (e) => {
      e.preventDefault();
      dropZone.classList.remove('drag-over');
      const value = e.dataTransfer.getData('text/plain');
      if (dropZone.querySelector('span')) {
        dropZone.innerHTML = '';
      }
      const badge = document.createElement('div');
      badge.className = 'drag-item';
      badge.style.cssText = 'background:linear-gradient(135deg,#27ae60,#2ecc71);padding:8px 16px;font-size:13px;';
      badge.textContent = value + ' ✓';
      dropZone.appendChild(badge);
      dragLog.textContent = `✅ 放置成功: ${value}`;
    });
  </script>
</body>
</html>
<h4>048-mobile-popover-attributes.html</h4>
html
<!DOCTYPE html>
<!--
  来源:基础知识/7-全局属性.md
  演示:移动端优化属性(inputmode / enterkeyhint / autocapitalize / autofocus)
       以及 popover 弹出层属性
-->
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>移动端优化 & popover 属性演示</title>
  <style>
    * { margin: 0; padding: 0; box-sizing: border-box; }
    body { font-family: -apple-system, sans-serif; padding: 30px; background: #f5f7fa; max-width: 900px; margin: 0 auto; }
    h1 { color: #1a1a1a; margin-bottom: 25px; }
    .section { background: white; padding: 25px; margin-bottom: 25px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0,0,0,0.06); }
    h2 { color: #333; margin-bottom: 15px; font-size: 17px; border-bottom: 2px solid #eee; padding-bottom: 8px; }

    .form-group { margin: 15px 0; }
    .form-group label { display: block; font-weight: 600; margin-bottom: 6px; font-size: 14px; color: #555; }
    .form-group input, .form-group textarea {
      width: 100%; padding: 12px 15px; border: 2px solid #e0e0e0; border-radius: 8px;
      font-size: 15px; transition: border-color 0.2s;
    }
    .form-group input:focus, .form-group textarea:focus {
      outline: none; border-color: #3498db; box-shadow: 0 0 0 3px rgba(52,152,219,0.15);
    }
    .hint { font-size: 12px; color: #888; margin-top: 5px; }
    .attr-tag { display: inline-block; background: #e8f4fd; color: #1976d2; padding: 2px 8px; border-radius: 4px; font-size: 12px; font-family: monospace; }

    /* popover 演示 */
    [popover] {
      padding: 20px; border: none; border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.2);
      background: white; max-width: 350px;
    }
    .pop-btn {
      padding: 10px 20px; border: 2px solid #3498db; background: white; color: #3498db;
      border-radius: 8px; cursor: pointer; font-size: 14px; font-weight: 600; transition: all 0.2s;
    }
    .pop-btn:hover { background: #3498db; color: white; }

    table { width: 100%; border-collapse: collapse; font-size: 13px; margin: 15px 0; }
    th, td { padding: 10px 12px; text-align: left; border: 1px solid #dee2e6; }
    th { background: #f8f9fa; font-weight: 600; }

    code { background: #f4f4f4; padding: 2px 6px; border-radius: 3px; font-size: 13px; color: #e74c3c; }
    pre { background: #263238; color: #eceff1; padding: 15px; border-radius: 6px; overflow-x: auto; font-size: 12px; line-height: 1.6; }

    .keyboard-preview {
      display: inline-flex; gap: 3px; padding: 8px 12px; background: #222; border-radius: 8px;
      vertical-align: middle; margin-left: 10px;
    }
    .key { width: 28px; height: 36px; background: #444; border-radius: 4px; display: flex; align-items: center; justify-content: center; color: #aaa; font-size: 11px; }
    .key.enter { width: 50px; background: #3498db; color: white; font-weight: bold; }
    .key.space { width: 80px; }
  </style>
</head>
<body>
  <h1>📱 移动端优化 & popover 弹出层属性</h1>

  <!-- ========== 1. inputmode 虚拟键盘 ========== -->
  <div class="section">
    <h2>1. inputmode — 控制虚拟键盘类型</h2>
    <p>在移动设备上,不同 <code>inputmode</code> 会调出不同的虚拟键盘布局:</p>

    <div class="form-group">
      <label>inputmode="text" <span class="attr-tag">默认</span></label>
      <input type="text" inputmode="text" placeholder="标准文本键盘">
      <div class="hint">标准全键盘,适合一般文本输入</div>
      <div class="keyboard-preview" title="键盘预览">
        <div class="key">Q</div><div class="key">W</div><div class="key">E</div><div class="key">R</div>
        <div class="key">T</div><div class="key">Y</div><div class="key enter">搜索</div>
      </div>
    </div>

    <div class="form-group">
      <label>inputmode="tel" <span class="attr-tag">电话号码</span></label>
      <input type="text" inputmode="tel" placeholder="请输入手机号">
      <div class="hint">纯数字键盘 + *# 符号,适合电话号码</div>
      <div class="keyboard-preview">
        <div class="key">1</div><div class="key">2</div><div class="key">3</div>
        <div class="key">4</div><div class="key">5</div><div class="key">6</div><div class="key">7</div><div class="key">8</div><div class="key">9</div><div class="key">0</div>
      </div>
    </div>

    <div class="form-group">
      <label>inputmode="email" <span class="attr-tag">邮箱</span></label>
      <input type="text" inputmode="email" placeholder="example@mail.com">
      <div class="hint">包含 @ 和 . 的邮箱专用键盘</div>
    </div>

    <div class="form-group">
      <label>inputmode="url" <span class="attr-tag">网址</span></label>
      <input type="text" inputmode="url" placeholder="https://...">
      <div class="hint">包含 / : . 等URL字符的键盘</div>
    </div>

    <div class="form-group">
      <label>inputmode="numeric" <span class="attr-tag">纯数字</span></label>
      <input type="text" inputmode="numeric" placeholder="0-9 数字">
      <div class="hint">纯数字键盘(不含符号),适合验证码、数量等</div>
    </div>

    <div class="form-group">
      <label>inputmode="decimal" <span class="attr-tag">小数</span></label>
      <input type="text" inputmode="decimal" placeholder="金额、坐标等">
      <div class="hint">数字 + 小数点,适合金额、测量值</div>
    </div>

    <div class="form-group">
      <label>inputmode="search" <span class="attr-tag">搜索</span></label>
      <input type="text" inputmode="search" placeholder="搜索关键词...">
      <div class="hint">回车键变成"搜索"按钮</div>
    </div>

    <table>
      <tr><th>inputmode 值</th><th>键盘特点</th><th>适用场景</th></tr>
      <tr><td><code>text</code></td><td>标准全键盘</td><td>通用文本输入</td></tr>
      <tr><td><code>tel</code></td><td>数字 + *#</td><td>电话号码</td></tr>
      <tr><td><code>email</code></td><td>含 @ .</td><td>邮箱地址</td></tr>
      <tr><td><code>url</code></td><td>含 / : .</td><td>网址输入</td></tr>
      <tr><td><code>numeric</code></td><td>纯数字</td><td>验证码、PIN</td></tr>
      <tr><td><code>decimal</code></td><td>数字 + 小数点</td><td>金额、坐标</td></tr>
      <tr><td><code>search</code></td><td>回车→搜索</td><td>搜索框</td></tr>
      <tr><td><code>none</code></td><td>不弹出键盘</td><td>只读日期选择器等</td></tr>
    </table>
  </div>

  <!-- ========== 2. enterkeyhint 回车键提示 ========== -->
  <div class="section">
    <h2>2. enterkeyhint — 回车键文案提示</h2>
    <p>控制移动端虚拟键盘回车键上显示的文字:</p>

    <div style="display:flex;gap:15px;flex-wrap:wrap;">
      <div class="form-group" style="flex:1;min-width:200px;">
        <label>enterkeyhint="enter" <span class="attr-tag">换行</span></label>
        <input type="text" enterkeyhint="enter" placeholder="多行输入场景">
      </div>
      <div class="form-group" style="flex:1;min-width:200px;">
        <label>enterkeyhint="done" <span class="attr-tag">完成</span></label>
        <input type="text" enterkeyhint="done" placeholder="表单最后一项">
      </div>
      <div class="form-group" style="flex:1;min-width:200px;">
        <label>enterkeyhint="go" <span class="attr-tag">前往</span></label>
        <input type="text" enterkeyhint="go" placeholder="URL 输入框">
      </div>
      <div class="form-group" style="flex:1;min-width:200px;">
        <label>enterkeyhint="next" <span class="attr-tag">下一项</span></label>
        <input type="text" enterkeyhint="next" placeholder="表单中间项">
      </div>
      <div class="form-group" style="flex:1;min-width:200px;">
        <label>enterkeyhint="search" <span class="attr-tag">搜索</span></label>
        <input type="text" enterkeyhint="search" placeholder="搜索框">
      </div>
      <div class="form-group" style="flex:1;min-width:200px;">
        <label>enterkeyhint="send" <span class="attr-tag">发送</span></label>
        <input type="text" enterkeyhint="send" placeholder="聊天输入框">
      </div>
    </div>

    <pre style="font-size:11px;">// enterkeyhint 可选值:
// "enter" → 换行符(多行文本)
// "done"  → 完成(表单末尾)
// "go"    → 前往(URL 导航)
// "next"  → 下一项(表单中间)
// "search" → 搜索
// "send"  → 发送(消息)
// "previous" → 上一项(罕见)</pre>
  </div>

  <!-- ========== 3. autocapitalize 自动大小写 ========== -->
  <div class="section">
    <h2>3. autocapitalize — 自动大小写控制</h2>

    <div style="display:flex;gap:15px;flex-wrap:wrap;">
      <div class="form-group" style="flex:1;min-width:220px;">
        <label>autocapitalize="sentences" <span class="attr-tag">句子</span></label>
        <input type="text" autocapitalize="sentences" placeholder="每句首字母大写">
        <div class="hint">"hello world" → "Hello world"</div>
      </div>
      <div class="form-group" style="flex:1;min-width:220px;">
        <label>autocapitalize="words" <span class="attr-tag">单词</span></label>
        <input type="text" autocapitalize="words" placeholder="每个单词首字母大写">
        <div class="hint">"john doe" → "John Doe"</div>
      </div>
      <div class="form-group" style="flex:1;min-width:220px;">
        <label>autocapitalize="characters" <span class="attr-tag">全部大写</span></label>
        <input type="text" autocapitalize="characters" placeholder="全部转为大写">
        <div class="hint">"hello" → "HELLO"</div>
      </div>
      <div class="form-group" style="flex:1;min-width:220px;">
        <label>autocapitalize="none" <span class="attr-tag">不自动</span></label>
        <input type="text" autocapitalize="none" placeholder="保持原始输入">
        <div class="hint">不做任何自动大小写转换</div>
      </div>
    </div>
  </div>

  <!-- ========== 4. autofocus 自动聚焦 ========== -->
  <div class="section">
    <h2>4. autofocus — 页面加载自动聚焦</h2>
    <p>页面加载时自动获得焦点的输入框(本页面的第一个输入框可能已被抢占焦点 😄):</p>
    <div class="form-group">
      <label>autofocus 演示 <span class="attr-tag">已激活</span></label>
      <input type="text" autofocus placeholder="这个输入框会自动获得焦点">
      <div class="hint">⚠️ 一个页面只能有一个 autofocus 元素生效(第一个声明的)</div>
    </div>
  </div>

  <!-- ========== 5. popover 原生弹出层 ========== -->
  <div class="section">
    <h2>5. popover — 原生弹出层属性</h2>
    <p>HTML 新增的原生弹出层 API,无需 JS 即可实现轻量弹窗:</p>

    <div style="display:flex;gap:15px;flex-wrap:wrap;margin:20px 0;">
      <!-- 默认 popover -->
      <div>
        <button popovertarget="pop-default" class="pop-btn">▶ 默认弹出层</button>
        <div id="pop-default" popover>
          <h3 style="margin-bottom:10px;">默认弹出层</h3>
          <p style="font-size:13px;color:#666;line-height:1.6;">点击外部区域或按 Esc 可关闭。<br>这是最基础的 popover 用法。</p>
        </div>
      </div>

      <!-- 手动 popover -->
      <div>
        <button popovertarget="pop-manual" class="pop-btn" style="border-color:#e74c3c;color:#e74c3c;">▶ 手动模式</button>
        <div id="pop-manual" popover="manual" style="border:2px solid #e74c3c;">
          <h3 style="margin-bottom:10px;color:#e74c3c;">手动模式 (manual)</h3>
          <p style="font-size:13px;color:#666;line-height:1.6;">手动模式不会在点击外部时自动关闭!<br><br>
          <button onclick="document.getElementById('pop-manual').hidePopover()" style="padding:6px 14px;background:#e74c3c;color:white;border:none;border-radius:4px;cursor:pointer;">手动关闭</button></p>
        </div>
      </div>

      <!-- 提示框式 popover -->
      <div>
        <button popovertarget="pop-hint" class="pop-btn" style="border-color:#f39c12;color:#f39c12;">💡 提示框</button>
        <div id="pop-hint" popover="auto" style="max-width:260px;background:#fff8e1;border:1px solid #ffecb3;">
          <strong style="color:#f57f17;">💡 小提示</strong>
          <p style="font-size:12px;color:#666;margin-top:8px;">popover 属性可以用来做 tooltip、下拉菜单、确认框等各种弹出层组件!</p>
        </div>
      </div>

      <!-- 表单 popover -->
      <div>
        <button popovertarget="pop-form" class="pop-btn" style="border-color:#27ae60;color:#27ae60;">📝 表单弹出</button>
        <div id="pop-form" popover>
          <h3 style="margin-bottom:12px;">快速登录</h3>
          <input type="text" placeholder="用户名" style="width:100%;padding:8px;border:1px solid #ddd;border-radius:4px;margin-bottom:8px;font-size:13px;">
          <input type="password" placeholder="密码" style="width:100%;padding:8px;border:1px solid #ddd;border-radius:4px;margin-bottom:10px;font-size:13px;">
          <button style="width:100%;padding:8px;background:#27ae60;color:white;border:none;border-radius:4px;cursor:pointer;font-size:13px;">登录</button>
        </div>
      </div>
    </div>

    <table>
      <tr><th>popover 值</th><th>关闭方式</th><th>适用场景</th></tr>
      <tr><td><code>auto</code>(默认)</td><td>点击外部、Esc、调用 hidePopover()</td><td>Tooltip、菜单、提示框</td></tr>
      <tr><td><code>manual</code></td><td>只能通过 JS 关闭</td><td>模态框、持续显示的通知</td></tr>
    </table>

    <h3 style="margin:20px 0 10px;">JavaScript API</h3>
    <pre>// Popover API
const popover = document.getElementById('pop-default');

popover.showPopover();    // 打开
popover.hidePopover();    // 关闭
popover.togglePopover();  // 切换

// 事件监听
popover.addEventListener('toggle', (e) => {
  console.log('状态:', e.newState); // "open" | "closed"
});

// 检测浏览器支持
if (typeof HTMLElement.prototype.showPopover === 'function') {
  console.log('✅ 支持 popover API');
}</pre>
  </div>

</body>
</html>