前端开发者学堂 - fedev.cn

给Web页面提供正确图像的姿势

发布于 大漠

在《探索Web上图片使用方式》一文中介绍了Web上图片的各种使用方式,从引入图片、图片效果处理、图片适配、图片加载以及图片优化几个方面介绍Web的图片使用方式。其中有一个值得我们去探讨的话题,那就是如何根据用户的设备为其提供正确的图片。那在这篇文章中我们就一起来聊聊这方面的技术。

提供正确图像的优势

随着科技的进一步发展,现在Web的展示不仅仅是在PC电脑上了,它需要面对更多的媒体终端,手机、电视以及其他智能屏幕等。而图像又是Web页面或Web程序必不可少的媒体元素之一。如果我们根据不同的终端设备能提供不同的图像,那会让我们在处理图像上省事情的多。

按照以前的方式,为所有终端提供的都是一张图片,所以我们面对这么多种不同的设备和屏幕,我们都要削尖了脑袋去想办法,怎么让图片能适合屏幕,而且还不失伪合度。面对这样的一个场景,社区很多人都在探讨响应式图像的处理方式。在W3cplus上也有不少这方面的文章,感兴趣的不仿了解一下。

而我们今天要探讨的是后一种方式。可以根据不同的终端加载不同的图像。这也意味着不需要依赖浏览器调整大小来在不同的屏幕上显示图像,也不需要将高分辨率的图像运用在低分辨率的设备上,这样做会浪费用户巨大的带宽。毕竟,一个320px的屏幕上不需要一个1920px的图像,即使是三倍的屏幕。

除此之外,这种方式其主要优势主要有:

  • 加载适当大小图像的文件,可以让带宽得到更充分的利用
  • 加载不同裁剪并具有不同纵横比的图像可以更好的适合不同宽度布局的变化
  • 加载更高的像素密图像,让图像可以显示的更为清晰

提供正确图的姿势

如果做过响应式设计的同学,难免会涉及到处理响应式图像,但处理响应式图像时,有很多问题需要解决。只不过在这篇文章中我们不再探讨这方面的问题,如果想把这个事情聊彻底,估计都可以写本书了。我们还是回到今天的主题上来:给你的用户提供正确的图像

简单地说,可以通过以下两种方式达到我们的诉求:

  • 使用<img>元素加载图像,使用该元素最新属性srcsetsize给用户加载正确的图像
  • 使用<picture>元素给用户加载正确的图像

看上去好像很简单一样,其实不然,这里面有很多概念需要理解。接下来我尝试着向大家介绍些。

物理像素 和 逻辑像素

物理像素(Physical pixels)又被称为设备像素。设备能控制显示的最小物理单位,显示器上一个个的点。大小固定,不可变。

众所周之,显示屏是由一个个物理像素点组成的,通过控制每个像素点的颜色,使屏幕显示出不同的图像,屏幕从工厂出来那天起,它上面的物理像素点就固定不变了,单位pt。其中每一个像素又由一些红色,绿色和蓝色的 “潜在像素” 组成。我们的肉眼无法看到 “潜在像素”,因为三个颜色被混合为一个单一 像素的颜色并呈现给我们,但是这些内容并不和程序员直接相关。下面两张图也许有助于你对像素有一个本质的认识:

逻辑像素(Logical pixels)又被称为设备独立像素或密度无关像素。可以认为是计算机坐标系统中的一个点,这个点代表一个可以由程序使用的虚拟像素(比如CSS像素),这个点是没有固定的,越小越清晰,然后由相关系统转换为物理像素。

我们可以把逻辑像素看成数据世界的像素,也可以说成一个电子图片的最小可寻址单元。比如我们平时常说的100px * 100px图像时,这里的像素指的就是逻辑像素。

需要注意的是,相对于物理像素而言逻辑像素没有固定的物理尺寸,也就是说除非我们指定显示条件,不然图像的物理尺寸是不确定的。

假设你有一台iPhone X的设备,其屏幕分辨率是1125px * 2436px。这意味着该设备的屏幕宽度是1125px,高度是2560px

而在实际使用的时候,下面的CSS代码也能匹配到iPhone X设备:

@media only screen and (min-width: 375px) and (max-width: 767px) { 
    /* Your Styles... */ 
}

这究竟是为什么呢?不知道大家有没有想过这个问题?

虽然iPhone X设备的物理像素是1125px * 2436px,但如果浏览器试图使用每一个像素在5.8英寸的屏幕上显示内容,内容就会小得看不见。如果我们把逻辑像素的概念套进来,应该会更易于理解。

逻辑像素 = 物理像素 / 设备像素比(DPR)

设备像素比(DPR)是由设备制造商定义。简单地说,它指的是一个逻辑像素中包含的物理像素的数量:

设备像素比 = 物理像素 / 逻辑像素

DPR为2的设备意味着一个逻辑像素包含42 x 2)个物理像素,同样的,要是DPR是为,那么表示一个逻辑像素等于93 x 3)个物理像素。假设我们有一个元素widthheight都是2px,那么在不同的DPR下对应的物理像素是不一样的,比如下图所示:

iPhone X设备的DPR是3。这也意味着它的逻辑分辨率是(1125 / 3)px * (2436 / 3)px,即375px * 812px。这就是为什么可以匹配上了。事实上呢,你在CSS中定义的像素,它是一个逻辑像素而不是一个物理像素。正因为这个原因,开发人员时间也把逻辑像素称为CSS像素。

时常在使用图片时为了区分DPR的不同,常会使用@2x@3x这样的方式来表达,这里的2x3x之类的指的就是DPR对应的值。

在调试页面的时候,我们在浏览器查看器中就可以查看对应设备的DPR。比如在Chrome浏览器下:

你也可以使用window.devicePixelRatio来获取设备的DPR。

有了对物理像素和逻辑像素的以及像素比DPR的理解,后面我们要聊的东西,涉及到的一些术语会让你更易于理解。

使用img加载正确的图像

为了更好的向大家展示如何给用户提供正确的图片资源,我们提供几个尺寸下的图片:

  • @1x屏加载的图片375px * 250px下载
  • @2x屏加载的图片750px * 494px (下载)
  • @3x屏加载的图片1125px * 750px下载

在HTML中,我们一般习惯用img标签来加载图片:

<img src="https://static.fedev.cn/sites/default/files/blogs/2018/1811/source-375@1x.jpeg" alt="source-375@1x" />

这是最基本的使用了,并不能达到我们的诉求。但不用急,img元素新增了两个属性,一个是srcset,另一个是sizes。现在我们使用img标签时,可以像下面这样使用:

<img 
    srcset="https://static.fedev.cn/sites/default/files/blogs/2018/1811/source-375@1x.jpeg 375w,
        https://static.fedev.cn/sites/default/files/blogs/2018/1811/source-375@2x.jpeg 750w,
        https://static.fedev.cn/sites/default/files/blogs/2018/1811/source-375@3x.jpeg 1125w"
    sizes="(max-width: 375px) 750px, 375px"
    src="https://static.fedev.cn/sites/default/files/blogs/2018/1811/source-375@1x.jpeg" alt="Load the required images"
/>

一眼看到上面这么一坨代码,估计有点蒙圈。但不要紧,随着往下看,就很快能明白上面代码是什么意思。

srcset属性

先来看srcset属性。srcset属性你指定多个图像以及每个图像的大小。每个逗号之前,我们写:

  • 图像文件名,比如source-375@1x.jpeg
  • 一个空格
  • 图像的固定宽度,比如475w

注意,这里使用的w单位,而不是我们常用的px。这个图像固定宽度指的是图像的真实大小。而这里的375w的意思是图片在Web页面上最宽到375以内。假设DPR为2的屏幕上,有一张图片指定他为375px,那么这张图实际的逻辑像素就会是375 * 2 = 750,所以会是750w

srcset="https://static.fedev.cn/sites/default/files/blogs/2018/1811/source-375@1x.jpeg 375w,
        https://static.fedev.cn/sites/default/files/blogs/2018/1811/source-375@2x.jpeg 750w,
        https://static.fedev.cn/sites/default/files/blogs/2018/1811/source-375@3x.jpeg 1125w"

比如示例中的代码,在DPR为1的屏幕,如果浏览器视窗不大于375px宽就会使用source-375@1x.jpeg,在DPR为2的时候就会加载source-375@2x.jpeg。比如在我的Mac Pro屏幕上,它的DPR值为2。所以当视窗宽度在小于265px时,加载的是source-375@1x.jpeg

接着改变浏览器视窗的宽度到266px(只要不超过459px),加载的图像是source-750@2x.jpeg

当视窗宽度大于460时,加载的是source-750@3x.jpeg

值得注意的是,如果使用了srcset来指定不同版本图片,但却没有显式的设置宽度,那么预设的宽度就会变成100w,即:视窗的100%宽度。所以要记得显式设置宽度。

srcset除了和w结合使用之外,还可以和x语法结合来选择适当分辨率的图片。上面的示例修改一下。

<img
        srcset="https://static.fedev.cn/sites/default/files/blogs/2018/1811/source-375@1x.jpeg 1x,
            https://static.fedev.cn/sites/default/files/blogs/2018/1811/source-375@2x.jpeg 2x,
            https://static.fedev.cn/sites/default/files/blogs/2018/1811/source-375@3x.jpeg 3x"
        
        src="https://static.fedev.cn/sites/default/files/blogs/2018/1811/source-375@1x.jpeg" alt="Load the required images"
    />

有一点需要注意,使用srcset时,xw不能混用,比如像下面这样:

<img
        srcset="https://static.fedev.cn/sites/default/files/blogs/2018/1811/source-375@1x.jpeg 1x,
            https://static.fedev.cn/sites/default/files/blogs/2018/1811/source-375@2x.jpeg 750w,
            https://static.fedev.cn/sites/default/files/blogs/2018/1811/source-375@3x.jpeg 3x"
        
        src="https://static.fedev.cn/sites/default/files/blogs/2018/1811/source-375@1x.jpeg" alt="Load the required images"
    />

如果一个img中使用了750w来指定图片大小,那么就必须统一使用w来表示。

sizes

img中使用srcset属性时,经常和sizes属性一起配合使用。这个sizes属性定义了一组媒体条件(例如屏幕宽度)并且指明当某些媒体条件为真时,什么样的图片是最佳选择。sizes的使用方式:

  • 一个媒体查询条件,比如示例中的(max-width: 375px),即表示视窗宽度不大于375px就满足条件
  • 一个空格
  • 当媒体查询条件为真时,图像将填充的槽的宽度,即示例中的375px

注意: 对于槽的宽度,你也许会提供一个固定值 (px, em) 或者是一个相对于视口的长度(vw),但不是%。你也许以及注意到最后一个槽的宽度是没有媒体条件的,它是默认的,当没有任何一个媒体条件为真时,它就会生效。 当浏览器成功匹配第一个媒体条件的时候,剩下所有的东西都会被忽略,所以要注意媒体条件的顺序。

回到我们的示例中来:

<img
    srcset="https://static.fedev.cn/sites/default/files/blogs/2018/1811/source-375@1x.jpeg 375w,
        https://static.fedev.cn/sites/default/files/blogs/2018/1811/source-375@2x.jpeg 750w,
        https://static.fedev.cn/sites/default/files/blogs/2018/1811/source-375@3x.jpeg 1125w"
    sizes="(max-width: 375px) 750w, 375px"
    src="https://static.fedev.cn/sites/default/files/blogs/2018/1811/source-375@1x.jpeg" alt="Load the required images"
/>

这里img元素设置的sizes值为(max-width: 375px) 750px, 375px,其表示当视窗宽度不大于375px时,图片的宽度限制为750px,其他情况下,使用375px

比如我们在调试器中,自定义了一个尺寸:

由于视窗宽度小于375px,而且DPR为1。此时图片计算的是source-375@1x.jpeg,如果把DPR换成2,其加载的图片就是source-375@2x,视窗大于375px时,加载的图片是souce-375@3x.jpeg

简单的总结一下

如果要让浏览器根据srcset提供的值选择正确的图像,它需要具备:

  • 知道浏览器视窗尺寸和设备像素比(DPR)
  • 检查size中定义的媒体条件,确保第一个是符合条件
  • 在媒体查询中检查分配给图像的大小
  • srcset中定义的变量中加载最适合的图像

使用picture加载正确图像

<picture>是HTML5一个新的元素。

如果<picture>元素与当前的<audio><video>元素协同合作将大大增强响应式图像的工作进程。它允许你放置多个source标签,以指定不同的图像文件名,进而根据不同的条件进行加载。

它可以让你根据以下条件加载完全不同的图像:

  • 媒体特性结果如:视口的当前高度(viewport height),宽度(width),方向(orientation)
  • 像素密度

其工作步骤如下:

  • 创建<picture></picture>标签。
  • 在这些标签内创建一个你想用来执行任何一个特性的<source>元素。
  • 添加一个media属性,用来包含你想要的特性,如视口的当前高度(viewport height),宽度(width),方向(orientation)等。
  • 添加一个srcset属性与相应的图像文件名相匹配,进行加载。如果你想提供不同的像素密度,例如Retina显示屏,你可以添加额外的文件名到你的srcset属性中,
  • 添加一个回退的<img>元素。

来看一个简单的示例:

<picture>
    <source
        srcset="sample_image-480.webp 480w,
        sample_image-768.webp 768w,
        sample_image-1024.webp 1024w"
        sizes="(max-width: 480px) 480px,
        (min-wdith: 800px) 750px,
        1024px" type="image/webp">
    <source srcset="sample_image-480.jpg 480w,
        sample_image-768.jpg 768w,
        sample_image-1024.jpg 1024w"
        sizes="(max-width: 480px) 480px,
        (min-wdith: 800px) 750px,
        1024px"
        type="image/jpg">
    <img src="sample_image-1024.jpg" alt="">
</picture>

<source>元素包含一个media属性,这一属性包含一个媒体条件,就像img中的srcset例子,这些条件来决定哪张图片会显示——第一个条件返回真,那么就会显示这张图片。在这种情况下,如果视窗的宽度为480px或更少,第一个<source>元素的图片就会显示。如果视窗的宽度是800px或更大,就显示第二张图片。

srcset属性包含要显示图片的路径。请注意,正如我们在<img>上面看到的那样,<source>可以使用引用多个图像的srcset属性,还有sizes属性。所以你可以通过一个 <picture>元素提供多个图片,不过也可以给每个图片提供多分辨率的图片。实际上,你可能不想经常做这样的事情。

在任何情况下,你都必须在 </picture>之前正确提供一个<img>元素以及它的srcalt属性,否则不会有图片显示。当媒体条件都不返回真的时候(你可以在这个例子中删除第二个<source> 元素),它会提供图片;如果浏览器不支持 <picture>元素时,它可以作为后备方案。

上面示例提供了两个source,第一个引入的是webp图片格式,浏览器支持webp格式的图片就会加载该格式的图片资源,如果不支持则会加载jpg图片。

有关于picture更详细的介绍,可以阅读《如何使用 HTML5 的picture元素处理响应式图片》一文。

艺术方向

一个改进的方法是,当网站在狭窄的屏幕上观看时,显示一幅图片的包含了重要细节的裁剪版本,第二个被裁剪的图片会在像平板电脑这样的中等宽度的屏幕设备上显示,这就是众所周知的艺术方向问题(Art Direction Problem)。

艺术方向问题涉及要更改显示的图像以适应不同的图像显示尺寸。例如,如果在桌面浏览器上的一个网站上显示一张大的、横向的照片,照片中央有个人,然后当在移动端浏览器上浏览这个网站时,照片会缩小,这时照片上的人会变得非常小,看起来会很糟糕。这种情况可能在移动端显示一个更小的肖像图会更好,这样人物的大小看起来更合适。<picture>元素允许我们这样实现。

这样的代码允许我们在宽屏和窄屏上都能显示合适的图片,像下面展示的一样:

总结

根据用户终端加载正确的图像是一种新的加载图像技术,这种技术可以更好的帮助你创建响应式图像,除此之外,这种方式还可以帮助你节省带宽,给用户提供最佳的体验。不管是img还是picture,他们都可以让你按需加载图片。特别是可以将sourcetype、媒体查询、srcsetsizes相结合。感兴趣的同学,不仿自己亲自体验一吧,如果有更好的建议,欢迎在下面的评论中分享你的案例和经验。 Air Max 90 YEEZY 2 SP