HTML5 新特性

HTML5 常用新特性及使用示例

问题

列举 HTML5 的主要新特性,包括语义化标签、Video/Audio、Canvas、LocalStorage、Drag API、Web Worker。

解答

1. 语义化标签

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>语义化标签示例</title>
</head>
<body>
  <!-- 页面头部 -->
  <header>
    <nav>
      <ul>
        <li><a href="/">首页</a></li>
        <li><a href="/about">关于</a></li>
      </ul>
    </nav>
  </header>

  <!-- 主要内容 -->
  <main>
    <article>
      <header>
        <h1>文章标题</h1>
        <time datetime="2024-01-01">2024年1月1日</time>
      </header>
      <section>
        <p>文章内容...</p>
      </section>
      <footer>
        <p>作者:张三</p>
      </footer>
    </article>

    <!-- 侧边栏 -->
    <aside>
      <h2>相关推荐</h2>
    </aside>
  </main>

  <!-- 页面底部 -->
  <footer>
    <p>&copy; 2024</p>
  </footer>
</body>
</html>

常用语义化标签:

标签用途
<header>页面或区块的头部
<nav>导航链接
<main>页面主要内容
<article>独立的文章内容
<section>文档中的节
<aside>侧边栏内容
<footer>页面或区块的底部
<figure>图片、图表等
<figcaption>figure 的标题
<time>日期时间
<mark>高亮文本

2. Video/Audio

<!-- 视频播放 -->
<video 
  id="myVideo" 
  width="640" 
  height="360" 
  controls 
  poster="cover.jpg"
>
  <source src="video.mp4" type="video/mp4">
  <source src="video.webm" type="video/webm">
  您的浏览器不支持视频播放
</video>

<!-- 音频播放 -->
<audio id="myAudio" controls>
  <source src="audio.mp3" type="audio/mpeg">
  <source src="audio.ogg" type="audio/ogg">
  您的浏览器不支持音频播放
</audio>

<script>
const video = document.getElementById('myVideo')

// 播放控制
video.play()
video.pause()

// 常用属性
console.log(video.currentTime) // 当前播放时间
console.log(video.duration)    // 总时长
console.log(video.paused)      // 是否暂停
console.log(video.volume)      // 音量 0-1

// 常用事件
video.addEventListener('play', () => console.log('开始播放'))
video.addEventListener('pause', () => console.log('暂停'))
video.addEventListener('ended', () => console.log('播放结束'))
video.addEventListener('timeupdate', () => {
  console.log('播放进度:', video.currentTime)
})
</script>

3. Canvas

<canvas id="myCanvas" width="400" height="300"></canvas>

<script>
const canvas = document.getElementById('myCanvas')
const ctx = canvas.getContext('2d')

// 绘制矩形
ctx.fillStyle = '#3498db'
ctx.fillRect(10, 10, 100, 80)

// 绘制边框矩形
ctx.strokeStyle = '#e74c3c'
ctx.lineWidth = 3
ctx.strokeRect(130, 10, 100, 80)

// 绘制圆形
ctx.beginPath()
ctx.arc(300, 50, 40, 0, Math.PI * 2)
ctx.fillStyle = '#2ecc71'
ctx.fill()

// 绘制线条
ctx.beginPath()
ctx.moveTo(10, 120)
ctx.lineTo(100, 180)
ctx.lineTo(200, 120)
ctx.strokeStyle = '#9b59b6'
ctx.lineWidth = 2
ctx.stroke()

// 绘制文字
ctx.font = '24px Arial'
ctx.fillStyle = '#333'
ctx.fillText('Hello Canvas', 10, 230)

// 绘制图片
const img = new Image()
img.onload = () => {
  ctx.drawImage(img, 200, 150, 100, 100)
}
img.src = 'image.png'

// 导出为图片
const dataURL = canvas.toDataURL('image/png')
</script>

4. LocalStorage / SessionStorage

// ========== LocalStorage ==========
// 数据持久存储,关闭浏览器后仍然保留

// 存储数据
localStorage.setItem('username', '张三')
localStorage.setItem('user', JSON.stringify({ id: 1, name: '张三' }))

// 读取数据
const username = localStorage.getItem('username')
const user = JSON.parse(localStorage.getItem('user'))

// 删除数据
localStorage.removeItem('username')

// 清空所有数据
localStorage.clear()

// 获取存储数量
console.log(localStorage.length)

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

// ========== SessionStorage ==========
// 会话存储,关闭标签页后清除,API 与 localStorage 相同

sessionStorage.setItem('token', 'abc123')
const token = sessionStorage.getItem('token')

// ========== 监听存储变化 ==========
// 在其他标签页修改 storage 时触发
window.addEventListener('storage', (e) => {
  console.log('key:', e.key)
  console.log('旧值:', e.oldValue)
  console.log('新值:', e.newValue)
  console.log('来源:', e.url)
})

// ========== 封装工具函数 ==========
const storage = {
  set(key, value, expires) {
    const data = {
      value,
      expires: expires ? Date.now() + expires : null
    }
    localStorage.setItem(key, JSON.stringify(data))
  },
  
  get(key) {
    const item = localStorage.getItem(key)
    if (!item) return null
    
    const data = JSON.parse(item)
    // 检查是否过期
    if (data.expires && Date.now() > data.expires) {
      localStorage.removeItem(key)
      return null
    }
    return data.value
  },
  
  remove(key) {
    localStorage.removeItem(key)
  }
}

// 使用:存储 1 小时过期的数据
storage.set('cache', { data: 'test' }, 60 * 60 * 1000)

5. Drag and Drop

<style>
  .drag-item {
    width: 100px;
    height: 100px;
    background: #3498db;
    color: white;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: move;
    margin: 10px;
  }
  .drop-zone {
    width: 300px;
    height: 200px;
    border: 2px dashed #ccc;
    display: flex;
    align-items: center;
    justify-content: center;
  }
  .drop-zone.over {
    border-color: #3498db;
    background: #ecf0f1;
  }
</style>

<!-- 可拖拽元素 -->
<div 
  class="drag-item" 
  draggable="true" 
  id="item1"
>
  拖拽我
</div>

<!-- 放置区域 -->
<div class="drop-zone" id="dropZone">
  放置区域
</div>

<script>
const dragItem = document.getElementById('item1')
const dropZone = document.getElementById('dropZone')

// 拖拽开始
dragItem.addEventListener('dragstart', (e) => {
  // 设置拖拽数据
  e.dataTransfer.setData('text/plain', e.target.id)
  e.dataTransfer.effectAllowed = 'move'
  // 添加拖拽样式
  e.target.style.opacity = '0.5'
})

// 拖拽结束
dragItem.addEventListener('dragend', (e) => {
  e.target.style.opacity = '1'
})

// 拖拽进入放置区域
dropZone.addEventListener('dragenter', (e) => {
  e.preventDefault()
  dropZone.classList.add('over')
})

// 在放置区域上方移动
dropZone.addEventListener('dragover', (e) => {
  e.preventDefault() // 必须阻止默认行为才能触发 drop
  e.dataTransfer.dropEffect = 'move'
})

// 离开放置区域
dropZone.addEventListener('dragleave', (e) => {
  dropZone.classList.remove('over')
})

// 放置
dropZone.addEventListener('drop', (e) => {
  e.preventDefault()
  dropZone.classList.remove('over')
  
  // 获取拖拽数据
  const id = e.dataTransfer.getData('text/plain')
  const element = document.getElementById(id)
  
  // 移动元素到放置区域
  dropZone.appendChild(element)
})
</script>

6. Web Worker

// ========== 主线程 main.js ==========
// 创建 Worker
const worker = new Worker('worker.js')

// 发送消息给 Worker
worker.postMessage({
  type: 'calculate',
  data: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
})

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

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

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

// ========== Worker 线程 worker.js ==========
// 接收主线程消息
self.onmessage = (e) => {
  const { type, data } = e.data
  
  if (type === 'calculate') {
    // 执行耗时计算(不会阻塞主线程)
    const result = heavyCalculation(data)
    
    // 返回结果给主线程
    self.postMessage(result)
  }
}

function heavyCalculation(arr) {
  // 模拟耗时操作
  let sum = 0
  for (let i = 0; i < 1000000000; i++) {
    sum += i
  }
  return arr.reduce((a, b) => a + b, 0)
}

// Worker 内部关闭自己
// self.close()

// ========== 内联 Worker ==========
// 不需要单独的 js 文件
const workerCode = `
  self.onmessage = (e) => {
    const result = e.data * 2
    self.postMessage(result)
  }
`
const blob = new Blob([workerCode], { type: 'application/javascript' })
const inlineWorker = new Worker(URL.createObjectURL(blob))

inlineWorker.postMessage(10)
inlineWorker.onmessage = (e) => {
  console.log('内联 Worker 结果:', e.data) // 20
}

7. 其他新特性

// ========== Geolocation 地理定位 ==========
navigator.geolocation.getCurrentPosition(
  (position) => {
    console.log('纬度:', position.coords.latitude)
    console.log('经度:', position.coords.longitude)
  },
  (error) => {
    console.error('定位失败:', error.message)
  }
)

// ========== History API ==========
// 添加历史记录
history.pushState({ page: 1 }, '标题', '/page1')

// 替换当前记录
history.replaceState({ page: 2 }, '标题', '/page2')

// 监听前进后退
window.addEventListener('popstate', (e) => {
  console.log('状态:', e.state)
})

// ========== 新的表单特性 ==========
/*
<input type="email" required>
<input type="url">
<input type="number" min="0" max="100">
<input type="range" min="0" max="100">
<input type="date">
<input type="color">
<input type="search" placeholder="搜索...">
<input pattern="[0-9]{11}" title="请输入11位手机号">
*/

// ========== 新的选择器 API ==========
document.querySelector('.class')
document.querySelectorAll('div')
element.classList.add('active')
element.classList.remove('active')
element.classList.toggle('active')
element.classList.contains('active')

关键点

  • 语义化标签:提升可读性和 SEO,常用 header/nav/main/article/section/aside/footer
  • Video/Audio:原生多媒体支持,通过 source 提供多格式兼容
  • Canvas:2D 绘图 API,适合图表、游戏、图像处理
  • LocalStorage:持久存储 5MB,SessionStorage 会话级存储,都是同步 API
  • Drag API:原生拖拽支持,核心是 dragstart/dragover/drop 事件
  • Web Worker:独立线程执行耗时任务,通过 postMessage 通信,不能操作 DOM