HTML 中的最佳图像
你已经有了漂亮的页面,现在正在添加背景图片……
.hero {
/* 🚩 */
background-image: url('/image.png');
}
等待!
你知道这在性能方面会非常不优化吗?而且不止一方面。
为什么(通常)应该background-image在 CSS 中避免使用 CSS
最佳图像尺寸
除了使用 SVG 格式之外,考虑到如今人们使用的屏幕尺寸和分辨率种类繁多,几乎没有任何情况下,网站的每个访问者都应该收到完全相同的图像文件。
你们的网站现在能用手表了吗?(开玩笑……我想应该可以)
你可以说……哦!媒体查询,我会手动指定屏幕尺寸和图像尺寸的范围:
/* 🚩 */
.hero { background-image: url('/image.png'); }
@media only screen and (min-width: 768px) {
.hero { background-image: url('/image-768.png'); }
}
@media only screen and (min-width: 1268px) {
.hero { background-image: url('/image-1268.png'); }
}
嗯,这样做有个问题。除了相当繁琐冗长之外,它只考虑了屏幕尺寸,而没有考虑分辨率。
所以你可以说……啊哈!我知道一个很棒的技巧,image-set可以根据不同的分辨率指定不同的图像尺寸:
/* 🚩 */
.hero {
background-image: image-set(url("/image-1x.png") 1x, url("/image-2x.png") 2x);
}
你说得对,这样做确实有一些好处。但总的来说,我们需要同时考虑屏幕尺寸和分辨率。
所以我们可以编写一些臃肿的 CSS,将媒体查询和图像集结合起来,但这只会变得复杂,而且这意味着我们需要确切地知道每个屏幕上的图像大小,即使网站布局随着时间的推移而演变。
而且它仍然不支持一些关键功能,例如延迟加载、受支持浏览器的下一代格式、优先级提示、异步解码等等……
更糟糕的是,我们还遇到了链式请求的问题:
避免链式请求
将“获取 CSS -> 获取图像”改为“获取 html -> 获取图像”/>
通过图像标签,HTML 代码右侧会有一个链接src。这样浏览器就可以获取初始 HTML 代码,扫描图像,并立即开始获取优先级高的图像。
如果要在 CSS 中加载图片,假设你使用外部样式表(link rel=”styleshset”就像大多数人那样,而不是style到处使用内联样式),浏览器必须扫描你的 HTML,获取 CSS,然后找到background-image应用了 CSS 的元素,最后才能获取图片。这会花费更长时间。
没错,有些问题可以通过一些方法解决——比如内联 CSS、预加载图片和预连接到源。但是,正如你将在下文中读到的,你会发现 HTMLimg标签还有一些 CSS 所不具备的优势background-image。
何时考虑使用背景图片
在我们继续讨论加载图片的最佳方式之前——就像所有规则一样,这里也有例外。例如,如果您有一张非常小的图片想要平铺显示background-repeat,据我所知,使用标签并没有简单的方法来实现重复显示img。
但是对于任何大于 50px 的图像,我强烈建议避免在 CSS 中设置它,而应该使用img标签,即使不是对所有图像都如此。
优化图像加载
既然我们已经抱怨了在 CSS 中使用 CSS 的挑战background-image,那么让我们来谈谈实际的解决方案。
在现代HTML中,`<img>`img标签提供了许多有用的属性,可以帮助我们优化图像加载。让我们一起来看看这些属性。
浏览器原生懒加载
我们能从标签中获得的第一个img提升图像性能的强大属性是loading=lazy:
<!-- 😍 -->
<img
loading="lazy"
...
>
这已经是一项巨大的改进,因为现在访客不会自动下载不在视口内的图片。更棒的是,它的性能非常出色,完全由浏览器原生实现,无需 JavaScript,并且所有现代浏览器都支持此功能。
请注意一个重要细节——理想情况下,不要对“首屏”图片进行延迟加载(即,图片会在浏览器首次加载时立即显示在视口中)。这样可以确保最重要的图片能够尽快加载,而其他图片则仅在需要时才加载。
PS:loading=lazy 也适用于iframes😍
适用于所有屏幕尺寸和分辨率的最佳尺寸
图片的适配srcset至关重要。除非你加载的是 SVG 格式的图片,否则你需要确保不同屏幕尺寸和分辨率下的图片都能显示最佳尺寸:
<img
srcset="
/image.png?width=100 100w,
/image.png?width=200 200w,
/image.png?width=400 400w,
/image.png?width=800 800w
"
...
>
需要注意的一点是,这比 CSS 中的版本功能更强大,因为你可以在 中image-set使用该w单位img srcset。
它的优点在于它同时考虑了图像尺寸和分辨率。因此,如果图像当前在 2 倍像素密度设备上显示为 200 像素宽,srcset浏览器会识别并抓取该400w图像(即宽度较大的图像400px,因此在 2 倍像素密度下显示效果最佳)。同样,在 1 倍像素密度设备上,浏览器也会抓取同一图像200w。
现代格式带有picture标签
您可能已经注意到,我们.png在示例中使用的是 .png 格式。虽然所有浏览器都支持 .png 格式,但它几乎从来都不是最佳的图像格式。
这就是为什么在标签picture周围添加 `<img>` 标签img可以让我们指定更现代、更优化的格式,例如webp,而受支持的浏览器会优先选择这些格式source:
<picture>
<source
type="image/webp"
srcset="
/image.webp?width=100 100w,
/image.webp?width=200 200w,
/image.webp?width=400 400w,
/image.webp?width=800 800w
" />
<img ... />
</picture>
您还可以选择支持其他格式,例如AVIF:
<picture>
<source
type="image/avif"
srcset="/image.avif?width=100 100w, /image.avif?width=200 200w, /image.avif?width=400 400w, /image.avif?width=800 800w, ...">
<source
type="image/webp"
srcset="/image.webp?width=100 100w, /image.webp?width=200 200w, /image.webp?width=400 400w, /image.webp?width=800 800w, ...">
<img ...>
</picture>
别忘了aspect-ratio
需要注意的是,我们还要避免布局偏移。如果在图片下载之前没有为其指定精确尺寸,图片加载时就会发生布局偏移。有两种方法可以解决这个问题。
首先,要为图片指定 `a`width和`h`height属性。另外,虽然并非必须,但通常建议在 CSS 中将图片设置height为auto`overflow: hidden`,以便图片能够随着屏幕尺寸的变化而正确响应:
<img
width="500"
height="300"
style="height: auto"
...
>
或者,您也可以使用aspect-ratioCSS 中较新的属性来自动保持正确的宽高比。使用此选项,您无需知道图像的确切宽度和高度,只需知道其宽高比即可:
<img style="aspect-ratio: 5 / 3; width: 100%" ...>
aspect-ratio也非常适合与object-fit和搭配使用object-position(它们分别与background-size和非常相似background-position,用于背景图像)。
.my-image {
aspect-ratio: 5 / 3;
width: 100%;
/* Fill the available space, even if the
image has a different intrinsic aspect ratio */
object-fit: cover;
}
异步图像解码
此外,您还可以指定decoding="async"允许浏览器将图像解码操作移出主线程。MDN 建议对屏幕外图像使用此功能。
<img decoding="async" ... >
资源提示
最后一个更高级的选项是fetchpriority……。这可以帮助浏览器判断某个图像的优先级是否特别高,例如您的LCP 图像。
<img fetchpriority="high" ...>
或者,降低图片的优先级,例如,如果您有一些位于首屏但并不重要的图片,例如在轮播图的其他页面上的图片:
<div class="carousel">
<img class="slide-1" fetchpriority="high">
<img class="slide-2" fetchpriority="low">
<img class="slide-3" fetchpriority="low">
</div>
alt孩子们,请添加文字。
是的,替代文本对于可访问性和搜索引擎优化至关重要,不容忽视:
<img
alt="Builder.io drag and drop interface"
...
>
或者,对于纯粹用于展示的图像(例如抽象形状、颜色或渐变),您可以使用role属性将其明确标记为仅用于展示:
<img role="presentation" ... >
理解 sizes 属性
上述属性的一个重要注意事项 srcset 是,浏览器需要知道图像的渲染尺寸,以便选择最佳尺寸的图像进行获取。
也就是说,一旦图像渲染完成,浏览器就知道它的实际显示尺寸,将其乘以像素密度,然后在 srcset 中获取尺寸最接近的图像。
但对于 初始 页面加载,像 Chrome 这样的浏览器有一个预加载扫描器,它会在 HTML 中查找 img 标签,以便立即开始预取它们。
问题在于——这种情况甚至发生在页面渲染之前。例如,我们的 CSS 甚至还没有被加载,所以我们无法预知图片将如何显示以及尺寸大小。因此,浏览器不得不做出一些假设。
默认情况下,浏览器会假定所有图片都占据 100vw 整个页面宽度。这比它们实际的尺寸要大得多,甚至可能略大 一些 。因此,这种做法远非最佳。
这时,sizes 属性就派上用场了:
<img
srcset="..."
sizes="(max-width: 800px) 100vw, 50vw"
...
>
通过此属性,我们可以告诉浏览器在各种窗口大小下,我们的图像应该有多大(可以是精确的像素值 500px,例如 50vw 10 ...
上面的代码示例告诉浏览器,对于任何 800px 宽度不超过 100 毫米的屏幕,假设图像填充整个屏幕(100vw),对于任何其他(更大的)屏幕尺寸,假设图像填充屏幕的一半(50vw),并相应地进行预取。
因此,在这个例子中, 900px 宽屏不会被第一个子句()捕获(max-width: 800px),而是会匹配回退子句,该子句指定对于大屏幕,假定图像将以 宽度显示 50vw。由于 50vw * 900px = 450px 浏览器会针对 像素密度显示器 寻找450px 宽屏图像 , 针对像素密度显示器寻找宽屏图像 ,以此类推。然后,它会在 中查找最接近的匹配项 ,并将其用作预取图像。1x900px2xsrcset
我们可以根据需要添加任意数量的条款,例如:
<img
srcset="..."
sizes="(max-width: 400px) 200px, (max-width: 600px) 20vw, 50vw"
...
>
例如,在上面的例子中, 350px 宽屏会 200px 根据此子句获取与当前屏幕尺寸匹配的宽图像: (max-width: 400px) 200px。如果该屏幕的 2x 像素密度为,它仍然知道图像将以显示 200px,但会将该值乘以 2 并获取 400px 与此更高分辨率匹配的图像。
让我们回顾一下
哇,好的,信息量真大。我们来把它们整合起来。
这是一个图片加载速度极快的绝佳示例:
<picture>
<source
type="image/avif"
srcset="/image.avif?width=100 100w, /image.avif?width=200 200w, /image.avif?width=400 400w, /image.avif?width=800 800w" />
<source
type="image/webp"
srcset="/image.webp?width=100 100w, /image.webp?width=200 200w, /image.webp?width=400 400w, /image.webp?width=800 800w" />
<img
src="/image.png"
srcset="/image.png?width=100 100w, /image.png?width=200 200w, /image.png?width=400 400w, /image.png?width=800 800w"
sizes="(max-width: 800px) 100vw, 50vw"
style="width: 100%; aspect-ratio: 16/9"
loading="lazy"
decoding="async"
alt="Builder.io drag and drop interface"
/>
</picture>
上面的图片是一个很好的默认图片,最适合放在页面底部的图片。
对于优先级最高的图像,如果这是您绝对最重要的图像(例如 LCP 图像),则应删除loading="lazy"并decoding="async"考虑添加:fetchpriority="high"
style="width: 100%; aspect-ratio: 16/9"
- loading="lazy"
- decoding="async"
+ fetchpriority="high"
alt="Builder.io drag and drop interface"
使用图片作为背景
哦,对了,差点忘了我们这篇文章开头讨论的是最初的使用场景是背景图片。
虽然这里讨论的图像优化适用于你可能想使用的任何类型的图像(背景、前景等),但只需要少量 CSS(即一些绝对定位和 object-fit 属性)就img可以使图像表现得像一个……background-image
以下是一个简化的示例,您可以自己尝试一下:
<div class="container">
<picture class="bg-image">
<source type="image/webp" ...>
<img ...>
</picture>
<h1>I am on top of the image</h1>
</div>
<style>
.container { position: relative; }
h1 { position: relative; }
.bg-image { position: absolute; inset: 0; }
.bg-image img { width: 100%; height: 100%; object-fit: cover; }
</style>
使用这么多额外的 HTML 代码会对性能产生不利影响吗?
是也不是,但主要是不是。
人们很容易忘记图片的大小(以字节为单位)。在 HTML 代码中添加几个字节,就能加载更优化的图片版本,从而节省数千甚至数百万字节的空间。
其次,别忘了 gzip 压缩技术的存在。你为每张图片添加的额外标记很快就会变得非常冗余,而 gzip 正好可以帮你消除这些冗余。
因此,虽然 DOM 膨胀和有效负载大小肯定始终是一个需要考虑的问题,但我认为在这种情况下,权衡利弊是对你有利的。
更简便的方法
如今,你几乎不需要手动编写上面那些复杂的代码了。像NextJS和Qwik这样的框架,以及像Cloudinary和Builder.io这样的平台,都提供了图像组件,让这一切变得简单,看起来就像下面这样:
<!-- 😍 -->
<Image
src="/image.png"
alt="Builder.io drag and drop interface" />
这样一来,你就可以自动获得上述大部分(如果不是全部)优化(包括生成所有不同的图像尺寸和格式)。
结论
尽可能在 HTML 中使用img图片而不是 CSS 。使用懒加载、 `<img>`标签以及我们上面讨论的其他优化方法,以最佳方式呈现图片。注意区分高优先级图片和低优先级图片,并相应地调整图片属性。background-imagesrcsetpicture
或者,只需使用一个好的框架(如NextJS或Qwik)和/或好的平台(如Cloudinary或Builder.io),一切就迎刃而解了,这很简单。
关于我
嗨!我是 Steve, Builder.io的首席执行官。
我构建了我们的Image组件和图像优化 API,并花费了大量时间在数百个真实世界的网站和应用程序中对它们进行性能分析。
我们的平台提供了一种通过拖放组件的方式,以可视化的方式在现有网站或应用程序上创建页面和其他 CMS 内容。
它完全基于 API 驱动,并集成了所有现代框架。就是这样:
import { BuilderComponent, registerComponent } from '@builder.io/react'
import { Hero, Products } from './my-components'
// Dynamically render compositions of your components
export function MyPage({ json }) {
return <BuilderComponent content={json} />
}
// Use your components in the drag and drop editor
registerComponent(Hero)
registerComponent(Products)
它给你带来以下好处:
文章来源:https://dev.to/builderio/optimal-images-in-html-5bg9
