现代 CSS

图解CSS: CSS 背景(Part1)

特别声明:如果您喜欢小站的内容,可以点击申请会员进行全站阅读。如果您对付费阅读有任何建议或想法,欢迎发送邮件至: airenliao@gmail.com!或添加QQ:874472854(^_^)

背景(background)是 CSS 中最常见也是最基础的一个属性,它自 CSS 1.0 版本就开始有了。最早的时候我们可以通过 background 给一个元素盒子的背景层设置背景颜色(background-color)、背景图像(background-image)。如果是背景图像的话,还可以调整背景图像的位置(background-position)、设置背景图像的平铺方式(background-repeat)以及背景图像在背景层的依附模式(background-attachment)。不过,随着 CSS 技术不断向前发展,CSS 为开发者提供了一些新特性用来控制背景图像,比如背景图像的大小(background-size)、背景图像的剪切(background-clip)、背景图像的定位区域(background-origin)以及给同一个元素盒子设置多个背景。除此之外,CSS 还为 background 的一些老属性新增了功能,比如 background-position可以指定四个值,通过方向设置背景图像的位置,background-repeat 多了几种不同的平铺方式。另外,CSS 还可以使用 background-blend-mode 属性来设置不同的混合模式,从而改变背景图像的效果。

当然,你可能对这些属性的基本使用有一定的了解,但其中有一些细节是不为人之的,接下来,我将从基础使用开始和大家聊聊CSS中背景方面的知识,希望能给大家带来不一样的收获!如果你感兴趣的话,那请继续往下阅读。

背景层

在具体聊 CSS 的背景(background)之前,先简单的来聊一下元素盒子的背景层。众所周之,CSS 会视每一个元素为一个盒子。每个盒子都有不同的上下文格式(视觉格式化模型,可以使用 display 属性来改变视觉模型)和大小(CSS 盒模型中的相关属性将会影响盒子的大小)。其实,每个盒子都有着相同的层,即 内容层content-box)、内距层padding-box)、边框层border-box)、背景层background-box)和 阴影层shadow-box):

背景层是完全位于边框层之下的,它从其padding-box区域开始呈现在框的内容后面。这使背景层根本不与边界重叠。而我们今天要聊的 background 相关的属性就是用来美化背景层的。

背景简介

CSS 的 background 属性是隶属于 CSS Backgrounds and Borders Module Level 3 模块中的一部分,它的使用很简单:

.element {
    background: [<bg-layer># ,]? <final-bg-layer>
}

其中 <bg-layer><final-bg-layer> 分别是:

<bg-layer> = <bg-image> || <bg-position> [ / <bg-size> ]? || <repeat-style> || <attachment> || <box> || <box>
<final-bg-layer> =  <'background-color'> || <bg-image> || <bg-position> [ / <bg-size> ]? || <repeat-style> || <attachment> || <box> || <box>

即:

<background-color> = background-color
<bg-image> = background-image
<bg-position> = background-position
<bg-size> = background-size
<repeat-style> = background-repeat
<attachment> = background-attachment
<box> = background-origin
<box> = background-clip

也就是说,backgroundbackground-colorbackground-imagebackground-positionbackground-sizebackground-repeatbackground-attachmentbackground-originbackground-clip 属性的简写。比如下面这个示例:

.box {
    background: #fff url("avatar.svg") left top / 160px 160px no-repeat;
}

背景图像位于元素盒子的左上角,而且该背景图像的大小是 160px x 160px

使用简写属性时需要有几个细节需要注意:

  • <bg-position><bg-size> 之间需要使用斜杠分隔符 /分隔开,并且要严格遵守 <bg-position> / <bg-size>方式,否则会让 background 失效
  • 如果background中同时有两个<box>,第一个则是 background-origin,第二个则是 background-clip;如果只有一个<box>,表示background-originbackground-clip 取相同的值

在 CSS 中给定一个有效的 background 声明时,对于每一层,简写首先将 background-imagebackground-positionbackground-sizebackground-repeatbackground-originbackground-clipbackground-attachment 的相应层设置为该属性的初始值,然后为声明中为该层指定的显式值赋值。最后,background-color 设置为指定的颜色(如果有的话),否则设置为初始值(transparent)。

另外还可以给 background 中使用逗号分隔符, 来给元素盒子设置多个背景层,每个背景层都可以设置 <bg-layer>,这样设置时,也被称为多背景。

.element {
    background: 
        url(a.png) top left no-repeat,
        url(b.png) center / 100% 100% no-repeat,
        url(c.png) white;
}

后面有一节会专门和大家聊聊多背景的使用和细节。

上面演示的只是 background 属性的最基本使用,其实在使用它时有很多细节需要我们注意或者说掌握,比如 background-imagebackground-positionbackground-repeatbackground-size等。接下来,针对background每个子属性来展开介绍。

背景颜色

我们可以使用 background-color 来显式的给元素盒子的背景层设置一个纯色,比如:

.element {
    background-color: #09f;
}

background-color 可以接受的值有 <color>transparentcurrentColor。其中 transparent 是其初始值,也就是说,如果未显式设置 background-color 的值,则表示元素盒子背景层是一个透明层,允许父级的内容可见。如果显式的设置了值,元素盒子背景图层上设置的有效颜色会位于该元素上绘制的其他内容的后面。

<color> 可以是颜色关键词或一个数值范围,具体可以参阅读 CSS Color Module Level 3 模块中的相关描述,或者移步阅读 图解CSS 系列中的《CSS 颜色》一文。

background-color 除了显式设置 <color> 值之外,还可以设置关键词 currentColor

.element {
    background-color: currentColor;
}

如果background-color值为currentColor的话,它的计算值取决于元素的color值,如果元素自身未显式设置color值,将会取自于元素的祖先元素的color值(color是一个可继承属性),直至到HTML的根元素<html>color值。如果 <html> 未显式设置color值,将会取自客户端(浏览器)的默认设置值,一般为 #000(即black):

你可能已经发现了,如果同时给元素设置了背景颜色和背景图像时,背景颜色将会位于背景图像下面,当背景图像尺寸足够大或采用完全平铺的方式(background-repeat: repeat)时,背景图像会完全遮盖住背景颜色,此时即使设置了背景颜色也会不可见:

两者之间如果分层来看的话,类似下图这样:

相对而言,background-color 的使用是较为简单的,在实际使用的时候,如果你希望给一外元素背景层设置一个纯色时,就可以使用 background-color 来实现。当然,你也可以使用纯色的背景图像来实现(比如纯色的渐变)。不过,如无特殊情况,一般不这么使用。

背景图像

你可以使用 background-image 给元素背景层添加背景图像:

.element {
    background-image: <bg-image>#
}

其中 <bg-image> 的值为 <image>none,而且nonebackground-image属性的初始值。

当一个元素的 background-image 取值为 none 时,它(none值)也会算作图像层,但不绘制任何东西。而且下列情景之一也会被视为 none

  • 一个空白的图像(零宽度或零高度),比如 url()
  • 图像下载失败,比如 url(avatar.png)(相应的路径中找不到 avatar.png
  • 不能显示(例如,它不是一个支持的图像格式),比如 url(avatar.bg)

当一个元素的 background-image 取值为 <image> 时,它可以是 url() 函数引入的图片(图片要存在,且加载成功)或 CSS 渐变绘制的渐变图像。

background-image: <image>
<image> = <url> | <gradient>
<url> = url( <string> <url-modifier>* )
<gradient> = <linear-gradient()> | <repeating-linear-gradient()> | <radial-gradient()> | <repeating-radial-gradient()> | <conic-gradient()> | repeating-conic-gradient()

如果使用 CSS 的 url() 函数引入背景图像的话,可以是相对路径下的图像,也可以是绝对路径的图像,比如:

.element {
    background-image: url('../images/avatar.svg'); /* 相对路径 */
}

.element {
    background-image: url('//www.w3cplus.com/images/avatar.svg'); /* 绝对路径 */
}

除此之外,url() 函数还可以引用 数据URI

.element {
    background-image: url('data:image/svg+xml;base64,PD94bWwgdmVyc2l...')
}

另外,可用于<img>的图片格式都可以用于background-image,比如.jpg.png.svg.gif.webp.avif.apng等。

刚才提到过了,<image>除了url()函数引入的图像之外,CSS的渐变也会被视为<image>,也就是说,使用CSS的 linear-gradient()(线性渐变)、repeating-linear-gradient()(重复线性渐变)、radial-gradient()(径向渐变)、repeating-radial-gradient()(重复径向渐变)、conic-gradient()(圆锥渐变)和 repeating-conic-gradient()(重复圆锥渐变)等绘制的渐变效果当作背景图像使用:

.element {
    background-image: var(--gradient);
}

.linear-gradient {
    --gradient: linear-gradient(red, gold, yellow, blue, lime, #09f)
}

.radial-gradient {
    --gradient: radial-gradient(circle at center,red, gold, yellow, blue, lime, #09f)
}

.conic-gradient {
    --gradient: conic-gradient(red, gold, yellow, blue, lime, #09f)
}

.repeating-linear-gradient{
    --gradient: repeating-linear-gradient(red, red 10%, gold 10%, gold 20%)
}

.repeating-radial-gradient {
    --gradient: repeating-radial-gradient(circle at left top, red, red 10px, gold 10px, gold 20px)
}

.repeating-conic-gradient {
    --gradient: repeating-conic-gradient(red, gold 20deg)
}

就像上面示例所呈现的渲染效果,不管使用哪种渐变绘制的背景图像,本质上都会调整大小以匹配元素的空间大小。也可以使用background-size 来调整其大小。

在 CSS 中结合CSS的渐变特性以及多背景,再加上background-sizebackground-clip等特性,可以使用纯 CSS 绘制一些具有纹理效果的背景。比如 MagicPattern网站@Lea Verou写的一个纹理库 提供的效果:

我们来看一个最简单的示例,就是黑白相隔的纹理效果:

body {
    background-color: #eee;
    background-image:
    linear-gradient(45deg, black 25%, transparent 25%, 
        transparent 75%, black 75%, black),
    linear-gradient(45deg, black 25%, transparent 25%, 
        transparent 75%, black 75%, black);
    background-size: 60px 60px;
    background-position: 0 0, 30px 30px;
}

效果如下:

注意,如果你看不懂这个示例不用着急,等你阅读完后面的内容就知道其中的道道。这里提到了CSS渐变,如果你对CSS渐变感兴趣或者想深入了解CSS渐变相关的知识,强烈建议你花点时间阅读下面几篇文章:

优化CSS背景图像

在 CSS 中,使用 background-image: url() 给元素设置背景图像其实是一种粗暴的做法。为什么这么说呢?

作为一名Web开发者,特别是现代Web开发中,需要面对的终端设备越来越多,从PC到平板,再到移动手机等,或者未知的终端设备。面对这么多不同的终端设备,你在元素设置背景图像时,仅仅像下面这样做并不是最佳的:

.element {
    background-image: url('images/avatar.png')
}

尤其在为了满足更多终端适配时,很多开发者会把 avatar.png 设置为较大尺寸,这对于使用移动端的用户而言是不公平的,他访问时需要花较多的数据流量来加载这张图片,甚至因为环境不好(网速慢),加载图片变得尤其的慢。

也有开发者给avatar.png选择较小尺寸的图片,但这样一来对于使用高端设备的用户来说也是不友好的,可能会因为用户使用的是高分辨率的(DPR较高)手持设备,图片变得模糊。

为此,如果想给用户提供更好的体验,就需要针对不同的环境提供不同的背景图片。在 CSS 中我们可以使用 CSS 媒体查询image-set()来实现。

使用媒体查询

使用媒体查询是一种常用的技术,即,使用 @media 规则为特定的媒体或设备类型加载不同的背景图片。比如:

/* 默认的非 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"); 
    }
}

除了根据媒体查询断点和设备 DPRbackground-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> 的相关规范和特性的出现。

事实上这也涉及到 Web 开发中响应式图片的使用,并且这是一个复杂且重要的一个话题,如果你对这方面话题感兴趣,可以移步阅读:

image-set() 函数

虽然使用媒体查询 @media 规则可以根据媒体条件给元素背景层提供不同的背景图像,但它还是有较大的缺陷性,比如他无法像 <picture> 那样提供更好的格式化图片:

<picture>
    <source srcset="../images/avatar.avif" type="image/avif">
    <img src="../images/avatar.jpg" alt="" > 
</picture>

至今为止,@media 是无法根据 type="image/avif" 的方式来进行规则匹配的。

庆幸的是,早在 2012 年开始,CSS 提供了另一个引background-image属性引入背景图片的方式,那就是 CSS 函数中的 image-set() 函数(它可以为三个不同的 CSS 属性指定值:contentcursor 和最有用的 background-image)。W3C 规范是这样描述 image-set() 的:

为用户的设备提供最合适的图像分辨率可能是一项艰巨的任务。理想情况下,图像应该与正在查看它们的设备具有相同的分辨率,这可能因用户而异。但是,其他因素可能会影响决定加载哪个图像;例如,如果用户的网络连接速度较慢,他们可能更愿意接收较低分辨率的图像,而不是等待加载较大的正确分辨率图像。image-set() 函数允许开发者不用考虑太多,只需提供多种分辨率(或多种格式)图像并让 UA 决定在给定情况加载哪张图像才是最合适的。

简单地说,开发者只需要提供多种分辨率图片或多种格式的图像,并且运用到background-image属性,最终决定定交给客户端(用户所用的终端设备)来决定。

image-set() 是一个函数,它的使用可以像其他的 CSS 函数一样,给其传递相关的参数:

image-set() = image-set( <image-set-option># )
<image-set-option> = [ <image> | <string> ][ <resolution> || type(<string>) ]

简单介绍一下 image-set() 函数中参数的作用:

  • image-set() 中的每个 <string> 代表的是一个 <url>,该 <url> 指的是图像源路径,可以是相对路径也可以是绝对路径
  • <image-set-option> 定义了一个可能的图像,供 image-set() 函数使用

每个 <image-set-option> 主要有三部分组成

  • <image>必须的,它可以是一个 <url> 引入的图像,也可以是一个 数据URI,还可以是 CSS 渐变绘制的图像
  • <resolution> 指的是分辩率,它是一个 可选参数,用于帮助客户端(UA)决定选择哪个 <image-set-option>(即哪个图像)
  • type(<string>) 函数也是一个 可选参数,用来指定图像的 MIME类型(图片格式)

来看一个具体的示例:

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

其中hero@1x.pnghero@2x.pnghero@3x.png 表示三张图像,而 1x 用于识别低分辨率图像,2x 则用于定义高分辨率图像。xDPPX 的别名,代表每像素单位的点数

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

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

<img 
    src="hero@1x.png" 
    srcset="hero@1x.png 1x, hero@2x.png 2x, hero@3x.png 3x" alt="" />

另外,HTML5 中可以在 <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>

如果希望背景中使用的图片也达到类似的效果,可以在 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@1x.webp" type("image/webp") 1x,
        "puppy@2x.webp" type("image/webp") 2x,
        "puppy@1x.png" type("image/png") 1x,
        "puppy@2x.png" type("image/png") 2x
    );
}

正如上面示例所示,当 image-set() 函数包含一个或多个 <image-set-option> 值时,image-set() 函数仅会选择其中一个 <image-set-option> 作为最终输出(即确定它将代表什么图像)。image-set() 函数将会按下面的方式来做出判断和选择:

  • 首先,从列表中删除任何在其 type() 值中指定未知或不受支持的 MIME 类型的 <image-set-option>,即type()指定的图像格式不受支持
  • 其次,从列表中删除与列表中前一个选项具有相同 <resolution> 的任何 <image-set-option>
  • 最后,在剩余的 <image-set-option> 中,根据被认为相关的任何标准(例如显示器的分辨率、连接速度等),做出特定于 UA 的选择来加载哪个
  • image-set() 函数然后表示所选 <image-set-option><image>

注意,image-set() 函数不能直接或间接嵌套在自身内部。

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

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

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

<image> 的新特性

在 CSS 中除了前面介绍的 url()image-set() 函数和 CSS 渐变给 background-image 属性设置值之外,还有几个新的函数(实验性属性)给 background-image 设置值。从 W3C 的 CSS Image Values and Replaced Content Module Level 4 模块中,我们可以获知 <image> 类型在之前的 <url><image-set()><gradient> 基础上新增了 <image()><cross-fade()><element()> 函数:

<image> = <url> | <image-set()> | <gradient> | <image()> | <cross-fade()> | <element()>

需要注意的是,<image> 除了可以用于 background-image 属性之外还可以用于 mask-imageborder-imagelist-style-imagecursor 等属性!

也就是说,之后我们可以在 background-image 属性上像使用 url()image-set() 给元素背景层设置背景图像。只不过,image()cross-fade()element() 所起的作用略有不同。接下来,咱们花一点点时间简单了解这几个函数在 background-image 上的使用会带来什么样的作用。

image() 函数

image() 函数除了可以像 url() 一样给 background-image 设置值之外,还有着自己特殊的能力,比如:

  • 双向感知:指定图像的方向性,类似于 HTML 元素的 dir 或 CSS 的 direction 特性
  • 图像片段:能够将媒体片段标识符添加到图像源上,只显示源图像的一部分,模仿 CSS Sprites(雪碧图)的方式
  • 颜色回退:指定一个纯色做为回退,以防图像加载失败或没有图像可渲染
  • 图像类型查询:根据浏览器的支持加载不同的类型的图像,类似于 image-set() 函数中的 type() 功能

image() 函数的语法规则:

image() = image( <image-tags>? [ <image-src>? , <color>? ]! )
<image-tags> = [ ltr | rtl ]
<image-src> = [ <url> | <string> ]

我们通过几个简单的示例来聊聊这几个新功能。

双向感知

从 《Web中向左向右》一文中可以得知,Web排版(或者说布局)时会因为语言不同,其阅读方式也会有所不同,常见的有 ltr (英文,中文)或 rtl(阿拉伯文或希伯来语)。

<!-- HTML -->
<ul>
    <li dir="ltr" lang="en">Bullet is a right facing arrow on the left</li>
    <li dir="rtl" lang="ar">الرمز النقطي هو سهم مواجه ↢ لليمين على اليسار</li>
</ul>

如果给 li 添加标识符(可以使用 list-style-image,也可以使用 background-image,还可以使用伪元素 ::marker 来设置),这里我们以 background-image 。它的效果可能像下图这样:

以往要实现上图这样的效果,可能会使用两张图像:

li[dir="ltr"] {
    background-image: url(ltr-arrow.png);
}

li[dir="rtl"] {
    background-image: url(rtl-arrow.png);
}

或者基于伪元素::before::after::marker做水平翻转:

li::before {
    content: "↣";
}
li[dir="rtl"]::before {
    transform: scaleX(-1);
    display: inline-flex;
}

如果我们使用 image() 函数实现上图的效果就会显得很容易:

li {
    background-image: image(ltr 'arrow.png')
}
颜色回退

不知道大家平时在写代码的时候有没有这样的习惯,即 为了防止图片加载失败影响Web的可读性,一般会在运用 <img> 或在背景图像下加一个 background-color,比如:

img {
    background-color: #444;
}

.element {
    background-color: #444;
    background-image: url('../images/black.png');
    color: #fff;
}

image() 函数就具备这样的能力:

.element {
    background-image: image("../images/black.png", #444);
    color: #fff;
}

当图像 black.png 加载失败或无法正常渲染时,将会把 #444 当作一个纯色和图像作为备用图像(相当于linear-gradient(to top, #444, #444) 绘制的线色图像),这样就可以确保文本仍然可阅读。

我们还可以利用该特性,像 <gradient> 一样绘制纯色的图像:

.element {
    background-image: image(rgb(0 0 0));

    /* 相当于 */
    background-image: linear-gradient(to bottom, rgb(0 0 0), rgb(0 0 0));
}

还可以绘制带有一定透明度的纯色图像:

.element {
    background-image: image(rgb(0 0 0 / .5));

    /* 相当于 */
    background-image: linear-gradient(to bottom, rgb(0 0 0 / .5), rgb(0 0 0 / .5));
}

这样一来,我们就又多了一种处理图片上文字效果的技术方案

上图中带透明的纯色层,就可以使用 image() 函数来实现,只不过,需要和CSS多背景结合起来一起使用:

.element {
    background-image: image(rgb(0 234 255 / .4)), url("avatar.svg");
}

两个背景合成之后的效果:

;

是不是很完美!

那么,在 CSS 中除了使用 <gradient> 构建纯色(或带有一定透明度的纯色)背景层之外,还可以使用 image()来构建,它们都和 background-color 不一样,因为background-color 始终是在背景图像的下面。

图像片段

在早期的 Web 开发者,为了节约资源的请求,开发者喜欢将很多小图片(往往是一些图标)集合在一张图片上,并且通过不同的坐标来控制显示的图像:

.icon {
    width: 48px;
    height: 48px;
    background-image: url('../images/icons.png');
    background-repeat: no-repeat;
}

.icon--facebook {
    background-position: 0 0;
}

.icon--rss {
    background-position: -57px 0;
}

我们把这种技术称为CSS Sprites技术,俗称雪碧图

image() 函数就具备类似的能力。就是给 image() 函数中引入的图像中添加一段媒体标识符(沿着xy轴的起点以及宽度w和高度h)只在元素背景层中显示背景图像中的一部分。

.element {
    background-image: image('sprites.png#xywh=338,324,360,390')
}

其中 sprites.png 是引入的背景图像名称和相应的路径,和url()函数引入背景图像是一样的;#xywh=xVal,yVal,wVal,hVal 指的是媒体标识符,也就是需要显示的图像对应的xy坐标以入宽度和高度:

正如上图所示,它的使用和以前CSS Sprites 是相似的,不同的只是使用媒体标识符来指定要显示的图像的坐标位置和大小。这也意味着,你可以可以通过 CSS 裁剪和加载图像的一部分。

注意,媒体标识符 #xywh=xVal,yVal,wVal,hVal 是向后兼容的,如果浏览器不理解媒体标识符将会被忽略,image()函数引入的图片将会被视为无效。另外,url()函数中也可以使用媒体标识符,只不过浏览器不理解时会显示整张图片。

图像类型

image()函数还有一个新功能,即给 image() 传入一组不同类型的(格式不同)图像,浏览器将按照image()中的写入的图像顺序做遍历,并使用它支持的第一个图像类型:

.element { 
    background-image: image("avatar.avif", "avatar.webp", "avatar.jpg"); 
}

比如上面的示例,如果浏览器支持.avif格式的图片,将会使用avatar.avif,否则会使用avatar.webp,如果浏览器也不支持.webp格式图像,则会使用avatar.jpg

element() 函数

element() 是一个非常有意思的函数,它可以将 HTML 的元素当作背景图像渲染。

element() = element(<id-selector>)

其中 <id-selector> 为CSS选择器中的ID选择器。具体使用的时候也像在 CSS 中使用 ID 选择器一样,需要在前面添加 # 标识符,如 element(#source)

使用 element() 函数将某个元素(包括其后代元素)当作另一个元素的背景图像时,该引用的元素外观发生变化时,目标元素的背景层(背景图像)也会相应发生变化。

使用 element() 函数可以很容易实现视频所展示的效果:

<!-- HTML -->
<div id="element-source" contenteditable="true">Please press me to enter the content you want!</div>
<div class="target"></div>

/* CSS */
.target {
    background-repeat: no-repeat;
    background-size: 100% 100%;
    background-image: element(#element-source);
}

这个示例将 idelement-source 当作 element() 函数的参数,成为目标元素 .target 元素的背景图像。简单地说,element() 可以把HTML中的任意元素,当作另一个元素的背景图像,而且神奇的是,只要元素修改了(样式或内容),那么对应的背景也会改变。

注意: element() 函数仅再现引用元素的外观,而不是实际内容及其结构。开发者应该仅将其用于装饰目的,并且不得使用 element() 在页面上复制具有重要内容的元素。相反,只需将元素的多个副本插入到文档中。

正如上面示所示,element() 函数可以将任意 DOM 元素当作另一个元素的背景,使用该特性,开发者可以将 <canvas><img><video><svg> 元素当作背景图像,实现一些有创意的效果。比如实现像 Visual Studio Code 编辑器右侧预浏的缩略图效果:

具体代码可以查阅 @Preethi Sam 在 CodePen 提供的示例

在 Firefox 浏览器中你看到的效果如下:

cross-fade() 函数

2021年年底,@Jake 花了很长的篇幅阐述了“目前为止为什么没有什么方法可以对任意两个DOM元素进行交叉淡入淡出

其实,CSS中有一个 cross-fade() 函数可以解决该问题。

W3C 规范是这样描述 cross-fade() 函数的:

在图像之间转换时,CSS 需要一种方法来显式引用中间图像,该中间图像是开始图像和结束图像的组合。这是通过 cross-fade() 函数完成的,该函数指示要组合的两个图像以及组合在过渡中的距离。

cross-fade() = cross-fade( <cf-image># )
<cf-image> = <percentage [0,100]>? && [ <image> | <color> ]

简单地说,cross-fade() 可以将两个或多个图像组合在一起生成一个新的图像,并且运用于元素的背景图层上。比如:

.element {
    background-image: cross-fade( url(white.png) 0%, url(black.png) 100%);
}

正如示例中代码所示,我们可以在每张图像后面显式指定一个百分比值(<percentage>),用来定义了在与其他图像混合时,每个图像在不透明度方面保留了多少。百分比值必须不带引号编码,必须包含 % 符号,并且其值必须介于 0%100% 之间,其中 0% 的值表示图像完全透明,而 100% 的值表示图像完全不透明。如果省略了任何百分比,则将所有指定的百分比相加并从中减去 100%。如果结果大于 0%,则结果将平均分配给所有省略百分比的图像。

cross-fade() 函数的规范允许多个图像和每个图像具有独立于其他值的透明度值。情况并非总是如此。在某些浏览器中已经实现的原始语法只允许使用两张图片,而这两张图片的透明度之和正好是 100%

cross-fade() = cross-fade( <image>, <image>, <percentage> )

比如:

.element {
    background-image: cross-fade(url(white.png), url(black.png), 50%)
}

这相当于设置了 50% 透明度(opacity: .5)的black.png图片盖在透明度为 100%(完全不透明)的 white.png上。

从这几个函数展示的效果,可以获知,在未来(有一天所有浏览器都支持了这几个函数)我们可以设置在 background-image 值不再局限于 url()<gradient>,我们可以使用这些新特性,让运用于元素背景层的图像更丰富,更灵活。

注意,这里提到的 image()element()cross-fade() 都是 <image> 的新类型,他们不局限于用在 background-image 属性上,只要是 CSS 属性可运用 <image> 值类型的属性都可以使用。

背景图片平铺方式

在元素的背景层设置背景图片时,默认情况下,背景图片会沿着容器的水平和垂直方向重复平铺,以填充整个背景层:

我们可以显式使用 background-repeat 属性来改变背景图像在背景层中的平铺方式:

.element {
    background-repeat:  <repeat-style>#
}

<repeat-style> = repeat-x | repeat-y | [repeat | space | round | no-repeat]{1,2}

从语法规则上来看,background-repeat 可以显式设置一个值,也可以显式设置两个值,比如:

.element {
    background-repeat: no-repeat; /* 一个值 */
    background-repeat: no-repeat repeat; /* 两个值 */
}

background-repeat 只显式设置一个值时,不同的值所代表的含义(对应的两个值)是有所差异的:

  • repeat 相当于 repeat repeat
  • repeat-x 相当于 repeat no-repeat
  • repeat-y 相当于 no-repeat repeat
  • space 相当于 space space
  • round 相当于 round round
  • no-repeat 相当于 no-repeat no-repeat

另外,background-repeat 可以是任意两个值组合在一起,比如:

.element {
    background-image: space round; 

    /* 或 */
    background-image: round space;
}

除此之外,还可以使用 background-repeat-xbackground-repeat-y 单独指定背景图像在背景层中沿x轴或y轴平铺的方式:

.element {
    background-repeat: space no-repeat;

    /* 相当于 */
    background-repeat-x: space;
    background-repeat-y: no-repeat;
}

取不同值,所产生的效果是不一样的:

接下来,花点时间来看每个不同值所起得作用以及差异。

repeat

repeatbackground-repeat 的初始值。

background-repeat 显式设置值为 repeat 时,相当于 repeat repeat,表示背景图像会沿着元素背景层的水平方向(x轴)和垂直方向(y轴)重复平铺,直到铺满整个背景层的空间。

.element {
    background-repeat: repeat;

    /* 等同于 */
    background-repeat: repeat repeat;

    /* 等同于 */
    background-repeat-x: repeat;
    background-repeat-y: repeat;
}

设置 repeat 值时,如果背景图片的尺寸和元素背景层空间不是倍数比例时,背景图片在最右侧和(或)最低部会被裁剪掉:

从上图中你可能已经发现了,如果值为 repeat 时,背景图片在平铺时,会铺满整个背景层的空间,而且背景图片剪切会一直延伸到元素的<border-box>(延伸到边框的外边缘)。如果你不希望图片按这样的方式来对背景图片裁剪,可以通过 background-clip 来改变。

另外,背景图片平铺的起点位置0 0,指的是<padding-box>,也就是从边框的内边缘(内距的外边缘)来起始点,所以你上面看到的(0 0位置)并不是容器的左上角。如果你希望从边框的外边缘开始平铺背景图像,则需要使用 background-origin 来重置。

有关于 background-clipbackground-origin 的详细介绍,请参考后面的章节!

repeat-x

repeat-xrepeat-y 都是 repeat 的一个分值,如果background-repeat 显式设置值为单个 repeat-x 时,它就相当于 repeat no-repeat,即:

.element {
    background-repeat: repeat-x;

    /* 等同于 */
    background-repeat: repeat-x no-repeat;

    /* 等同于 */
    background-repeat-x: repeat;
    background-repeat-y: no-repeat;
}

从上图的效果中可以得知,repeat-x 只会让背景图像在元素背景层中沿着x轴进行重复平铺。和repeat有点类似,有可能在元素背景层最右侧不具备背景图片展示的空间,背景图片会被裁切掉。

repeat-y

repeat-yrepeat-x 类似,不同的是,它只允许背景图片沿着元素背景层y方向重复平铺。当 background-repeat 只显式设置repeat-y 值时,它相当于no-repeat repeat,即:

.element {
    background-repeat: repeat-y;

    /* 等同于 */
    background-repeat: no-repeat repeat;

    /* 等同于 */
    background-repeat-x: no-repeat;
    background-repeat-y: repeat;
}

同样的,使用 repeat-y 有可能造成容器底部没有足够的空间展示整个背景图片。

no-repeat

no-repeat 刚好和 repeat 相反,表示背景图片在元素背景层中不会被重复平铺。当 background-repeat 只显式设置 no-repeat 值时,它相当于 no-repeat no-repeat,即:

.element {
    background-repeat: no-repeat;

    /* 等同于 */
    background-repeat: no-repeat no-repeat;

    /* 等同于 */
    background-repeat-x: no-repeat;
    background-repeat-y: no-repeat;
}

background-repeat 值为 no-repeat 时,如果背景图片尺寸小于容器背景层尺寸的话,那么背景图片无法填满整个背景层,在没有显式设置 background-color 值(它的初始值为 transparent)时,那么位元素底部的内容就会被透出:

反之,如果背景图片尺寸大于或等于容器背景层尺寸时,背景图片会填满整个背景层;当大于容器背景层尺寸时,背景图片还会被裁切掉:

注意,可以使用 background-size 来动态调整背景图片尺寸。后面的章节中,我们会详细介绍 background-size

也就是说,background-repeat 取值为 repeat-xrepeat-yno-repeat 时,背景图片都有可能不会(背景图片尺寸小于背景层尺寸)填满元素背景层。在这种情景中,它的位置是由 background-position 来决定的(默认是0 0位置)。换句话说,background-position 的值会调整背景图片在背景层的位置,其实它也会影响background-repeat属性的其他值,包括接下来要介绍的 spaceround

有关于 background-position 属性的详细介绍,请参阅后面的章节!

space

spaceroundbackground-repeat 新增的两个属性值,我想大多数开发者对他们并不了解,甚至从未使用过(其实我自己也很少用到这他们),但它们的功能还是很有用处的。这里我们先来看 space

background-repeat 取值为 space 时,背景图片在容器的背景层重复平铺的方式看上去像 repeat,但它有一个最大的特色,即 背景图像不会因为背景层的尺寸(空间)不匹配而被裁切,换句话说,space会让背景图片按整数倍(n)沿着容器背景层的 xy 轴重复平铺:

  • 如果背景层的宽度刚好是背景图片宽度的n倍,那么背景层的x轴方向有n张背景图重复平铺
  • 如果背景层的宽度不背景图片宽度的 n 倍,那么浏览器会确保第一张和最后一张背景图固定在背景层x轴的最两端,同时每两张背景图之间会有一个空白的间距,这个空白的间距相等的,即 (容器背景层宽度 - n × 背景图片宽度 ) ÷ (n - 1)。假设元素背景层的宽度是 570px,背景图片宽度是 100px,在背景层x轴最多可以平铺 5 张背景图(因为6张需要6 x 100px = 600px,无法容纳下),这样一来,相应的 n 就等于 5,根据相应的公式,可以计算出背景图片之间的间距是:(570px - 5 × 100px) ÷ (5 - 1) = 17.5px,大约 17.5px
  • 如果背景层的高度刚好是背景图片高度的 n 倍,那么背景层的 y 轴方向会有 n 张背景图平铺
  • 同样的,如果背景层的高度不是背景图片高度的 n 倍,那么浏览器同样会确保第一张和最后一张背景图固定在背景层y轴的两端,同时每两张背景图之间会有一个空间的间距,这个空白的间距也是相等的,即 (容器背景层高度 - n x 背景图片高度) ÷ (n - 1)。假设元素背景层的高度是 246px,背景图片高度是 100px,在背景层y轴最多可以平铺 2 张背景图(因为 3 张需要 3 x 100px = 300px,无法容纳下),这样一来,相应的 n 就等于 2,根据相应的公式,可以计算出背景图片之间的间距是: (246px - 2 × 100px) ÷ (2 - 1) = 46px,大约 46px

上述示例中提到的效果,就会像下图所示这样:

background-repeat 中显式设置一个 space 值时,它相当于 space space,即:

.element {
    background-repeat: space;

    /* 等同于 */
    background-repeat: space space;

    /* 等同于 */
    background-repeat-x: space;
    background-repeat-y: space;
}

前面我们提到过,background-position 取值对 background-repeat 的平铺是有一定影响的,不过有一个细节需要注意的是,当 background-repeat 取值 为 space时,只有一个图像(或单行)被无裁切地显示时,才会受 background-position 影响,否则 background-position 属性会被忽视。

另外,当 background-repeat 值为 space 时,只在一种情况下裁剪会发生,那就是图像太大了以至于没有足够的空间来完整显示一个图像。

round

从渲染的结果上来看,round 是最近 repeat 的。如果 background-repeat 取值为 round 时,背景图片会沿着元素背景层的 x 轴和 y 轴重复平铺,直到平满整个背景层。它和 repeat 最大的差异是 round会根据元素背景层尺寸和背景图的尺寸做一个自适应处理

换句话说,随着允许的空间在尺寸上的增长,被重复平铺的背景图像会伸展(不会像space留有空隙),直到有足够的空间来添加一个图像。当下一个图像被添加后,所有的当前的图像会被压缩来腾出空间。

简单地说,round会根据元素容器层尺寸和背景图片尺寸(以background-size设置的为准,如果未显式设置background-size,将会以背景图片的原始尺寸为准)做取整计算,类似 JavaScript的 Math.floor() 函数,向下取整。来看一个简单的示例,当元素背景层的尺寸为 624px x 277px,背景图片的尺寸为 100px x 100px(显式使用background-size: 100px 100px重置背景图片尺寸),那么:

// » x轴 » 沿着元素背景层 x 轴方向(水平方向)
624 ÷ 100 = 6.24 » Math.floor(624 / 100) = 6

// » y轴 » 沿着元素背景层 y 轴方向(垂直方向)
277 ÷ 100 = 2.77 » Math.floor(277 / 100) = 2

即水平方向(x)会铺放 6张;垂直方向(y)会铺放 2 张,如下图所示:

注意,平铺之后的背景图尺寸不再是 100px x 100pxbackground-size显式设置的值),因为浏览器的渲染引擎会重新计算背景图片的尺寸(根据background-size的计算规则计算出来的)。就我们这个示例而言,相当于 background-size: calc(100% / 6) calc(100% / 2)。此时,重新计算尺寸之后的背景图片,有可能会扭曲变形(宽高比例不一致)。要是观察仔细的话,上图中的背景图片就有点挤压变形了。

更有意思的是,当背景图片尺寸大于元素背景层尺寸,并且没有显式使用 background-size 重置背景图片尺寸(初始尺寸)时,round 依旧会让背景图片重新计算尺寸来填满元素背景层,确保背景图不会被裁剪。

round始终能确保背景图在平铺过程中不被裁剪,即使只有一张的时候也是如此!

不过,round 并不是说,完全会让背景图片填充满整个背景层,在某些情况之下,是会留白的,它的表现形为有点类似于background-sizecontain行为。这并不能说这种现象是错误的,反而能说他的表现行为是对的。因为round值在计算的时候,就是按照background-size规则计算的。

如果 background-repeat 显式设置值为 round 时,相当于 round round,即:

.element {
    background-repeat: round;

    /* 等同于 */
    background-repeat: round round;

    /* 等同于 */
    background-repeat-x: round;
    background-repeat-y: round;
}

多值组合

上面我们所说的都是 background-repeat 分别取 repeatrepeat-xrepeat-yno-repeatspaceround值的效果,以及他对应的两个分值。事实下,我们在使用的时候,开发者可以根据需要来进行组合:

.element {
    background-repeat: repeat repeat;
    background-repeat: repeat no-repeat;
    background-repeat: repeat round;
    background-repeat: repeat space;

    background-repeat: no-repeat repeat;
    background-repeat: no-repeat no-repeat;
    background-repeat: no-repeat round;
    background-repeat: no-repeat space;

    background-repeat: round repeat;
    background-repeat: round no-repeat;
    background-repeat: round round;
    background-repeat: round space;

    background-repeat: space repeat;
    background-repeat: space no-repeat;
    background-repeat: space round;
    background-repeat: space space;
}

使用两个值组合时,第一个对应是 background-repeat-x 属性的值,第二个则是background-repeat-y的值。其中 repeat-xrepeat-y 是明确了重复平铺的方向,具有明确的方向性,为此,不建议像下面这样使用:

.element {
    background-repeat: repeat-x repeat;
    background-repeat: repeat-x no-repeat;
    background-repeat: repeat-x repeat-y;
    background-repeat: repeat-x space;
    background-repeat: repeat-x round;

    background-repeat: repeat repeat-y;
    background-repeat: repeat-x repeat-y;
    background-repeat: no-repeat repeat-y;
    background-repeat: round repeat-y;
    background-repeat: space repeat-y;
}

它们都会被浏览器视为无效声明。

背景图片依附

这里所说的背景图片依附指的是 background-attachment 如何控制背景图片(背景图层的背景图像)固定位置的行为。在 CSS 中,如果显式使用 background-image 指定了背景图像(<image>),那么 background-attachment 属性将指定它们是相对于视口固定(fixed)还是随元素滚动(scroll)或相对于元素的内容是固定的(local)。

background-attachment:  <attachment>#
<attachment> = scroll | fixed | local

每个关键词值所代表的意思是:

  • scroll:该值是其初始值,表示背景图像相对于元素本身固定,而不是随着它的内容滚动(对元素边框是有效的)
  • fixed:表示背景图像相对于视口(浏览器视窗)固定。即使一个元素拥有滚动机制,背景图像也不会随着元素的内容滚动
  • local:表示背景图像相对于元素的内容固定。如果一个元素拥有滚动机制,背景图像则会随着元素的内容滚动,并且背景的绘制区域和定位区域是相对于滚动的区域而不是包含他们的边框

我们来看看这几个属性值的具体表现以及所渲染出来的效果:

scroll

先来看 scroll 的效果。

在 CSS 中,如果未显式设置 background-attachment 的值,则默认就是 scroll,因为其初始值就是 scroll。背景图像相对于元素本身是固定的,不随其内容滚动(它被有效地附加到元素的边框(<border-box>)上)。背景图像会沿着包含它的CSS框滚动。

.element {
    background-image: url('image/hero.jpg');
    background-size: cover;
    background-position: 0 0;
    background-attachment: scroll;
}

fixed

fixed 相对来说好理解。当 background-attachment 属性值为fixed时,表示元素中背景层上的背景图像会相对于浏览器视窗固定(有点类似于 CSS 的position: fixed效果)。

.element {
    background-image: url('image/hero.jpg');
    background-size: cover;
    background-position: 0 0;
    background-attachment: fixed;
}

正如上面示例所示,背景相对于视口是固定的。在多个元素上使用背景图像,并且将它们的 background-attachment 显式设置为 fixed 时,可以看到,随着浏览器不断往下滚动(默认在 <body> 元素上的滚动行为),内容下会呈现不同的背景图像。换句话说,一旦背景层图像最初占用的空间需要滚动(或渲染)到屏幕(视窗)外,背景层内的图像将保持固定在背景层启用它们的原始位置,直到整个层被视口滚动到屏幕外。

另外,在没有视口的分页媒体中,固定的背景相对于页框(<page-box>)是固定的,因此背景图像就像是复制到每一页上。

注意,每个视图只有一个视口。即使元素有滚动机制,固定的背景也不会随元素移动。

就我个人经验来说,fixed相对来说使用的频率要少(本来 background-attachment 就不怎么显式重置)。除此之外,有很多平台至今都对 background-attachment: fixed 不支持,尤其是在移动端上。这是因为,固定背景导致重绘的成本很高,并且滚动表现也不尽人意,所以在一些移动端是被禁止的。有关于这个话题,我们稍后再聊!

local

background-attachment 取值为 local 时,会使元素的背景图像相对于元素的内容固定。如果元素具有滚动机制,背景图像将随着元素的内容滚动,并且背景绘制区域和背景定位区域相对于元素的可滚动区域,而不是元素的边框(<border-box>),这主要是元素的可滚动区域不包括边框区域。

.element {
    background-image: url('image/hero.jpg');
    background-size: cover;
    background-position: 0 0;
    background-attachment: local;
}

有一点需要注意的是,background-attachment取值为local时,由于可滚动区域不包括边框区域,所以对于可滚动元素,background-clip取值为 padding-boxborder-box处理结果是相同的。

背景图像依附案例

background-attachment 往往很少重置属性值,但当 background-attachment 取值为 fixed 时,可以帮助我们构建一些 Web 效果,比如视差滚动效果。在介绍如何使用它来构建视差滚动效果之前,先来看一个固定背景图像的一个简单示例。就是网格布局中的所有网格项目共享同一张背景图像。

这个示例来自于 Codepen,是由@Noah Raskin 提供的:Grid Items Share Background

示例采用的是 CSS Grid 构建的布局:

.grid {
    --gap: 10px;
    display: grid;
    grid-template-columns: repeat(4, 1fr);
    gap: var(--gap);
}

.grid__item:nth-of-type(1) {
    grid-column: 1/3;
}

.grid__item:nth-of-type(2) {
    grid-column: 3/5;
    grid-row: 1/3;
}

.grid__item:nth-of-type(4) {
    grid-column: 2/3;
    grid-row: 2/5;
}

@media screen and (max-width: 840px) {
    .grid {
        height: auto;
        grid-template-columns: repeat(2, 1fr);
    }
    .grid__item {
        min-height: auto;
        aspect-ratio: 1 / 1;
        width: 100%;
    }

    .grid__item:nth-of-type(1) {
        grid-column: auto;
    }

    .grid__item:nth-of-type(2) {
        grid-column: auto;
        grid-row: auto;
    }

    .grid__item:nth-of-type(4) {
        grid-column: auto;
        grid-row: auto;
    }
}

但所有网格项目共享同一张背景图片,其关键之处是 所有网格项目使用了同一张背景图片,并且 background-attachment都显式设置了值为 fixed,具体代码如下:

.grid__item {
    background-image: url("https://picsum.photos/2016/1642?image=10");
    background-size: cover;
    background-position: center;
    background-attachment: fixed;
}

具体效果如下:

如果你从未接触过 CSS Grid,而又想进一步了解或掌握它,建议您从《2022年不能再错过 CSS 网格布局了》一文中索引你想要的内容!

再来看一个使用 background-attachment:fixed实现视差效果的案例。

这个示例的效果是 “在所有背景图像中的完全相同的位置上放置相同的元素(比如示例中的iPhone手机),这样当您滚动时,所有东西都会移动,但iPhone手机不会移动”。

<!-- HTML -->
<section class="fixed-background img-1">
    <div class="content">
        <h2>Title here</h2>
        <p>Lorem ipsum dolor sit amet, ...</p>
    </div>
</section>

<section class="fixed-background img-2">
    <!-- ... -->
</section>

HTML结构很简单。每个部分都包含一个 .content 带有标题(<h2>)和段落(<p>)元素,并且在每个<section>中有一个不同的类名,比如 .img-1.img-2等,它们主要用于在 CSS 中设置不同的背景图像。

关键的CSS代码如下所示:

.fixed-background {
    --bg-image: url("https://picsum.photos/2016/1642?image=10");
    --start-color: #09f;
    --stop-color: #90f;
    background-position: center;
    background-size: cover;
    background-image: linear-gradient(
        to bottom,
        var(--start-color),
        var(--stop-color)
        ),
        var(--bg-image);
    background-attachment: fixed;
    background-blend-mode: overlay, exclusion;
}

.img-1 {
    --start-color: #00deff;
    --stop-color: #ff8c00;
    --bg-image: url("https://picsum.photos/2016/1642?image=10");
}

.img-2 {
    --start-color: #111340;
    --stop-color: #4800ff;
    --bg-image: url("https://picsum.photos/2016/1642?image=20");
}

.img-3 {
    --start-color: #7c87a8;
    --stop-color: #ff0028;
    --bg-image: url("https://picsum.photos/2016/1642?image=30");
}
.img-4 {
    --start-color: #84a87c;
    --stop-color: #1b6571;
    --bg-image: url("https://picsum.photos/2016/1642?image=40");
}
.img-5 {
    --start-color: #5d4012;
    --stop-color: #5d912a;
    --bg-image: url("https://picsum.photos/2016/1642?image=50");
}

固定背景的黑魔法

前面提到过,background-attachment取值为fixed,在移动端上并不受欢迎。@Murtuzaali Surti 在 CSS-TRICKS 上发表过关于这个话题的一篇博文《The Fixed Background Attachment Hack》。详细地阐述了为什么background-attachment:fixed在移动端上渲染的时候存在问题以及如何使用CSS黑魔法来实现类似background-attachment:fixed的效果。

@Murtuzaali Surti 在文中录制了相关的视频,向大家展示 background-attachment:fixed 在移动端渲染时的效果(并不是我们想要的):

渐变只是与其他内容一起滚动然后跳回来

另一个需要注意的有趣的事情是,当 background-attachment: fixed 应用时,即使我们明确指定了高度,它也会被忽略。这是因为 background-attachment 计算固定背景的位置是相对于视口计算。

即使在 body 上显式设置 height100vhbackground-attachment: fixed 渲染出来的效果也不完全符合。其中原委并不知。但有这样的一种观点:“也许原因是 background-attachment: fixed 依赖于最小的视口,而元素依赖于最大的视口”。

@David Bokan曾经这样说过:

Lengths defined in viewport units (i.e. vh) will not resize in response to the URL bar being shown or hidden. Instead, vh units will be sized to the viewport height as if the URL bar is always hidden. That is, vh units will be sized to the “largest possible viewport”. This means 100vh will be larger than the visible height when the URL bar is shown.

大致意思是,“以视口单位(即 vh)定义的长度不会响应 URL 栏的显示或隐藏而调整大小。相反, vh 单位将根据视口高度调整大小,就好像 URL 栏始终处于隐藏状态一样。也就是说, vh 单位将被调整为“最大可能的视口”。这意味着 100vh 将大于显示 URL 栏时的可见高度。”

其实这些问题在 Caniuse上也有相应的描述

如果你碰到这个问题,在业务场景中又需要这样的效果,那么我们可以考虑下面这样的Hack手段来实现类似background-attachment:fixed的效果:

<!-- HTML -->
<div class="bg"></div>
<div class="content"></div>

/* CSS */
.bg {
    background: linear-gradient(335deg, rgba(255,140,107,1) 0%, rgba(255,228,168,1) 100%);
    background-repeat: no-repeat;
    background-position: center;
    height: 100vh;
    width: 100vw;
    position: fixed;
    /* z-index usage is up to you.. although there is no need of using it because the default stack context will work. */
    z-index: -1; // this is optional
}

.content{
    position: absolute;
    margin-top: 5rem;
    left: 50%; 
    transform: translateX(-50%);
    width: 80%;
}

注意,这段代码来自@Murtuzaali Surti。最终效果如下:

效果已经很完美了。但上面的 CSS 代码我们还可以进一步的优化,我们可以使用伪元素::before::after来替代.bg

<!-- HTML -->
<div class="content"></div>

/* CSS */
.content {
    width: 80%;
    margin: auto;
}

body {
    position: relative;
}

body::before {
    content: '';
    background: linear-gradient(335deg, rgba(255,140,107,1) 0%, rgba(255,228,168,1) 100%);
    background-repeat: no-repeat;
    background-position: center;
    height: 100vh;
    width: 100vw;
    position: fixed;
    z-index: -1;
}

上面代码还可以进一步的优化,只在移动端上使用伪元素定位:

body{
    background-image: url(image.png);
    background-size:cover;
    background-attachment:fixed;
}
/* 移动端响应*/
@media screen and (max-width:767px){
    body{
        background-position: -99999px -99999px; /* 移出可视区,也可以将其background-size设置为0 */
    }
    body::before {
        content: ""; 
        background-image: inherit; 
        position: fixed; 
        inset:0;
        height: 100vh; 
        width: 100vw; 

        background-size: cover;
        z-index: -1;
    }
}

待续...

返回顶部