响应式图片使用指南(Part4)

发布于 大漠

前面我们花了较多的篇幅围绕着 HTML 的 <img> <picture> 元素是如何构建响应式图片。在 <img> 标签元素上可以使用 srcsetsizes 属性大不同环境下提供不同尺寸的图片;在 <picture> 中使用 <source> ,并且结合mediasrcsetsizestype等属性可以为用户提供不同图片(环境不同提供不同的图片)和不同类型图片(type指定图片类型)等。但在 Web 开发中,很多时候还会使用背景图片(background-image)、边框图片(border-image)和蒙板图片(mask-image)等。

我们接下来围绕着 CSS 中的背景图片(background-image)来展开,换句话说,在background-image中是否也可以像 <img><picture> 一样,根据用户环境来提供不同的图片。

使用媒体查询加载不同的背景图片

我想大家首先想到的是 CSS 媒体查询。比如:

/* 默认的非 Retina 屏幕 */
.hero { 
    background-image: url("/hero970.jpg"); 
}

@media only screen and (max-width: 320px) {
    /* 小屏幕且非 Retina 屏幕 */
    .hero { 
        background-image: url("/hero290.jpg"); 
    }
}
@media  only screen and (min-resolution: 2dppx) and (max-width: 320px) {
    /* 小的 Retina 屏幕 */
    .hero { 
        background-image: url("/hero290@2x.jpg"); 
    }
}
@media only screen and (min-width: 321px) and (max-width: 538px) {
    /* 中等屏幕且非 Retina 屏幕 */
    .hero { 
        background-image: url("/hero538.jpg"); 
    }
}
@media only screen and (min-resolution: 2dppx) and (min-width: 321px) and (max-width: 538px) {
    /* 中等屏幕且Retina屏幕 */
    .hero { 
        background-image: url("/hero538@2x.jpg"); 
    }
}
@media only screen and (min-resolution: 2dppx) and (min-width: 539px) {
    /* 大屏幕且Retina屏幕 */
    .hero { 
        background-image: url("/hero970@2x.jpg"); 
    }
}

除了根据媒体查询断点和设备DPR给background-image设置不同的图片源之外,也可以根据用户偏好的查询来加载不同的背景图片,比如:

.hero {
    background-image: url(./hero-light.png);
}

@media (prefers-color-scheme: dark) { 
    .hero {
        background-image: url(./hero-dark.png);
    }
}

.ani {
    background-image: url(./animation.gif);
}

@media (prefers-reduced-motion: no-preference) { 
    .ani {
        background-image: url(./animation.jpg);
    }
}

.image {
    background-image: url(image/heavy.jpg)
}

@media (prefers-reduced-data: reduce) {
    .image {
        background-image: url(image/light.jpg)
    }
}

虽然使用 CSS 媒体查询在不同的环境为 background-image 引入不同的图片源能满足一定的需求,但无法像 <img><picture> 标签元素那样的灵活。除此之外,也会让代码变得非常地复杂和冗余,并且增加图片的制作成本。也正因为这些因素,才会有 <img>srcsetsizes以及<picture>的相关规范和特性的出现。

那么,在CSS的背景图片的使用呢?有没有更好的方式呢?答案是肯定的,那就是 CSS函数中的 image-set()函数。

image-set()

image-set() 函数早在 2012年 Chrome 就支持,Safari 从第六版本开始支持,最近 Firefox 88 也开支持了该函数。简单地说:

使用image-set()函数可以在background-image中为同一图像提供多种分辨率的图片来源

这相当于 <img>srcset提供不同分辩率图片源的使用方式:

<img 
    src="srcset-x1.png" 
    srcset=" 
        srcset-x1.png 1x, 
        srcset-x2.png 2x, 
        srcset-x3.png 3x" 
    alt="srcset for img" 
/>

background-image使用image-set()可以像下面这样使用:

.hero {
    background-image: image-set(
        'hero@1x.png' 1x,
        'hero@2x.png' 2x,
        'hero@3x.png' 3x
    )
}

其中 1x 用于识别低分辨率图像,2x则用于定义高分辨率图像。x是DPPX的别名,代表每像素单位的点数。

到目前为止,在 Chrome、Edge 等浏览器需要添加 -webkit-前缀。在Safari 浏览器中不再需要前缀,不过需要采用旧的语法,要使用 url()来指定图片路径。不过为了让不支持image-set()也能像用户呈现背景图片,还是不能缺少background-image

.hero {
    /* 不支持image-set()时采用 */
    background-image: url('hero.png');

    /* image-set()老语法, Safari */
    background-image: -webkit-image-set(
        url('hero@1x.png') 1x,
        url('hero@2x.png') 2x,
        url('hero@3x.png') 3x
    )

    /* image-set() 标准语法 */
    background-image: image-set(
        'hero@1x.png' 1x,
        'hero@2x.png' 2x,
        'hero@3x.png' 3x
    )
}

设备的 DPR 不同时,加载图片资源将不同:

我们还可以使用 PostCSS的插件,让 image-set() 函数使用与分辨率有关的图像,遵循CSS图像规范。即,使用 x 描述符之外,还可以使用 dpi 描述符:

.example {
    background-image: image-set(
        url(img.png) 1x,
        url(img@2x.png) 2x,
        url(img@print.png) 600dpi
    );
}

编译出来的CSS将使用媒体查询来描述:

.example {
    background-image: url(img.png);
}

@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
    .example {
        background-image: url(img@2x.png);
    }
}


@media (-webkit-min-device-pixel-ratio: 6.25), (min-resolution: 600dpi) {
    .example {
        background-image: url(my@print.png);
    }
}

现在,使用高端设备的用户将看到一个超级清晰的图像,但这并不代表着就是满足用户需求的。即使是使用高端设备的用户,他在网络连接慢,或者想节约数据流量的时候,也不一定希望加载高清的图像。像上面这样使用x描述符,在image-set()加载图像,将会强制用户在所有环境之下加载高清图像。让用户将花费更多的流量,在网络连接慢的情况之下,会更慢加载图像。这对于用户的体验来说,是不爽的。

前面说过,在 <picture><source> 元素上,可以使用 type 来加载指定图片的格式类型。这样,我们可以在支持高级图片格式的设备中更快加载图片,呈现更清晰的图片,并且还能节约用户的数据流量。比如:

<picture>
    <source srcset="photo.jxl" type="image/jxl">
    <source srcset="photo.avif" type="image/avif">
    <source srcset="photo.webp" type="image/webp">
    <source srcset="photo.jpg" type="image/jpeg">
    <img src="photo.jpg" alt="" />
</picture>

值得庆幸的是,CSS 的 image-set() 函数的新语法,新增了对 type() 函数的支持。即,在使用 image-set() 函数时,可以使用 type()函数指定图片源的格式,比如:

.photo {
    background-image: url("./photo.jpg");
    background-image: image-set(
        "./photo.jxl" type("image/jxl"),
        "./photo.avif" type("image/avif"),
        "./photo.webp" type("image/webp"),
        "./photo.jpg" type("image/jpg")
    );
}

image-set() 中的 type() 函数还可以和 x 描述符同时使用,比如:

.puppy {
    background-image: image-set( 
        "puppy.webp" type("image/webp") 1x,
        "puppy2x.webp" type("image/webp") 2x,
        "puppy.png" type("image/png") 1x,
        "puppy2x.png" type("image/png") 2x
    );
}

为什么使用 image-set(),而不使用媒体查询

在 CSS 中使用图片,为什么建议使用 image-set() 函数,而不使用媒体查询。主要因素是:

  • image-set() 函数不像 媒体查询,image-set() 函数不需要告诉浏览器使用什么图片,而是直接提供了图片源让浏览器选择。它可以在使用Retina设备浏览网页,但网速慢,告诉设备采用低分辨率的图片,甚至你还能根据网速智能的选择需要的图片
  • 使用媒体查询的问题是,在高分辨率的设备中,浏览器他没有选择的权利。也就是说,设备 DPR 为x时,必须加载指定的图片
  • image-set() 的好处是,在支持image-set()的浏览器会在高分辨率下匹配需要的图片,而不需要做额外的事情
  • image-set()能让各种不同分辨率、不同格式的图片都显示在 CSS 中的同一个地方;而使用媒体查询显示不同图片时,可能会在CSS不同位置

选择正确的方式

到目前为止,在 Web 开发中构建响应图片,我们有多种方式:

  • 使用 <img> 和它的 sizessrcset 属性,加载不同尺寸的图片,并且可以针对用户的使用环境,指定图片呈现时的尺寸
  • 使用 <picture> ,在<source> 标签元素上使用 srcsetsizesmediatype 加载不同图片,并且可以针对用户的使用环境,加载不同图片
  • 使用 image-set() 函数在 CSS 中像 <img><picture> 中类似,使用不同图片

也就是说,我们现在有很多技术可以用来显示不同屏幕尺寸和分辨的图片。我们在实际使用的时候应该怎么选择呢?如果你不好做出选择,可以尝试使用下图的流程来做选择:

小结

加上 image-set() 的使用,有关于 Web 中响应式图片使用指南就全了,总共花了四个部分来阐述:

  • 第一部分:在这个部分主要和大家一起探讨了和响应式设计相关的概念和术语。这些术语和概念对大家理解响应式设计以及接下来响应式图片使用有较大的帮助
  • 第二部分:在这个部分主要和大家一起探讨了 HTML 的 <img> 元素的 srcsetsizes属性。使用这两种属性,我们可以为用户提供不同尺寸的图片,浏览器会根据相应的条件,加载最为合适的图片源
  • 第三部分:在这个部分主要和大家一起探讨了 HTML 的 <picture>元素,在 <picture>的中可以使用<source><img>子元素,在<source>元素上,可以使用 srcsetsizesmediatype 等属性。可以加载不同图片源,裁剪的图片、不同格式的图片等
  • 第四部分:在这个部分主要和大家一起探讨的 CSS 中如何使用 image-set()type() 函数,在 CSS 中实现类似 <img><picture> 相似的效果

在 Web 中使用图片,除了响应式相关的技能之外,还会涉及其他方面,如果你对这方面技术感兴趣的话,还可以阅读: