Skip to content
This page has been auto-translated and may contain errors.View in English

图片与媒体 ​

docs.scrimba.com

网页的内容很大一部分是文字,但绝不是全部。照片、图表、插图、视频片段和声音同样是内容,HTML 为每一种都准备了相应的元素。这一章讲的就是把媒体放到页面上要用的标签:怎样指向一个图片文件,怎样为看不见图片的人描述它,以及怎样把合适的文件发给合适的屏幕,而不浪费别人的流量。

图片元素 ​

在页面上放一张图片要用**图片元素**,写作 <img>。它没有内容,也没有闭合标签。取而代之,你要通过属性告诉它两件事:显示哪个文件,以及这张图是什么内容。

html
<img src="golden-retriever.jpg" alt="A golden retriever sitting in tall grass">

src 是来源:图片文件的路径或网址。alt 是替代文本:用文字简要描述这张图片。如果图片加载不出来,或者有人是用屏幕阅读器"听"页面而不是用眼睛看,那么 alt 文本就是他们能得到的信息。所以写的时候要当作是在电话里向朋友描述这张图片一样。

<img> 元素会把一个外部文件引入页面。它是一个空元素,没有闭合标签;它需要的一切都写在属性里。其中两个属性是必不可少的:

html
<img src="/images/team-photo.jpg" alt="The five members of the design team at their desks">

src 是文件的路径,可以相对于当前页面,也可以是一个绝对 URL。alt 是**替代文本**,也就是当图片无法显示或无法被看见时,用来代替图片的文字。

把 alt 写对是大家常常忽略、但不该忽略的地方。要描述图片的内容和用途,而不是文件本身的细节。"柱状图显示销售额在 2023 到 2024 年间翻了一番"能告诉读者图表说的是什么;"chart.png" 则什么都没告诉他们。把它控制在一句话以内,也不要以"图片:"开头,因为浏览器本身就会告知这是一张图片。

<img> 是一个空元素,它加载一个外部资源,并默认与文字一起以行内方式排布。真正起作用的是两个属性:src(资源 URL)和 alt(文本替代内容)。这一节接下来主要讲 alt,因为一张图片的可访问性和正确性,其实就体现在这里。

alt 属性不是图注,也不是对文件的描述。它是一种替代:图片缺失时浏览器会在图片位置渲染出的文字,也是**屏幕阅读器**(为看不见页面的人朗读页面内容的软件)用来朗读的文字,代替了这张图。判断标准是功能上的等价性。如果把图片去掉,把你写的 alt 文本插入到那个位置,句子是否依然通顺,传达的信息是否一样?好的 alt 能通过这个测试;"photo1" 显然不能。

由此可以得出两条规则。第一,描述用途而不是像素内容:搜索按钮里的一个放大镜图标,alt 应该写成 alt="搜索",而不是 alt="放大镜",因为它在页面上的作用是执行搜索这个动作,不是展示一个图案。第二,装饰性图片应该使用空的 alt。当一张图片不承载任何信息时,比如一个背景装饰或分隔线,就写 alt="",引号之间什么都不放。这和不写 alt 完全不同。空的 alt 是在告诉辅助技术"跳过这个,它没有任何含义",而缺失的 alt 会让屏幕阅读器退回去朗读文件名,这比什么都不读还糟糕。

html
<!-- 有意义的内容:描述它传达的信息 -->
<img src="revenue-2024.png" alt="Revenue rose from 1.2 to 2.4 million across 2024">

<!-- 装饰性内容:空 alt,辅助技术会跳过它 -->
<img src="corner-flourish.svg" alt="">
Juno图片元素 一张图片需要两样东西:src,也就是要显示的文件,以及 alt,用文字描述这张图片。写 alt 的时候,就当作是在电话里告诉别人照片里有什么。这是最容易被忽略的部分,也是最重要的部分。
Juno图片元素<img> 是一个空元素:没有闭合标签,一切都在属性里。要描述图片传达的信息,而不是文件本身,也永远不要以图片:开头,浏览器早就知道这是一张图片。图表的 alt 应该告诉别人这张图表说的是什么数字。
Juno图片元素 好的 alt 是一种替代,不是图注:把它放进句子里,意思应该依然完整。要描述它的作用,而不是它的样子,所以一个搜索图标应该写作 搜索。而装饰性图片用 alt="" 是一个明确的选择,它告诉屏幕阅读器跳过,而缺失的 alt 会让它把文件名读出来。

响应式图片 ​

一张在笔记本电脑上看起来很清晰的照片,尺寸可能远远超出手机所需要的大小。把这个完整尺寸的文件发给小屏幕,会浪费访客的流量,还会拖慢页面速度。HTML 允许你为同一张图片提供几个不同的版本,让浏览器自己挑一个合适的。

刚开始学的时候,不是每张图片都需要这样处理。一个 <img> 配上一个大小合理的文件就够用了。但了解这个概念的存在是有好处的:同一张照片可以有几种尺寸,由浏览器来挑选。

html
<img src="beach-800.jpg" alt="Waves rolling onto a quiet beach at sunset">

**响应式图片**让浏览器可以根据屏幕情况选择要用的文件。你不再只写死一个来源,而是用 srcset 列出几个候选文件,并用 sizes 描述图片实际显示的宽度。浏览器会自己计算,下载最合适的那一个。

html
<img
  src="beach-800.jpg"
  srcset="beach-400.jpg 400w, beach-800.jpg 800w, beach-1600.jpg 1600w"
  sizes="(max-width: 600px) 100vw, 50vw"
  alt="Waves rolling onto a quiet beach at sunset">

在 srcset 里,每一项把一个文件和它的实际像素宽度配对(400w 表示这个文件宽 400 像素)。在 sizes 里,你告诉浏览器这张图片会占用多大的空间:这里在屏幕宽度不超过 600 像素时,图片会占满整个视口宽度(100vw),其他情况下占一半宽度(50vw)。src 依然保留,作为给旧浏览器的备用方案。注意 alt 只需要写一次,适用于所有来源,因为它们都是同一张图片。

如果你需要的不仅是尺寸切换,就该用 <picture> 了。它包裹着几个 <source> 元素和一个 <img>,浏览器会使用第一个匹配的 <source>。这可以处理艺术指导(art direction)——在不同宽度下提供裁切方式不同的图片——以及现代文件格式的问题:

html
<picture>
  <source srcset="hero.avif" type="image/avif">
  <source srcset="hero.webp" type="image/webp">
  <img src="hero.jpg" alt="Two cyclists climbing a mountain road at dawn">
</picture>

浏览器从上往下读取,选择第一个它能识别的格式,如果都不匹配,就退回到普通的 <img>。里面的 <img> 是必须写的;它才是真正被渲染出来的部分,alt 也写在这里。

响应式图片解决的是一个"分辨率切换"问题:同一张图片,针对不同的显示设备提供不同的像素尺寸,由浏览器在下载之前就做出选择,这样谁都不需要为看不见的像素付出流量代价。这里有两个工具,它们解决的是不同的问题。

srcset 配合 sizes 回答的问题是"用哪种分辨率?"。你给浏览器提供一组候选文件,每个文件都用 w 描述符标注了自己的固有宽度(像素),再给出一个 sizes 值,告诉浏览器这张图片在各个断点下会渲染成多宽。**断点(breakpoint)**是指布局发生变化的屏幕宽度临界值。浏览器会结合你给出的 sizes 提示和设备的像素密度,挑出仍然清晰、但体积最小的那个文件。

html
<img
  src="product-800.jpg"
  srcset="product-400.jpg 400w, product-800.jpg 800w, product-1200.jpg 1200w"
  sizes="(max-width: 700px) 100vw, 350px"
  alt="Leather backpack shown from the front">

<picture> 元素回答的是另一个问题:"完全换用哪个来源?"它适用于文件本身需要变化的场景,而不仅仅是缩放比例的变化。常见原因有两种。艺术指导:宽幅横幅在窄屏幕上要裁切成竖幅肖像图,才能让主体保持可见,这需要不同的图片文件,而不是把一个文件缩小。格式协商:当你想提供一种现代格式并附带回退方案时,需要浏览器根据自己能解码的格式来挑选。

html
<picture>
  <source media="(max-width: 600px)" srcset="hero-portrait.avif" type="image/avif">
  <source media="(max-width: 600px)" srcset="hero-portrait.jpg">
  <source srcset="hero-wide.avif" type="image/avif">
  <img src="hero-wide.jpg" alt="Chef plating a dish in a busy kitchen">
</picture>

顺序很重要:浏览器会采用第一个满足其 media 和 type 条件的 <source>,所以要把你最想用、限制条件最严格的选项放在前面。末尾的 <img> 是必须的,它才是页面真正渲染出来的元素;各个 <source> 上的 srcset 和 sizes 是为它提供数据的,但 alt、width、height 以及加载行为,都要写在这个内层的 <img> 上。

Juno响应式图片 同一张照片可以有几种尺寸,浏览器可以挑选适合屏幕的那一个,这样手机就不会下载一个笔记本电脑大小的文件。刚开始不需要用这个;一个普通的 <img> 就够了。等页面在手机上开始变得沉重时,这个选项就在那儿等着你用。
Juno响应式图片 想让浏览器挑尺寸时用 srcset 和 sizes,想换整个文件本身(换裁切方式或换成现代格式)时用 <picture>。<picture> 里面的 <img> 是必须的,alt 也写在这里。alt 只需要写一次,因为所有来源都是同一张图片。
Juno响应式图片 这是两件不同的事:srcset 加 sizes 是挑分辨率,<picture> 是为艺术指导或格式协商挑一整个来源。各个来源按从上到下的顺序读取,第一个匹配的胜出,所以要把限制最严格的放在最前面。真正渲染出来的一切都挂在内层的 <img> 上,所以 alt 和尺寸也都写在那里。

视频与音频 ​

HTML 不需要任何插件就能播放视频和声音。播放视频用 <video> 元素;播放声音用 <audio>。加上 controls 属性,浏览器就会免费给你一个播放按钮、一条时间轴和一个音量控制器。

html
<video src="tutorial.mp4" controls></video>

<audio src="podcast-episode.mp3" controls></audio>

和图片不一样,这两个元素都有闭合标签,因为你可以在开始标签和结束标签之间放置备用文本或额外的设置。刚入门时最该记住的一点:一定要加上 controls,否则访客就没有办法按下播放键。

<video> 和 <audio> 元素会在页面里嵌入一个原生的媒体播放器。controls 属性会显示浏览器内置的播放、暂停、跳转和音量界面;如果不加这个属性,就什么控制界面都没有。

html
<video src="product-demo.mp4" controls width="640" poster="demo-cover.jpg"></video>

有几个属性值得用上。poster 设置视频播放前显示的一张静态图片,这样播放器就不是一个黑框。width 用来固定播放器的尺寸。你还可以加上 autoplay、loop 和 muted,不过大多数浏览器只有在视频同时设置了 muted 时才允许 autoplay,所以这两个通常要一起出现。

如果想提供多种文件格式,就不要写 src,改成在里面列出 <source> 元素。浏览器会播放它第一个能播放的:

html
<video controls width="640" poster="demo-cover.jpg">
  <source src="product-demo.webm" type="video/webm">
  <source src="product-demo.mp4" type="video/mp4">
  Your browser cannot play this video.
</video>

各个来源后面的文字是备用内容,只有当浏览器无法播放其中任何一个时才会显示。<audio> 的用法一样,同样有 controls 和 <source> 这套模式,只是没有 poster、width 这类和画面有关的属性。

<video> 和 <audio> 嵌入了浏览器原生的媒体处理流程——解码、缓冲,以及内置的播放器界面——完全不需要第三方插件。两者都支持 controls(显示原生界面),也都能包含多个 <source> 子元素,让你提供多种编解码格式(codec)(视频或音频文件所采用的压缩格式),由浏览器挑选第一个自己能解码的。

html
<video controls width="640" height="360" poster="demo-cover.jpg" preload="metadata">
  <source src="demo.webm" type="video/webm">
  <source src="demo.mp4" type="video/mp4">
  <track kind="captions" src="demo-captions.en.vtt" srclang="en" label="English" default>
  Your browser cannot play this video.
</video>

这里主要有两个生产层面的问题需要考虑。第一个是可访问性,也就是让听不到或看不到的人也能使用这段媒体。这正是 <track> 的用途:它把一个带时间信息的文本文件附加到媒体上,通常是 WebVTT 格式(一种纯文本的字幕格式,文件后缀是 .vtt)。kind="captions" 为语音和重要声音提供文字版本,方便听力有障碍的人,或是处于静音环境下的人观看——而在公共场合刷手机的大多数人正是如此。kind="subtitles" 则是把对话翻译成另一种语言。default 属性能让某条字幕轨道自动开启,用户不需要自己去找。没有字幕的视频会把观众中相当一部分人排除在外,而字幕同样对搜索引擎有帮助,因为它们看不了视频,但能读文字稿。

第二个问题是带宽。preload 控制浏览器在播放开始前提前获取媒体的积极程度:preload="none" 在用户按下播放键之前什么都不下载,preload="metadata" 只获取时长和尺寸信息,preload="auto" 则可能把整个文件都拉下来。对于首屏以下的任何内容,默认设成 metadata 或 none 比较合适,因为一个自动加载的视频,是页面在不动声色间就能造成的最大负担之一。同样的 autoplay 限制依然适用:浏览器会阻止有声音的自动播放,所以自动播放的视频必须是 muted 的,这也是字幕之所以重要的另一个原因。

Juno视频与音频<video> 播放视频,<audio> 播放声音,都不需要插件。一定要加上 controls,否则就没有播放按钮,访客会卡在那里。这两个元素都有闭合标签,因为你可以在里面放设置和备用文本,这和图片不一样。
Juno视频与音频 一定要加上 controls,设置一个 poster,这样播放器就不是一块黑色的矩形;再列出几个 <source> 元素来提供多种格式。autoplay 只有在视频同时是 muted 时才会生效,所以这两个要一起用。音频也是一样的模式,只是没有那些和画面有关的属性。
Juno视频与音频 加一个带字幕的 <track>;大多数人是静音观看的,而搜索引擎也是靠读文字来"看"视频的。对于首屏以下的内容,把 preload 设成 metadata 或 none,一个自动加载的视频是让页面悄悄变重的方式之一。而且有声音的自动播放会被浏览器阻止,所以自动播放就意味着必须静音,这又是一个该加字幕的理由。

尺寸设置、延迟加载与性能 ​

图片加载的时候,可能会把页面上其他内容挤得东倒西歪。你正在读一段文字,上方一张照片突然加载完成,文字就猛地往下跳了一下。这种跳动很烦人,而且是可以避免的:提前用 width 和 height 告诉浏览器图片的尺寸。

html
<img src="team-lunch.jpg" alt="The team sharing lunch on the office balcony"
     width="800" height="600">

有了这两个数字,浏览器就会在图片到达之前先预留出正确大小的空间,这样图片加载完成时就不会有任何跳动。你给出的是文件的真实像素尺寸,CSS 依然可以在屏幕上重新调整它的显示大小。

图片会影响用户能感受到的两件事:页面加载时晃动的程度,以及消耗的流量。HTML 为这两方面都提供了相应的属性。

设置 width 和 height 可以防止**布局偏移**,也就是图片加载进来时内容跳来跳去的现象。当你声明了尺寸,浏览器会立刻预留出这块空间,而不是先把图片位置塌陷成零,等文件到达后再把它撑开。

html
<img src="article-header.jpg" alt="Overhead view of a busy newsroom"
     width="1200" height="675" loading="lazy">

loading="lazy" 属性告诉浏览器,在读者快要把这张图片滚动到视口之前,先不要下载它。对于一个很长的页面上位置很靠下的图片来说,这能节省流量,并加快首次渲染的速度。有一条规则要记住:不要对访客一进页面就能看到的第一张图片使用延迟加载。这张图片你希望它尽快加载出来,所以要把 loading 属性留空(或者设成 loading="eager")。

媒体内容通常是页面上最"重"的部分,所以那些控制它加载方式的属性,本质上就是性能控制手段,没有一个是可有可无的装饰。这里有三点值得注意。

第一是累积布局偏移(cumulative layout shift),衡量的是页面加载过程中,可见内容跳动的程度。常见的原因是一张没有声明尺寸的图片:浏览器会先把它的高度当作零,把下面的内容都排列好,等文件到达、知道了真实高度之后,再把整列内容重新排一遍。要避免这种情况,就要提前把宽高比告诉浏览器。把 width 和 height 设成文件的真实像素尺寸,浏览器就能计算出比例,即使 CSS 在改变显示尺寸,也能预留出正确大小的空间:

html
<img src="feature.jpg" alt="Solar panels across a green field"
     width="1600" height="900" loading="lazy" decoding="async">

你也可以用 CSS 的 aspect-ratio 属性来表达同样的意图;关键在于这个比例必须在像素下载之前就是已知的。

第二,loading="lazy" 会推迟屏幕外图片的下载,直到读者快要滚动到它们附近,这能减少长页面的初始加载量。一个关键的例外是你的 LCP 元素(Largest Contentful Paint,最大内容绘制,指的是首屏内可见的最大图片或区块,也是浏览器和搜索排名都会关注的一个指标)。如果对这张图片使用延迟加载,反而会拖慢这个指标本身要衡量的东西,所以首屏之内的主图应该立即加载,而不是延迟加载。

第三,decoding="async" 让浏览器可以在主线程之外解码图片,这样把压缩数据变成像素的过程就不会阻塞其他渲染工作。对于不是屏幕上第一个出现的图片来说,这是一个小而稳妥的优化。

格式选择是另一个可以利用的手段。WebP 和 AVIF 是现代图片格式,在同等视觉质量下,压缩后的体积比 JPEG 或 PNG 小得多;AVIF 通常在文件大小上更有优势,WebP 则在浏览器支持度和编码速度上更有优势。这两者的取舍就在于兼容性和工具链的支持,而这恰恰是带 type 的 <source> 配合 <picture> 要解决的问题:先提供 AVIF,再提供 WebP,最后用 JPEG 或 PNG 兜底,这样每个浏览器都能拿到自己实际能解码的最优文件。

Juno尺寸设置、延迟加载与性能 给每张图片都加上 width 和 height,页面就不会再随着照片加载而晃动。用文件的真实像素尺寸;CSS 依然可以在屏幕上把它缩小。这一个小习惯,就能解决加载过程中最让人烦躁的问题。
Juno尺寸设置、延迟加载与性能width 和 height 会预留空间,让图片加载时内容不会跳动;loading="lazy" 会推迟图片下载,直到它们快要被滚动进视口。唯一不该用延迟加载的,是页面上的第一张图片;那张图应该立刻加载。
Juno尺寸设置、延迟加载与性能 声明 width 和 height(或者设置 aspect-ratio),浏览器就会预留好这块区域,布局不会再跳动;这个比例只需要在像素到达之前就已知即可。除了首屏的主图之外,其他所有图片都可以延迟加载,因为对可见的最大图片做延迟加载,只会拖慢那个专门衡量它的指标。再通过 <picture> 配合 JPEG 兜底来提供 AVIF 或 WebP,这样每个浏览器都能拿到自己能解码的最小文件。