# 圖像標簽
圖片是互聯網的重要組成部分,讓網頁變得豐富多彩。本章介紹如何在網頁插入圖片。
## `<img>`
`<img>`標簽用于插入圖片。它是單獨使用的,沒有閉合標簽。
```html
<img src="foo.jpg">
```
上面代碼在網頁插入一張圖片`foo.jpg`。`src`屬性指定圖片的網址,上例是相對 URL,表示圖片與網頁在同一個目錄。
`<img>`默認是一個行內元素,與前后的文字處在同一行。
```html
<p>Hello<img src="foo.jpg">World</p>
```
上面代碼的渲染結果是,文字和圖片在同一行顯示。
圖像默認以原始大小顯示。如果圖片很大,又與文字處在同一行,那么圖片將把當前行的行高撐高,并且圖片的底邊與文字的底邊在同一條水平線上。
`<img>`可以放在`<a>`標簽內部,使得圖片變成一個可以點擊的鏈接。
```html
<a href="example.html">
<img src="foo.jpg">
</a>
```
上面代碼中,圖片可以像鏈接那樣點擊,點擊后會產生跳轉。
**(1)alt 屬性**
`alt`屬性用來設定圖片的文字說明。圖片不顯示時(比如下載失敗,或用戶關閉圖片加載),圖片的位置上會顯示該文本。
```html
<img src="foo.jpg" alt="示例圖片">
```
上面代碼中,`alt`是圖片的說明。圖片下載失敗時,瀏覽器會在圖片位置,顯示文字“示例圖片”。
**(2)width 屬性,height 屬性**
圖片默認以原始大小插入網頁,`width`屬性和`height`屬性可以指定圖片顯示時的寬度和高度,單位是像素或百分比。
```html
<img src="foo.jpg" width="400" height="300">
```
上面代碼中,`width`屬性指定圖片顯示的寬度為400像素,`height`屬性指定顯示高度為300像素。
注意,一旦設置了這兩個屬性,瀏覽器會在網頁中預先留出這個大小的空間,不管圖片有沒有加載成功。不過,由于圖片的顯示大小可以用 CSS 設置,所以不建議使用這兩個屬性。
一種特殊情況是,`width`屬性和`height`屬性只設置了一個,另一個沒有設置。這時,瀏覽器會根據圖片的原始大小,自動設置對應比例的圖片寬度或高度。舉例來說,圖片大小是 800像素 x 800像素,`width`屬性設置成200,那么瀏覽器會自動將`height`設成200。
**(3)srcset,sizes**
詳見下文的《響應式圖像》部分。
**(4)referrerpolicy**
`<img>`導致的圖片加載的 HTTP 請求,默認會帶有`Referer`的頭信息。`referrerpolicy`屬性對這個行為進行設置。
**(5)crossorigin**
有時,圖片和網頁屬于不同的網站,網頁加載圖片就會導致跨域請求,對方服務器可能要求跨域認證。`crossorigin`屬性用來告訴瀏覽器,是否采用跨域的形式下載圖片,默認是不采用。
簡單說,只要打開了這個屬性,HTTP 請求的頭信息里面,就會加入`origin`字段,給出請求發出的域名,不打開這個屬性就不加。
一旦打開該屬性,它可以設為兩個值。
- `anonymous`:跨域請求不帶有用戶憑證(通常是 Cookie)。
- `use-credentials`:跨域請求帶有用戶憑證。
下面是一個例子。
```html
<img src="foo.jpg" crossorigin="anonymous">
```
`crossorigin`屬性如果省略值的部分,則等同于`anonymous`。
```html
<img src="foo.jpg" crossorigin>
```
**(6)loading**
瀏覽器的默認行為是,只要解析到`<img>`標簽,就開始加載圖片。對于很長的網頁,這樣做很浪費帶寬,因為用戶不一定會往下滾動,一直看到網頁結束。用戶很可能是點開網頁,看了一會就關掉了,那些不在視口的圖片加載的流量,就都浪費了。
`loading`屬性改變了這個行為,可以指定圖片的懶加載,即圖片默認不加載,只有即將滾動進入視口,變成用戶可見時才會加載,這樣就節省了帶寬。
`loading`屬性可以取以下三個值。
> - `auto`:瀏覽器默認行為,等同于不使用`loading`屬性。
> - `lazy`:啟用懶加載。
> - `eager`:立即加載資源,無論它在頁面上的哪個位置。
```html
<img src="image.png" loading="lazy" alt="…" width="200" height="200">
```
由于行內圖片的懶加載,可能會導致頁面布局重排,所以使用這個屬性的時候,最好指定圖片的高和寬。
## `<figure>`,`<figcaption>`
`<figure>`標簽可以理解為一個圖像區塊,將圖像和相關信息封裝在一起。`<figcaption>`是它的可選子元素,表示圖像的文本描述,通常用于放置標題,可以出現多個。
```html
<figure>
<img src="https://example.com/foo.jpg">
<figcaption>示例圖片</figcaption>
</figure>
```
除了圖像,`<figure>`還可以封裝引言、代碼、詩歌等等。它等于是一個將主體內容與附加信息,封裝在一起的語義容器。
```html
<figure>
<figcaption>JavaScript 代碼示例</figcaption>
<p><code>const foo = 'hello';</code></p>
</figure>
```
## 響應式圖像
網頁在不同尺寸的設備上,都能產生良好的顯示效果,叫做[“響應式設計”](http://www.ruanyifeng.com/blog/2012/05/responsive_web_design.html)(responsive web design)。響應式設計的網頁圖像,就是“響應式圖像”(responsive image)。
響應式圖像的解決方案有很多,JavaScript 和 CSS 都可以實現。這里只介紹語義性最好的 HTML 方法,瀏覽器原生支持。
### 問題的由來
我們知道,`<img>`標簽用于插入網頁圖像,所有情況默認插入的都是同一張圖像。
```html
<img src="foo.jpg">
```
上面代碼在桌面端和手機上,插入的都是圖像文件`foo.jpg`。
這種處理方法固然簡單,但是有三大弊端。
**(1)體積**
一般來說,桌面端顯示的是大尺寸的圖像,文件體積較大。手機的屏幕較小,只需要小尺寸的圖像,可以節省帶寬,加速網頁渲染。
**(2)像素密度**
桌面顯示器一般是單倍像素密度,而手機的顯示屏往往是多倍像素密度,即顯示時多個像素合成為一個像素,這種屏幕稱為 Retina 屏幕。圖像文件很可能在桌面端很清晰,放到手機上會有點模糊,因為圖像沒有那么高的像素密度,瀏覽器自動把圖像的每個像素復制到周圍像素,滿足像素密度的要求,導致圖像的銳利度有所下降。
**(3)視覺風格**
桌面顯示器的面積較大,圖像可以容納更多細節。手機的屏幕較小,許多細節是看不清的,需要突出重點。


上面兩張圖片,下方的手機圖片經過裁剪以后,更突出圖像重點,明顯效果更好。
### `srcset`屬性
為了解決上面這些問題,HTML 語言提供了一套完整的解決方案。首先,`<img>`標簽引入了`srcset`屬性。
`srcset`屬性用來指定多張圖像,適應不同像素密度的屏幕。它的值是一個逗號分隔的字符串,每個部分都是一張圖像的 URL,后面接一個空格,然后是像素密度的描述符。請看下面的例子。
```html
<img srcset="foo-320w.jpg,
foo-480w.jpg 1.5x,
foo-640w.jpg 2x"
src="foo-640w.jpg">
```
上面代碼中,`srcset`屬性給出了三個圖像 URL,適應三種不同的像素密度。
圖像 URL 后面的像素密度描述符,格式是像素密度倍數 + 字母`x`。`1x`表示單倍像素密度,可以省略。瀏覽器根據當前設備的像素密度,選擇需要加載的圖像。
如果`srcset`屬性都不滿足條件,那么就加載`src`屬性指定的默認圖像。
### `sizes`屬性
像素密度的適配,只適合顯示區域一樣大小的圖像。如果希望不同尺寸的屏幕,顯示不同大小的圖像,`srcset`屬性就不夠用了,必須搭配`sizes`屬性。
第一步,`srcset`屬性列出所有可用的圖像。
```html
<img srcset="foo-160.jpg 160w,
foo-320.jpg 320w,
foo-640.jpg 640w,
foo-1280.jpg 1280w"
src="foo-1280.jpg">
```
上面代碼中,`srcset`屬性列出四張可用的圖像,每張圖像的 URL 后面是一個空格,再加上寬度描述符。
寬度描述符就是圖像原始的寬度,加上字符`w`。上例的四種圖片的原始寬度分別為160像素、320像素、640像素和1280像素。
第二步,`sizes`屬性列出不同設備的圖像顯示寬度。
`sizes`屬性的值是一個逗號分隔的字符串,除了最后一部分,前面每個部分都是一個放在括號里面的媒體查詢表達式,后面是一個空格,再加上圖像的顯示寬度。
```html
<img srcset="foo-160.jpg 160w,
foo-320.jpg 320w,
foo-640.jpg 640w,
foo-1280.jpg 1280w"
sizes="(max-width: 440px) 100vw,
(max-width: 900px) 33vw,
254px"
src="foo-1280.jpg">
```
上面代碼中,`sizes`屬性給出了三種屏幕條件,以及對應的圖像顯示寬度。寬度不超過440像素的設備,圖像顯示寬度為100%;寬度441像素到900像素的設備,圖像顯示寬度為33%;寬度900像素以上的設備,圖像顯示寬度為`254px`。
第三步,瀏覽器根據當前設備的寬度,從`sizes`屬性獲得圖像的顯示寬度,然后從`srcset`屬性找出最接近該寬度的圖像,進行加載。
假定當前設備的屏幕寬度是`480px`,瀏覽器從`sizes`屬性查詢得到,圖片的顯示寬度是`33vw`(即33%),等于`160px`。`srcset`屬性里面,正好有寬度等于`160px`的圖片,于是加載`foo-160.jpg`。
如果省略`sizes`屬性,那么瀏覽器將根據實際的圖像顯示寬度,從`srcset`屬性選擇最接近的圖片。一旦使用`sizes`屬性,就必須與`srcset`屬性搭配使用,單獨使用`sizes`屬性是無效的。
## `<picture>`
### 響應式用法
`<img>`標簽的`srcset`屬性和`sizes`屬性分別解決了像素密度和屏幕大小的適配,但如果要同時適配不同像素密度、不同大小的屏幕,就要用到`<picture>`標簽。
`<picture>`是一個容器標簽,內部使用`<source>`和`<img>`,指定不同情況下加載的圖像。
```html
<picture>
<source media="(max-width: 500px)" srcset="cat-vertical.jpg">
<source media="(min-width: 501px)" srcset="cat-horizontal.jpg">
<img src="cat.jpg" alt="cat">
</picture>
```
上面代碼中,`<picture>`標簽內部有兩個`<source>`標簽和一個`<img>`標簽。
`<picture>`內部的`<source>`標簽,主要使用`media`屬性和`srcset`屬性。`media`屬性給出媒體查詢表達式,`srcset`屬性就是`<img>`標簽的`srcset`屬性,給出加載的圖像文件。`sizes`屬性其實這里也可以用,但由于有了`media`屬性,就沒有必要了。
瀏覽器按照`<source>`標簽出現的順序,依次判斷當前設備是否滿足`media`屬性的媒體查詢表達式,如果滿足就加載`srcset`屬性指定的圖片文件,并且不再執行后面的`<source>`標簽和`<img>`標簽。
`<img>`標簽是默認情況下加載的圖像,用來滿足上面所有`<source>`都不匹配的情況,或者不支持`<picture>`的老式瀏覽器。
上面例子中,設備寬度如果不超過`500px`,就加載豎屏的圖像,否則加載橫屏的圖像。
下面給出一個例子,同時考慮屏幕尺寸和像素密度的適配。
```html
<picture>
<source srcset="homepage-person@desktop.png,
homepage-person@desktop-2x.png 2x"
media="(min-width: 990px)">
<source srcset="homepage-person@tablet.png,
homepage-person@tablet-2x.png 2x"
media="(min-width: 750px)">
<img srcset="homepage-person@mobile.png,
homepage-person@mobile-2x.png 2x"
alt="Shopify Merchant, Corrine Anestopoulos">
</picture>
```
上面代碼中,`<source>`標簽的`media`屬性給出屏幕尺寸的適配條件,每個條件都用`srcset`屬性,再給出兩種像素密度的圖像 URL。
### 圖像格式的選擇
除了響應式圖像,`<picture>`標簽還可以用來選擇不同格式的圖像。比如,如果當前瀏覽器支持 Webp 格式,就加載這種格式的圖像,否則加載 PNG 圖像。
```html
<picture>
<source type="image/svg+xml" srcset="logo.xml">
<source type="image/webp" srcset="logo.webp">
<img src="logo.png" alt="ACME Corp">
</picture>
```
上面代碼中,`<source>`標簽的`type`屬性給出圖像的 MIME 類型,`srcset`是對應的圖像 URL。
瀏覽器按照`<source>`標簽出現的順序,依次檢查是否支持`type`屬性指定的圖像格式,如果支持就加載圖像,并且不再檢查后面的`<source>`標簽了。上面例子中,圖像加載優先順序依次為 svg 格式、webp 格式和 png 格式。
## 參考鏈接
- [Responsive Images 101](https://cloudfour.com/thinks/responsive-images-101-definitions/), Jason Grigsby
- [Responsive images](https://developer.mozilla.org/en-US/docs/Learn/HTML/Multimedia_and_embedding/Responsive_images), MDN
- [Native lazy-loading for the web](https://web.dev/native-lazy-loading), Houssein Djirdeh