开篇
确保图片在所有屏幕尺寸上都能良好显示是一项困难的任务,因为你需要考虑图片的大小、图片的放置位置、显示图片的比例、用户连接的速度等等众多因素。结果是,大多数开发者只会为所有屏幕尺寸使用同一张图片,并让浏览器调整图片的大小以适应屏幕。这是一种不好的做法,因为浏览器仍会下载完整尺寸的图片(通常非常大),即使它只以其一部分尺寸显示。这会浪费用户的带宽,并且会显著减慢页面加载速度(尤其是在较慢的连接下)。
解决这个问题的方法是使用响应式图片。响应式图片是根据用户的屏幕尺寸进行优化的图片。这意味着图片将以适合用户设备的正确尺寸和质量进行下载。这将显著减少传输给用户的数据量,加快页面加载速度。有许多实现响应式图片的方法,从简单到复杂。在本文中,我将向您展示如何在您的网站上呈现响应式图片的所有方式。
img srcset 属性
到目前为止,实现响应式图片最简单的方法是在img标签上使用srcset属性。该属性允许您定义多个不同尺寸的图片,然后浏览器将自动选择最适合用户屏幕尺寸的图片。
<img
src="tree-1200.jpg"
alt="A tree"
srcset="tree-400.jpg 400w, tree-800.jpg 800w, tree-1200.jpg 1200w"
/>
这段代码可能看起来有些混乱,所以让我详细解释一下其中发生的事情。首先,我们有了您已经熟悉的普通src和alt属性,它们与所有图片一样。在极少数情况下,如果用户使用的浏览器不支持srcset,那么将使用src URL来显示图片。不过,这种情况非常罕见,因为srcset在所有主要浏览器中已经支持了5-10年。
让人困惑的是srcset属性。该属性接受一个逗号分隔的图片URL和它们的宽度列表。如果我们看一下列表中的第一项tree-400.jpg 400w,可以看到URL是tree-400.jpg。这个URL的名称并不重要,但通常当您在不同尺寸上有多个相同的图片时,您会希望在名称中加上尺寸信息。
这项内容的第二部分是400w。这可能会让人困惑,因为w不是CSS单位,实际上w代表图像的实际宽度,以像素为单位。您可以通过在文件浏览器/资源管理器中检查图像来轻松找到这个宽度。如果您使用的是Windows操作系统,可以右键单击图像,选择属性,而在Mac上应该有一个名为"获取信息"的选项。在本例中,图像的宽度为400像素,因此我们将宽度设置为400w。
浏览器将使用这些信息来自动确定要下载的图片。例如,如果用户的屏幕宽度小于400像素,它将使用tree-400.jpg图像,因为这是可以在不进行任何拉伸/模糊像素的情况下使用的最小图像。一旦浏览器的宽度大于400像素,浏览器将切换到使用tree-800.jpg图像。这是因为400像素的图像现在比当前屏幕尺寸小,如果使用它会被拉伸/模糊。对于所有屏幕尺寸,这个过程会一直持续,直到浏览器达到列表中最大的图像。
这很棒,因为现在在小屏幕上,浏览器将下载一个较小的图像,而大屏幕仍将获得高分辨率的图像。这将显著减少传输给用户的数据量,并加快页面加载速度。下面是一个示例,展示了这种情况。尝试将浏览器大小调整为较小的尺寸,然后重新加载页面,您将看到下载了较小的图像。
<img
style="width: 100%; border-radius: 1rem;"
src="https://placehold.co/3200x800/png"
srcset="
https://placehold.co/800x200/png 800w,
https://placehold.co/1600x400/png 1600w,
https://placehold.co/3200x800/png 3200w
"
/>
在进行测试时,您可能会注意到下载的图像实际上比您预期的要大。例如,如果您的屏幕宽度为700像素,您的浏览器可能仍会下载1600像素宽的图像,而不是800像素宽的图像。这是因为浏览器还考虑了您设备的像素密度。如果您使用的是高分辨率设备或浏览器缩放级别较高,浏览器将下载一个较大的图像,以确保在您的屏幕上显示良好,因为每个CSS像素实际上对应屏幕上的多个像素。要检查设备的像素密度,您可以在控制台中使用window.devicePixelRatio。
如何处理不同的像素密度
有时候,您可能有一张图像在屏幕上始终保持相同的尺寸,但您希望它在高分辨率设备上看起来很好。例如,如果您的标志始终为100像素宽,在只提供100像素宽图像的情况下,在高分辨率设备上会显得模糊不清。为了解决这个问题,您可以使用srcset属性,通过使用x单位来表示像素密度来提供多个不同尺寸的图像。
<img
src="logo-200.jpg"
alt="Our Logo"
srcset="logo-100.jpg 1x, logo-150.jpg 1.5x, logo-200.jpg 2x"
/>
上述代码与我们之前的srcset示例非常相似,但主要区别在于我们使用了类似1.5x和2x的单位,而不是硬编码的像素值。这些单位指的是屏幕的像素密度。例如,如果某人的屏幕具有每个CSS像素1.25个设备像素的像素密度,则将使用logo-150.jpg图像,因为这是可以在不拉伸/模糊像素的情况下使用的最小图像。
您无需包含1x单位,因为它是默认值。如果您只有两个图像,您可以使用logo-100.jpg,logo-200.jpg 2x,而不是logo-100.jpg 1x,logo-200.jpg 2x。
img sizes 属性
到目前为止,我们介绍的是实现响应式图片的最基本方法,但在许多情况下,您的图像尺寸实际上并不等于屏幕的宽度。本博客就是一个很好的例子。在小屏幕上,我的博客内容(包括图像)占据了整个屏幕的宽度,但在较大屏幕上,我将内容居中显示,并设置了一个有限的最大宽度。如果我们仅使用像上面那样的srcset,我们的图像将根据浏览器窗口的完整尺寸进行缩放,这将导致在大屏幕上图像比实际需要的要大。这就是sizes属性的用途。
sizes属性允许您定义图像的单个尺寸,例如50vw,或者一组媒体查询,用于确定图像应该使用的尺寸。默认情况下,如果您没有将sizes属性添加到img标签中,它会假定尺寸为100vw,这就是为什么上面的图像根据浏览器窗口的完整宽度进行缩放。让我们看一下如何使用sizes属性来考虑具有最大尺寸的博客这样的情况。
<img
src="tree-1200.jpg"
alt="A tree"
srcset="tree-400.jpg 400w, tree-800.jpg 800w, tree-1200.jpg 1200w"
sizes="(max-width: 800px) 100vw, 800px"
/>
上面的代码与之前的代码完全相同,只是我们添加了sizes属性。sizes属性接受一个以逗号分隔的媒体查询和尺寸列表。为了理解其中的内容,让我们逐个解析列表中的每个项。
我们的第一个项(max-width: 800px)100vw 有两个部分。第一部分是我们要检查的媒体查询。在这种情况下,我们要检查屏幕宽度是否小于800像素。第二部分是如果媒体查询为true时我们要使用的尺寸。在这种情况下,我们使用100vw,这意味着我们希望浏览器根据浏览器窗口的完整宽度选择图像尺寸。
第二个项800px没有媒体查询,而只是一个尺寸。这被视为我们的回退尺寸。如果之前定义的所有媒体查询都为false,那么它将使用这个回退尺寸。从本质上讲,您可以将其视为始终为true的媒体查询。我们通过这个项表达的意思是,假设我们的图像在屏幕上占据了800像素,我们应该选择我们的图像。然后,浏览器将使用这个尺寸来确定要下载的图像。如果您的浏览器具有高分辨率或您在页面上进行了缩放,它可能会下载比800像素更大的图像,但通常情况下,这是确保图像不会过大的一种好方法。
将这两个项组合起来,基本上是在说我们的图像应该根据浏览器的宽度选择,在800像素之前。在那一点上,图像在我们的屏幕上永远不会占用超过800像素的空间,所以我们应该根据这个800像素的尺寸来调整我们的图像尺寸。这是我为这个博客添加响应式图像的代码方式,因为我的博客在较大的屏幕尺寸上受到最大宽度的限制。让我们看一个实际的示例。
<img
style="width: 100%; border-radius: 1rem;"
src="https://placehold.co/3200x800/png"
srcset="
https://placehold.co/400x100/png 400w,
https://placehold.co/800x200/png 800w,
https://placehold.co/1200x300/png 1200w,
https://placehold.co/1600x400/png 1600w,
https://placehold.co/3200x800/png 3200w
"
sizes="(max-width: 800px) 100vw, 800px"
/>
我添加了许多不同的图像尺寸,这样您就可以看到它们如何与不同的像素密度配合工作。如果您使用的是高分辨率设备,您可能会注意到浏览器下载了比800像素更大的图像。您还可以通过缩放设备来模拟此过程,因为您的设备缩放得越多,像素密度就越高,如果您缩放足够多,浏览器将需要下载更高分辨率的图像,以确保在屏幕上显示良好。
潜在的陷阱
sizes属性非常强大,但在使用它时需要注意以下几点。
顺序很重要
如果您的sizes属性中有多个媒体查询,将选择第一个为true的媒体查询对应的图像。这意味着您的媒体查询的顺序很重要。
<img
src="https://placehold.co/3200x800/png"
srcset="
https://placehold.co/400x100/png 400w,
https://placehold.co/800x200/png 800w
"
sizes="(max-width: 800px) 100vw, (max-width: 500px) 50vw, 1200px"
/>
如果您按照上述的方式编写sizes属性,那么您的代码将无法按预期工作。原因是第一个媒体查询(max-width: 800px)100vw 在所有小于800像素的屏幕尺寸下都为真。这意味着第二个媒体查询(max-width: 500px)50vw 将永远不会被使用,因为只有在屏幕小于500像素时才为真,而在这些尺寸范围内第一个媒体查询将始终为真,因此它将始终被优先选择。为了解决这个问题,您需要重新排序媒体查询,使最具体的媒体查询排在最前面,最不具体的媒体查询排在最后。
<img
src="https://placehold.co/3200x800/png"
srcset="
https://placehold.co/400x100/png 400w,
https://placehold.co/800x200/png 800w
"
sizes="(max-width: 500px) 50vw, (max-width: 800px) 100vw, 1200px"
/>
同样重要的是,确保您的默认尺寸(即没有媒体查询的尺寸)始终放在最后,因为它总是为真,所以如果它排在最前面,它将始终被选择,而不考虑其他媒体查询。
使用百分比
到目前为止,我已经向您展示了如何使用像px这样的具体尺寸,以及如何使用基于浏览器窗口的尺寸,比如vw,但是百分比尺寸(如50%)该怎么办呢?不幸的是,在sizes属性中不支持百分比尺寸。原因是浏览器在不知道父元素的宽度之前,无法确定百分比定义的内容的宽度。这意味着浏览器必须等到整个页面加载完成后才能确定要下载哪个图像。这将是一个糟糕的用户体验,因为用户必须等到整个页面加载完成才能看到任何图像。
picture 元素
到目前为止,我们主要讨论了如何以不同尺寸渲染相同的图像,以帮助提高加载时间,但这并没有涵盖在不同屏幕尺寸下显示不同图像的情况。例如,如果您的页面有一个宽度跨越整个页面的大标题,您可能希望在移动设备和桌面设备上显示不同的图像,因为您可以在桌面设备上使用更多细节的图像。这就是picture元素的用途。
picture元素允许您定义多个source元素,用于在不同的屏幕尺寸下定义要使用的不同图像。然后,浏览器将选择与当前屏幕尺寸匹配的第一个source元素,并使用该图像。如果没有任何source元素与当前屏幕尺寸匹配,则将使用picture元素中定义的img作为备用图像。
<picture>
<source media="(max-width: 500px)" srcset="hiking-narrow.jpg" />
<img src="hiking-wide.jpg" alt="Someone jumping on a hike" />
</picture>
如果你调整浏览器的大小,你应该会看到图像在两个不同版本之间变化。如果你使用的是移动设备,你可能需要缩放来观察图像的变化。我们为较小的屏幕尺寸提供了更裁剪的图像版本,因为在较小的屏幕上,图像的焦点——人物——会变得太小。
现在让我们看一下实际的代码,了解它是如何工作的。为了让picture元素起作用,你至少需要将一个普通的img标签放在picture元素的最后。
<picture>
<img src="hiking-wide.jpg" alt="Someone jumping on a hike" />
</picture>
这样做将只是像普通的img标签一样渲染图像。在这里,更加高级的是使用source元素。除了默认版本之外,每个图像的变体都应该有自己的source元素。在我们的例子中,我们只有一个source元素,但您可以根据需要添加任意数量的source元素。
<picture>
<source media="(max-width: 500px)" srcset="hiking-narrow.jpg" />
<source media="(max-width: 1000px)" srcset="hiking-medium.jpg" />
<img src="hiking-wide.jpg" alt="Someone jumping on a hike" />
</picture>
在每个source元素内部,您有两个主要属性。srcset属性的工作方式与img标签的srcset属性相同。这意味着,如果我们有hiking-narrow.jpg图像的多个分辨率,我们可以将它们包含在srcset属性中。不过,在使用picture元素时,每个source元素通常只有一个分辨率,因此您可以将其作为srcset属性中的唯一URL。
另一个属性是media属性。它的工作方式类似于sizes属性中的媒体查询,但是在source元素的media属性中,您只能定义一个媒体查询。这些查询与sizes属性一样,从上到下逐个检查,只有第一个匹配的媒体查询会被使用。如果没有任何媒体查询匹配,则使用img标签作为备选项,这也是为什么我们没有针对较大屏幕尺寸专门设置source元素的原因。
为什么要使用picture元素而不是其他替代方案
对于picture元素的一个大误解是,为什么要使用它而不是img元素的sizes属性或CSS。
为什么 sizes 不适合
sizes属性不适合此任务的主要原因是,picture元素始终会切换到与当前屏幕尺寸匹配的source元素中定义的图像。这意味着,如果您通过缩放或调整窗口大小来更改屏幕尺寸,它将切换到正确的图像。
sizes属性的工作方式类似,但只适用于增大屏幕尺寸的情况。如果您的屏幕尺寸缩小,浏览器将不会切换或下载较小的图像,因为它已经有了较大的图像,因此将继续渲染该图像。这非常好,因为它可以节省带宽,因为当您已经拥有较大的图像时,下载较小的图像没有意义。但是,当您希望在不同的屏幕尺寸上显示不同的图像时,这可能会成为一个问题,这就是为什么picture元素是理想的选择。
为什么 CSS 不适合
如果您熟悉CSS,您可能会意识到我们可以通过使用一些简单的CSS属性来实现非常类似的效果。
img {
object-fit: cover;
object-position: center;
}
这样做将使图像填充父元素的整个宽度,然后裁剪图像,以确保图像的中心始终可见。这将给我们非常相似的效果,但缺点是即使在小屏幕尺寸下我们只显示图像的一部分,仍然需要下载完整分辨率的图像。这与我们使用响应式图像所要实现的目标背道而驰。
结论
响应式图像可能看起来是一个复杂的话题,但实际上并不需要如此。实现基本的响应式图像只需在img标签中添加srcset属性,然后让浏览器完成其余工作。如果您想进一步深入,您可以使用sizes属性来帮助浏览器选择正确的图像,或者如果您想在不同的屏幕尺寸上显示不同的图像,可以使用picture元素。
由于文章内容篇幅有限,今天的内容就分享到这里,文章结尾,我想提醒您,文章的创作不易,如果您喜欢我的分享,请别忘了点赞和转发,让更多有需要的人看到。同时,如果您想获取更多前端技术的知识,欢迎关注我,您的支持将是我分享最大的动力。我会持续输出更多内容,敬请期待。
原文:
https://blog.webdevsimplified.com/2023-05/responsive-images/非直接翻译,有自行改编和添加部分,翻译水平有限,难免有疏漏,欢迎指正