普通视图

发现新文章,点击刷新页面。
昨天 — 2025年11月28日首页

图片标签用 img 还是 picture?很多人彻底弄混了!

作者 刘大华
2025年11月28日 14:21

在网页开发中,图片处理是每个前端开发者都会遇到的基础任务。面对 <img><picture> 这两个标签,很多人存在误解:要么认为它们是互相替代的关系,要么在不合适的场景下使用了复杂的解决方案。今天,我们来彻底理清这两个标签的真正用途。

<img> 标签

<img> 是 HTML 中最基础且强大的图片标签,但它远比很多人想象的要智能。

基本语法:

<img src="image.jpg" alt="图片描述">

核心属性:

  • src:图片路径(必需)
  • alt:替代文本(无障碍必需)
  • srcset:提供多分辨率图片源
  • sizes:定义图片显示尺寸
  • loading:懒加载控制

<img> 的响应式能力被低估了

很多人认为 <img> 不具备响应式能力,这是错误的认知:

<img 
  src="image-800w.jpg"
  srcset="image-320w.jpg 320w,
          image-480w.jpg 480w,
          image-800w.jpg 800w"
  sizes="(max-width: 600px) 100vw,
         (max-width: 1200px) 50vw,
         33vw"
  alt="响应式图片示例"
>

这种写法的优势:

  • 浏览器自动选择最适合当前屏幕分辨率的图片
  • 根据视口大小动态调整加载的图片尺寸
  • 代码简洁,性能优秀

<picture> 标签

<picture> 不是为了替代 <img>,而是为了解决 <img> 无法处理的特定场景。

<picture> 解决的三大核心问题

1. 艺术指导(Art Direction) 在不同设备上显示不同构图或裁剪的图片:

<picture>
  <!-- 桌面端:宽屏全景 -->
  <source media="(min-width: 1200px)" srcset="hero-desktop.jpg">
  <!-- 平板端:适中裁剪 -->
  <source media="(min-width: 768px)" srcset="hero-tablet.jpg">
  <!-- 移动端:竖版特写 -->
  <img src="hero-mobile.jpg" alt="产品展示">
</picture>

2. 现代格式降级 优先使用高效格式,同时兼容老旧浏览器:

<picture>
  <source type="image/avif" srcset="image.avif">
  <source type="image/webp" srcset="image.webp">
  <img src="image.jpg" alt="格式优化示例">
</picture>

3. 复杂条件组合 同时考虑屏幕尺寸和图片格式:

<picture>
  <!-- 大屏 + AVIF -->
  <source media="(min-width: 1200px)" type="image/avif" srcset="large.avif">
  <!-- 大屏 + WebP -->
  <source media="(min-width: 1200px)" type="image/webp" srcset="large.webp">
  <!-- 大屏降级 -->
  <source media="(min-width: 1200px)" srcset="large.jpg">
  
  <!-- 移动端方案 -->
  <img src="small.jpg" alt="复杂条件图片">
</picture>

关键区别与选择指南

场景 推荐方案 原因
同一图片,不同分辨率 <img> + srcset + sizes 代码简洁,浏览器自动优化
不同构图或裁剪 <picture> 艺术指导必需
现代格式兼容 <picture> 格式降级必需
简单静态图片 <img> 无需复杂功能
兼容老旧浏览器 <img> 最广泛支持

常见误区纠正

误区一:<picture> 用于响应式图片

  • 事实: <img> 配合 srcsetsizes 已经能处理大多数响应式需求
  • 真相: <picture> 主要用于艺术指导和格式降级

误区二:<picture> 更现代,应该优先使用

  • 事实: 在不需要艺术指导或格式降级的场景下,<img> 是更好的选择
  • 真相: 合适的工具用在合适的场景才是最佳实践

误区三:响应式图片一定要用 <picture>

  • 事实: 很多响应式场景用 <img> + srcset 更合适
  • 真相: 评估需求,选择最简单的解决方案

场景分析

应该使用 <img> 的场景

网站Logo:

<img src="logo.svg" alt="公司Logo" width="120" height="60">

用户头像:

<img 
  src="avatar.jpg"
  srcset="avatar.jpg 1x, avatar@2x.jpg 2x"
  alt="用户头像"
  width="80" 
  height="80"
>

文章配图:

<img 
  src="article-image.jpg"
  srcset="article-image-600w.jpg 600w,
          article-image-1200w.jpg 1200w"
  sizes="(max-width: 768px) 100vw, 600px"
  alt="文章插图"
  loading="lazy"
>

应该使用 <picture> 的场景

英雄横幅(不同裁剪):

<picture>
  <source media="(min-width: 1024px)" srcset="hero-wide.jpg">
  <source media="(min-width: 768px)" srcset="hero-square.jpg">
  <img src="hero-mobile.jpg" alt="产品横幅" loading="eager">
</picture>

产品展示(格式优化):

<picture>
  <source type="image/avif" srcset="product.avif">
  <source type="image/webp" srcset="product.webp">
  <img src="product.jpg" alt="产品详情" loading="lazy">
</picture>

最佳实践

1. 始终遵循的规则

<!-- 正确:始终提供 alt 属性 -->
<img src="photo.jpg" alt="描述文本">

<!-- 错误:缺少 alt 属性 -->
<img src="photo.jpg">

<!-- 装饰性图片使用空 alt -->
<img src="decoration.jpg" alt="">

2. 性能优化策略

<!-- 优先加载关键图片 -->
<img src="hero.jpg" alt="重要图片" loading="eager" fetchpriority="high">

<!-- 非关键图片延迟加载 -->
<img src="content-image.jpg" alt="内容图片" loading="lazy">

<!-- 指定尺寸避免布局偏移 -->
<img src="product.jpg" alt="商品" width="400" height="300">

3. 现代图片格式策略

<picture>
  <!-- 优先使用AVIF,压缩率最高 -->
  <source type="image/avif" srcset="image.avif">
  <!-- 其次WebP,广泛支持 -->
  <source type="image/webp" srcset="image.webp">
  <!-- 最终回退到JPEG -->
  <img src="image.jpg" alt="现代格式示例">
</picture>

总结

<img><picture> 不是竞争关系,而是互补的工具:

  • <img>:处理大多数日常图片需求,特别是分辨率适配
  • <picture>:解决特定复杂场景,如艺术指导和格式降级

核心建议:

  1. 从最简单的 <img> 开始,只在必要时升级到 <picture>
  2. 充分利用 <img>srcsetsizes 属性
  3. 为关键图片使用 <picture> 进行格式优化
  4. 始终考虑性能和用户体验

掌握这两个标签的正确用法,你就能在各种场景下都做出最合适的技术选择,既保证用户体验,又避免过度工程化。

希望这篇指南能帮助你彻底理解这两个重要的HTML标签!

本文首发于公众号:程序员刘大华,专注分享前后端开发的实战笔记。关注我,少走弯路,一起进步!

📌往期精彩

《SpringBoot+Vue3 整合 SSE 实现实时消息推送》

《这20条SQL优化方案,让你的数据库查询速度提升10倍》

《SpringBoot 动态菜单权限系统设计的企业级解决方案》

《Vue3和Vue2的核心区别?很多开发者都没完全搞懂的10个细节》

🥁 用 HTML5 打造你的第一个“敲击乐” Web 应用

作者 今日无bug
2025年11月28日 01:45

在前端开发的世界里,HTML 是骨架,CSS 是皮肤,JavaScript 是灵魂。今天,我们将通过一个趣味十足的小项目——HTML5 敲击乐(Drum Kit) ,深入理解现代 Web 应用的构建逻辑:结构清晰、样式优雅、交互流畅,并实现真正的移动端适配。


一、项目目标:一个会“发声”的网页

想象一下:点击页面上的不同按键(如 Q、W、E、A、S 等),就能播放对应的鼓点或音效。这不仅是一个有趣的交互实验,更是对 HTML5 Audio API + 响应式布局 + 模块化开发思想 的综合实践。

而这一切,从一个静态页面开始。


二、HTML5 结构:语义化与职责分离

2.1 静态页面 = HTML + CSS

浏览器加载页面时:

  1. 下载并解析 HTML,构建 DOM 树;
  2. 遇到 <link> 标签,异步加载 CSS,构建 CSSOM;
  3. DOM + CSSOM 合并为 Render Tree,渲染出静态页面
  4. 最后执行 <script> 中的 JavaScript,赋予页面交互能力

最佳实践

  • CSS 放在 <head> 中,确保样式尽早加载;
  • JS 放在 </body> 前,避免阻塞 HTML 解析。
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
  <title>HTML5 敲击乐</title>
  <link rel="stylesheet" href="style.css" />
</head>
<body>
  <div class="container">
    <div class="key" data-key="81">Q</div> <!-- ASCII: Q=81 -->
    <div class="key" data-key="87">W</div>
    <div class="key" data-key="69">E</div>
    <!-- 更多按键... -->
  </div>
  <script src="app.js"></script>
</body>
</html>

💡 使用 data-key 存储键盘 keyCode,便于 JS 绑定事件。


三、CSS 样式:从 Reset 到响应式

3.1 为什么需要 CSS Reset?

不同浏览器对 <h1><ul><button> 等元素有默认样式差异(如 margin、padding)。若不统一,会导致跨浏览器显示不一致。

避免使用 * { margin: 0; padding: 0 } —— 虽然简单,但性能差(匹配所有元素),且可能误伤第三方组件。

✅ 推荐做法:显式列出常用标签重置

/* style.css */
html, body, div, span, h1, h2, p, ul, li {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

box-sizing: border-box 让 width 包含 padding 和 border,布局更可控。


3.2 背景图与单位选择:为移动端而生

我们的“敲击乐”界面可以有一个炫酷背景:

body {
  background: url('drum-bg.jpg') no-repeat center bottom;
  background-size: cover; /* 覆盖整个视口,可能裁剪 */
  /* 或用 contain:完整显示图片,但可能留白 */
  min-height: 100vh;
  font-family: sans-serif;
}

关于单位:

  • px:绝对单位,不随设备变化 → 不适合移动端
  • rem:相对于 <html>font-size
  • vh/vw:相对于视口高度/宽度(1vh = 1% 视口高度)。

移动端适配黄金法则
设置 html { font-size: 10px; },则 1rem = 10px,计算方便且可缩放。

html {
  font-size: 10px;
}

.container {
  min-height: 100vh;
  display: flex;
  justify-content: center;
  align-items: center;
  flex-wrap: wrap;
  gap: 2rem; /* 20px,但响应式 */
}

3.3 Flex 布局:让按键自动居中

我们希望 9 个 .key 按键在屏幕中央,无论手机还是平板:

.key {
  width: 15rem;      /* 150px 基准,但可随根字体缩放 */
  height: 15rem;
  background: rgba(255, 255, 255, 0.9);
  border-radius: 1rem;
  display: flex;
  justify-content: center;
  align-items: center;
  font-size: 4rem;
  font-weight: bold;
  cursor: pointer;
  transition: transform 0.1s ease;
}

.key:active {
  transform: scale(0.95);
}

🔥 display: flex + justify-content: center + align-items: center = 万能居中大法

即使设备宽度变化,Flex 也能智能排列(配合 flex-wrap: wrap 可换行)。


四、JavaScript 交互:让页面“发声”

4.1 使用 HTML5 Audio API

每个按键对应一个音频文件(如 clap.wav, hihat.wav):

// app.js
const sounds = {
  81: new Audio('sounds/clap.wav'),   // Q
  87: new Audio('sounds/hihat.wav'),  // W
  69: new Audio('sounds/kick.wav'),   // E
  // ...其他键
};

function playSound(e) {
  const keyCode = e.type === 'keydown' ? e.keyCode : parseInt(e.target.dataset.key);
  const sound = sounds[keyCode];
  if (sound) {
    sound.currentTime = 0; // 重置播放位置
    sound.play();
  }
}

// 键盘按下
window.addEventListener('keydown', playSound);

// 鼠标点击
document.querySelectorAll('.key').forEach(key => {
  key.addEventListener('click', playSound);
});

⚠️ 注意:现代浏览器要求用户主动交互(点击/按键)后才能播放音频,防止自动播放骚扰。


4.2 增强反馈:点击动画 + 音效同步

我们可以给按键添加“按下”效果:

function addEffect(keyCode) {
  const key = document.querySelector(`.key[data-key="${keyCode}"]`);
  if (key) key.classList.add('playing');
  setTimeout(() => {
    if (key) key.classList.remove('playing');
  }, 100);
}

配合 CSS:

.key.playing {
  transform: scale(0.9);
  box-shadow: 0 0 20px rgba(0, 100, 255, 0.7);
}

这样,视觉 + 听觉双重反馈,体验更沉浸。


五、响应式与性能优化

5.1 移动端触控支持

除了鼠标点击,还需监听 touchstart

document.querySelectorAll('.key').forEach(key => {
  key.addEventListener('touchstart', (e) => {
    e.preventDefault(); // 阻止默认滚动等行为
    playSound(e);
  });
});

5.2 预加载音频(可选)

为避免首次点击延迟,可提前加载:

Object.values(sounds).forEach(sound => sound.load());

六、总结:从前端基础到工程思维

通过这个“敲击乐”小项目,我们实践了:

技术点 实践价值
HTML 语义化 结构清晰,利于 SEO 与可访问性
CSS Reset 消除浏览器差异,打下一致基础
Flex 布局 实现复杂居中与自适应排列
rem / vh 单位 真正响应式,适配各种手机
模块化引入 CSS 在 head,JS 在 body 底部
HTML5 Audio 原生音效,无需插件
事件委托 & 用户交互 键盘 + 鼠标 + 触屏全覆盖

🎯 前端的天职不是炫技,而是:快速、稳定、一致地呈现内容,并提供流畅交互。

这个项目虽小,却涵盖了现代 Web 开发的核心理念:关注用户体验、拥抱响应式、坚持职责分离


七、延伸思考

  • 能否加入“录音”功能,录制一段节奏?
  • 能否用 Web MIDI API 连接真实电子鼓?
  • 能否用 Service Worker 实现离线使用?

技术无止境,但从一个静态页面开始,一步步赋予它生命,正是前端开发的魅力所在。


昨天以前首页

这 5 个冷门的 HTML 标签,能让你少写 100 行 JS

作者 ErpanOmer
2025年11月25日 15:38

image.png

大家好!😁。

Code Review 的时候,我最怕看到什么?

不是复杂的算法,也不是什么正则。而是明明一个 HTML 标签就能搞定的事,有人非要写几百行 JS + CSS 去重新发明轮子

前几天,我看到一个新同学为了写一个折叠面板(Accordion),引入了一个重型的第三方库,还写了一堆 useStateonClick 和动画逻辑。

我默默地把他的代码全删了,换成了 3 行 <details>。他看我的眼神,仿佛在看一个外星人🤣。

在 2025 年的今天,浏览器原生 HTML 的能力早已今非昔比。很多我们习惯用 JS 去模拟的交互,现在不仅有原生支持,而且性能更好、兼容性更强、无障碍(a11y)更完善

今天,我就来盘点 5 个被严重低估的HTML标签👇。


<details> & <summary>:折叠组件

你是不是还在写这样的 React 代码?

// JS 模拟版
const [isOpen, setIsOpen] = useState(false);
return (
  <div className="accordion">
    <div className="header" onClick={() => setIsOpen(!isOpen)}>
      点击展开 {isOpen ? '⬆️' : '⬇️'}
    </div>
    {isOpen && <div className="content">...</div>}
  </div>
);

为了这个功能,你还得写 CSS 动画,还得处理键盘事件(Tab 键能不能选到?回车能不能展开?等等)。

HTML 原生写法:

<details>
  <summary>点击展开</summary>
  <div class="content">
    这里是展开后的内容,原生支持 Ctrl+F 页内搜索!
  </div>
</details>
  1. 没有任何JS:自带点击展开/收起交互。
  2. 无障碍(a11y)满分:屏幕阅读器能完美识别,Tab 键、回车键原生支持。
  3. 页内搜索:这是 JS 模拟版最大的痛点。如果内容被 JS 隐藏了(display: none),浏览器的 Ctrl+F 往往搜不到。但 <details> 里的内容,即使折叠,浏览器也能搜到并自动展开!

Untitled ‑ Made with FlexClip.gif

配合 CSS 👇

details {
  border: 1px solid #ccc;
  border-radius: 6px;
  padding: 8px;
}

summary {
  cursor: pointer;
  font-weight: bold;
}

/* 包住内容,让它能动画高度 */
details > .content {
  overflow: hidden;
  max-height: 0;
  opacity: 0;
  transition: max-height .45s ease, opacity .3s ease;
}

/* details 处于 open 状态时 */
details[open] > .content {
  max-height: 200px; /* 你内容高度大概多少设多少,足够大即可 */
  opacity: 1;
}

依然可以做动画。


<dialog>:弹窗组件

写模态框(Modal)是前端最大的坑之一。你需要考虑:

  • z-index 层级会不会被遮挡?
  • 点击遮罩层关闭?
  • Focus Trap(焦点锁定) :打开弹窗后,Tab 键不能跑到底层页面去。
  • 按下 Esc 键关闭?

为了解决这些,我们通常会引入 Antd Modal 或者 React Portal。但在轻量级场景下,原生 <dialog> 才是神🫅。

HTML 原生:

<dialog id="myModal">
  <form method="dialog">
    <p>这是一个原生模态框</p>
    <button>关闭(自动)</button>
  </form>
</dialog>

<button onclick="myModal.showModal()">打开弹窗⏏</button>

Untitled ‑ Made with FlexClip.gif

  1. Top Layer(顶层特性) :浏览器会把它渲染在所有 DOM 的最上层,彻底无视父元素的 z-indexoverflow: hidden
  2. ::backdrop 伪元素:直接用 CSS 定制遮罩层样式。
/* 背景遮罩 */
dialog::backdrop {
    background: rgba(0, 0, 0, 0.45);
    backdrop-filter: blur(3px);
    transition: opacity .3s ease;
}
  1. 原生交互:自带 Esc 关闭,自带焦点管理,表单提交自动关闭。

<datalist>:搜索自动补全

当产品经理要求做一个带搜索建议的输入框时,你的第一反应是不是:“快!引入 Select2 或者 Antd AutoComplete!😖

且慢。如果只是简单的建议列表,几 KB 的 JS 库都显得太重了。

HTML 原生版:

<label>选择你喜欢的框架:</label>
<input list="frameworks" />

<datalist id="frameworks">
  <option value="React">
  <option value="Vue">
  <option value="Svelte">
  <option value="Angular">
  <option value="Solid">
</datalist>
  1. 模糊搜索:浏览器原生支持模糊匹配(打 u 会出来 Vue)。
  2. 响应式:在手机上,它会调用系统原生的下拉选择 UI,体验比网页模拟的更顺滑。
  3. 解耦:它只是一个建议列表,用户依然可以输入列表里没有的值(这点和 Select 不同)。

<fieldset> & disabled:一键禁用整个表单

场景:用户点击提交按钮后,为了防止重复提交,我们需要禁用表单里的所有输入框

JS 笨办法:

// 还要一个个去拿 DOM,或者维护一个 loading 状态传给所有组件
inputs.forEach(input => input.disabled = true);
buttons.forEach(btn => btn.disabled = true);

HTML 原生写法:

<form>
  <fieldset disabled id="login-group">
    <legend>登录</legend>
    <input type="text" placeholder="用户名">
    <input type="password" placeholder="密码">
    <button>提交</button>
  </fieldset>
</form>

<script>
  // 一行代码搞定状态切换
  document.getElementById('login-group').disabled = true; 
</script>

clideo_editor_c3a7f45a392f482ea0added4098a5be3.gif

这是一个极好的分组思维。通过给 <fieldset> 设置 disabled,浏览器会自动禁用内部所有的 <input>, <select>, <button>。不用写循环,不用维护复杂的 State。


<input type="file" capture>:H5 调用手机相机

场景:业务需要用户上传一张照片,可以是相册选的,也可以是当场拍的

很多新手的反应是:是不是要接微信 JSSDK?是不是要写个 Bridge 调原生 App 能力?

答案是不需要!

HTML 原生:

<input type="file" capture="environment" accept="image/*">

只要加上 capture 属性,在移动端(iOS/Android)点击上传时,系统会直接拉起相机,而不是让你去选文件。拍完照后,你拿到的就是一个标准的 File 对象。

不需要什么 JS SDK,实现原生级体验👍。


我总是强调 最好的代码,是没有代码!

HTML 标准一直在进化,很多曾经需要重型 JS 才能实现的功能,现在已经成了浏览器的出厂设置了。

使用这些原生标签,不仅能减少打包体积,更能让你的应用在可访问性性能上天然领先。

下次再想 npm install 一个 UI 库之前,先查查 MDN。说不定,HTML 早就帮你做好了🤔。

❌
❌