现代 CSS

图解CSS:CSS背景(Part3)

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

前面已经花了两篇的篇幅(《CSS背景:Part1Part2》)介绍完了除background-size之外的所有background子属性的特性以及使用。众所周之,在Web中对于<img>引入的图片,我们可以显式使用widthheightaspect-ratioobject-fit等属性来调整图片尺寸,但对于背景图片,我们只能使用background-size来进行调整。那么background-size是如何决定背景图片尺寸的计算呢?如果你感兴趣的话,这篇文章可以告诉你很多不为人知的答案。

背景图片尺寸

在 CSS 中,可以显式使用 background-size 给背景图片设置尺寸,背景图片可以保有其原有的尺寸,或者拉伸到新的尺寸,或者在保持其原有比例的同时缩放到元素的可用空间的尺寸。它的语法规则如下:

background-size: <bg-size>#
<bg-size> = [ <length-percentage [0,∞]> | auto ]{1,2} | cover | contain

可以按下面三种方式之一来设置背景图片尺寸:

  • 使用 [ <length-percentage [0,∞]> | auto ]{1,2} 给背景图片指定宽高值
  • 使用关键词 cover ,指定背景图像的大小应尽可能小,同时确保两个尺寸都大于或等于容器的相应大小
  • 使用关键词 contain,指定,无论包含框的大小如何,都应缩放背景图像,以使每一边尽可能大,同时不超过容器对应边的长度

当通过宽度和高度来指定背景图像尺寸时,你可以提供一个或两个数值,可以是 <length-percentage>(即 <length><percentage> 值)或 auto

  • 如果仅显式指定一个数值,那么该数值作为背景图片的宽度值,高度则会设定为 auto
  • 如果显式指定两个数值,那么第一个值作为背景图片的宽度值,第二个值作为背景图片的高度值

简单地说,background-size 可以取 <length><percentage>autocovercontain 值,不同的值所代表的含义如下:

  • auto,将会按照背景图片的宽高比自动缩放背景图片
  • <length>,使用长度值指定背景图片的大小,比如 100px2vw30rem
  • <percentage>,使用百分比值指定背景图片的大小,比如 100%50%等。它的计算是相对于背景层的盒子模型的尺寸,盒子模型的大小由 background-origin 来决定;如果 background-attachment 值为 fixed,它将相对于视窗大小来计算(不包括滚动条尺寸)
  • cover,将图像(同时保持其比例)缩放到尽可能小的尺寸以填充容器(即:其高度和宽度都完全覆盖容器),不留空白。如果背景的比例与元素不同,则图像将被垂直或水平裁剪。
  • contain,在不裁剪或拉伸图像的情况下,在其容器内尽可能大地缩放图像。如果容器大于图像,这将导致图像平铺,除非 background-repeat 属性设置为 no-repeat

下面这个示例展示了background-size取不同类型值时所呈现的效果:

背景图片尺寸的计算

background-size属性只有显式指定两个值为 <length> 值时才会按指定的大小渲染,其他几种情况都会涉及到背景图片的计算:

  • 只显式指定了一个<length><percentage>(另一个值是auto
  • 值为<percentage>
  • covercontain

尤其是取值为 covercontain 时,计算相对而言要复杂一点。接下来,我们一起来探讨背景图片是如何计算的。

背景图片尺寸的计算取决于背景图像的内在尺寸(widthhegiht)和内在比例(宽高比:ratio = width : height)。而这些参数又取决于运用于 background-image 属性的值 <image> 类型。

如果 <image> 是一张位图,那这张背景图有自己的原始尺寸,即宽(width)、高(height)以及相应的比例。比如下面这张 1600px x 900px 的JPG图片:

即:

width = 1600px 
height = 900px
ratio = 1600 / 900 = 16:9

相当于:

img[Attributes Style] {
    width: 1600px;
    aspect-ratio: auto 1600 / 900;
    height: 900px;
}

如果 <image> 是一张矢量图,比如 SVG 图,那它就不一定有内在尺寸。如果它有水平和垂直的固有尺寸,它也有固有的比例。如果它没有维度或只有一个维度,那么它可能有也可能没有比例。

如果 <image> 是渐变绘制的(<gradient>),那它不存在内在的尺寸和内在的比例。

如果 <image> 是使用 element() 函数创建的背景图像,那么它的内在尺寸和内在比例是 element() 函数引用的元素的尺寸和宽高比例。

注意,为了让背景图片计算简化,接下来我们主要围绕着位图的类型为例。比如1600_900.jpg

.element {
    background-image: url('1600_900.jpg');
}

该背景图片的内在尺寸和内在比例是:

  • 背景图片的内在宽度:width = 1600px
  • 背景图片的内在高度:height = 900px
  • 背景图片的内在宽高比:ratio = 16 : 9

我们也知道,在 CSS 中任何元素都是一个盒子,他也有自己的宽高和相应的比例,我们这里称之为容器的宽高和比例。比如:

.element {
    width: 400px;
    height: 300px;
}

那么 .element 容器的宽度是 400px,高是 300px,对应的宽高比是 4:3。也可以使用widthheightaspect-ratio属性来决定一个容器的大小:

.element {
    width: 400px;
    aspect-ratio: 4 / 3;
}

或:

.element {
    height: 300px;
    aspect-ratio: 4 / 3;
}

需要注意的是元素的 border-widthpadding 对于盒子大小是有影响的,而且 box-sizing的值也对盒子尺寸计算有一定的影响。接下来的示例以 box-sizingborder-box 方式来计算。

你可能会感到好奇,探讨背景图片尺寸计算,怎么还要和元素的大小和宽高比扯在一起呢?主要是因为,background-size取值covercontain时,背景图片的计算是会将图片的尺寸和元素容器的尺寸结合起来计算的,它们的计算有一套隐形的公式存在。稍后我们会详细聊到这方面。为了避免一些概念或术语的混淆,提前先统一起来。

参数 背景图片 元素容器 计算后的背景图片
宽度 Wimage Wcontainer W‘
高度 Himage Hcontainer H'
宽高比 Rimage Rcontainer R'

百分比 <percentage>

先来看background-size属性取百分比(%)值时,背景图片的尺寸计算。因为它相对来说要简单一点。background-size取百分比值时,它的计算是相对于元素背景区域的尺寸来计算,而且背景区域的尺寸由 background-origin 来决定(默认为内距盒子<padding-box>的尺寸)。比如:

.element {
    width: 400px;
    aspect-ratio: 4 / 3;
    background-image: url('1600_900.jpg');
    background-size: 50% 50%;
    border: 20px solid rgb(120 200 10 / 0.5);
    padding: 20px;
}

background-size取百分比值时,可以显式设置两个值,其中第一个值是相对于 <box> 的宽度计算,第二个则相对于<box>的高度计算。当然也可以显式指定一个值,如果仅指定一个值,那么第二个值则是 auto。这个时候,将会根据auto规则来计算,即 一个为auto,另一个不是auto

如果图像有固有比例,则指定的长度使用指定值,未指定的长度由指定值与固有比例计算。如果图像没有固有比例,则指定的长度使用指定值,未指定的长度使用图像相应的固有长度,若没有固有长度,则使用背景区相应的长度。

.element {
    background-size: 50%;

    /* 等同于 */
    background-size: 50% auto;

    /* 但不等同于 */
    background-size: auto 50%;
}

有一点需要注意的是,当 background-attachment 属性的值为 fixed 时,background-size取百分比值时,它的计算不是相对于元素的<box>尺寸计算,而是相对于不包含滚动条宽度的视窗尺寸计算。

covercontain

在深入探讨 background-size 取值为 covercontain 关键词时,背景图片如何计算之前,先来看它们实际渲染出来的效果。在这个示例中,区们有两张图片,分别 1600_900.jpg(宽是1600px,高是900px,宽高比是16:9)和 900__1600px(宽是900px,高是1600px,宽高比是9:16),将这两张不同尺寸(比例)的背景图片放置在一个4:31:13:4的背景层中:

以上面这个示例,来展开background-size 属性值为 covercontain ,背景图片的尺寸计算。

首先,示例中提供了两种图片尺寸:

  • 横屏(1600_900.jpg),图片的宽度大于高度,它的宽高比大于 1(它的内在尺寸是 1600px x 900px,宽高比是 ratio = 16 / 9 = 1.77778,即 Wimage = 1600pxHimage = 900pxRimage=1.77778
  • 竖屏(900_1600.jpg),图片的宽度小于高度,它的宽高比小于 1(它的内在尺寸是 900px x 1600px,宽高比是 ratio = 9 / 16 = 0.5625,即 Wimage = 900pxHimage = 1600pxRimage = 0.5625

将这两张图片分别运用于三种不同尺寸的容器(背景层):

.box {
    width: min(100%, 300px);
}

/* 横屏,width > height, 300px x 225px,ratio = 1.33333 */
.box--landscape {
    aspect-ratio: 4 / 3;
}

/* 正方形,width = height, 300px x 300px, ratio = 1*/
.box--square {
    aspect-ratio: 1;
}

/* 竖屏,width < height, 300px x 400px, ratio = 0.75 */
.box--portrait {
    aspect-ratio: 3 / 4;
}

容器 容器宽度(Wcontainer 容器高度(Hcontainer 容器宽高比(Rcontainer 备注
.box--landscape 300px 225px 1.33333 横屏,宽大于高
.box--square 300px 300px 1 正方形,宽高相等
.box--portrait 300px 400px 0.75 竖屏,宽小于高

从视觉上来看,两张图片的尺寸都大于容器尺寸。如果要让背景图片适应容器背景层尺寸,最简单的方法就是给 background-size 属性指定与容器宽高相等的值:

.box--landscape {
    --background-size: 300px 225px;
} 

.box--square {
    --background-size: 300px 300px;
}

.box--portrait {
    --background-size: 300px 400px;
}

在我们这个示例,可以直接将 background-size 设置为 100% 100%

但这样做,效果比较糟糕,背景图片会被挤压变形:

反之,如果背景图片内在尺寸小于容器尺寸,这样设置背景图片尺寸,会造成图片拉伸变形失真。

造成这样的问题,主要是背景图片转换尺寸之后,它的宽高比没有和内在的宽高比保持一致,即 R' ≠ Rimage

有意思的是,containcover 在保留其固有比例的同时,图像以包含在覆盖背景定位区域内的最大尺寸呈现。简单地说,background-size取值不管是 contain 还是 cover,都会让背景图片根据容器背景层做出相应的调整,而且始终保持调整后的宽高比和内在固有的宽高比相等,即 R' = Rimage

上图是 background-size属性值为 cover 的效果,背景图片覆盖了整个元素背景层!呈现出来的效果有着一个明显的特征:

  • 图片1600_900.jpg的宽高比Rimgage大于容器的宽高比RcontainerRimage > Rcontainer),调整尺寸之后的背景图片高度H'等于容器高度HcontainerH' = Hcontainer),背景图片的宽度等于调整后高度H'乘以背景图片宽高比Rimage,即 W' = H' x Rimage = Hcontainer x Rimage
  • 图片900_1600.jpg的宽高比Rimage小于容器的宽高比RcontainerRimage < Rcontainer),调整尺寸之后的背景图片宽度W'等容器宽度WcontainerW' = Wcontainer),背景图片的高度等于高整后宽度W'除以背景图片宽高比Rimage,即 H' = W' ÷ Rimage = Wcontainer ÷ Rimage

简单地规纳一下,background-size取值为cover时,背景图片的尺寸计算:

/** 
* Rimage     » 背景图片内在宽高比 » Rimage = Wimage ÷ Himage
* Wimage     » 背景图片宽度(原始宽度)
* Himage     » 背景图片高度(原始高度)
* W'         » 计算后的背景图片宽度
* H'         » 计算后的背景图片高度
* R'         » 计算后的背景图片宽高比,与背景图片内在宽高比相等 » R' = Rimage 
* Wcontainer » 容器宽度(容器元素的width)
* Hcontainer » 容器高度(容器元素的height)
**/

if (Rimage ≥ Rcontainer) {
    H' = Hcontainer
    W' = H' x Rimage = Hcontainer x Rimage
} else {
    W' = Wcontainer
    H' = W' ÷ Rimage = Wcontainer ÷ Rimage
}

再来看 background-size 取值为 contain的效果:

整个背景图片放在容器中,两边会有留下空白!就像我们看电影时的"黑边"。它呈现出来的特征是:

  • 图片1600_900.jpg的宽高比Rimage大于容器的宽高比RcontainerRimage > Rcontainer),调整尺寸之后的背景图片的宽度W'等于容器宽度WcontainerW' = Wcontainer),背景图片的高度等于调整后宽度W'除以背景图片的宽高比Rimage,即 H' = W' ÷ Rimage = Wcontainer ÷ Rimage
  • 图片900_1600.jpg的宽高比Rimage小于容器的宽高比RcontainerRimage < Rcontainer),调整尺寸之后的背景图片的高度H'等于容器高度HcontainerH' = Hcontainer),背景图片的宽度等于调整后高度H'乘以背景图片的宽高比Rimage,即 W' = H' x Rimage = Hcontainer x Rimage

它和 cover是惊人的相似,从计算来讲,contain的逻辑和cover刚好相反:

/**
* Rimage     » 背景图片内在宽高比 » Rimage = Wimage ÷ Himage
* Wimage     » 背景图片宽度(原始宽度)
* Himage     » 背景图片高度(原始高度)
* W'         » 计算后的背景图片宽度
* H'         » 计算后的背景图片高度
* R'         » 计算后的背景图片宽高比,与背景图片内在宽高比相等 » R' = Rimage 
* Wcontainer » 容器宽度(容器元素的width)
* Hcontainer » 容器高度(容器元素的height)
*/

if (Rimage ≥ Rcontainer) {
    W' = Wcontainer
    H' = W' ÷ Rimage = Wcontainer ÷ Rimage
} else {
    H' = Hcontainer
    W' = H' x Rimage = Hcontainer x Rimage
}
  cover contain 描述
Rimage ≥ Rcontainer H' = Hcontainer;
W' = H' x Rimage = Hcontainer x Rimage
W' = Wcontainer
H' = W' ÷ Rimage = Wcontainer ÷ Rimage
背景图片是横屏的,width > height
Rimage ≤ Rcontainer W' = Wcontainer
H' = W' ÷ Rimage = Wcontainer ÷ Rimage
H' = Hcontainer
W' = H' x Rimage = Hcontainer x Rimage
背景图片是竖屏的,width < height

要是你感兴趣的话,你可以调整上面示例中背景图片的尺寸,进一步验证上面的公式:

有关于 background-size 属性值 covercontain 计算背景图片更深入的探讨还可以阅读@Vjeux 早年前写的博文《CSS – Contain & Cover》! 注意,该公式也适用于 CSS 的 object-fitmask-size 中的 covercontain 的计算!

还有一点需要注意的是,这里提到的计算公式只适用于具有内在尺寸和比例的背景图片,比如位图。如果我们把背景图片换成 <gradient> 或部分矢量图,就不适用了:

auto

autobackground-size 属性的初始值(默认值),它相当于 auto auto。因为 background-size 显式只设置一个值的话,第二个值则为 auto。给 background-size 设置值时,关键词 auto 可以和 <length-percentage>(即 <length><percentage>) 组合在一起。比如:

.element {
    background-size: auto 100px;
    background-size: 100px auto; /* 等同于 background-size: 100px */

    background-size: auto 50%;
    background-size: 50% auto; /* 等同于 background-size: 50% */
}

但需要注意的是,auto <length-percentage><length-percentage> auto 并不同等同的,而 background-size 仅取一个 <length-percentage> 值时,它等同于 <length-percentage> auto

正如前面的示例所示,background-size 取值为 auto 时,同样对背景图片尺寸的计算会有一定的影响。这跟运用于 background-image<image> 类型有关。

如果<image> 是一张位图,它有内在的尺寸和宽高比,那么background-size 取值为 auto 时,将会以背景图片的内在尺寸渲染

.element {
    --size: auto;
    --image: url('1600_900.jpg');
    width: min(100%, 600px);
    aspect-ratio: 16 / 9;
    background: var(--image) left top / var(--size) no-repeat;
}

运用于 .element 的背景图是一张 1600px x 900px.jpg(位图),它有内在的尺寸和宽高比,因此,background-size取值为 auto时,就相当于给background-size 设置的值是 1600px 900px。即按背景图片原始尺寸渲染,溢出背景层的图片会被剪切(注意,背景图片裁剪区域和background-position以及background-origin取值都有一定的影响)。反之,如果背景图片尺寸小于元素背景层尺寸,且background-repeatno-repeat时,背景图片则不会铺满背景层(如果未显式指定background-color的值,则背景图片尺寸之外区域为透明区域):

.element {
    --image: url(200_200.jpg);
}

如果<image>没有内在尺寸和宽高比,比如<gradient>,那么background-size取值为 auto 时,将会以元素背景层大小来渲染

.element {
    --image: linear-gradient(to bottom, #09f, #f90, #09a);
}

正如上图所示,像 <gradient> 类型的 <image>background-size: auto就像和 background-size: 100% 100%相似,它也会受 background-origin 属性指定的 <box> 类型影响:

.element {
    --image: linear-gradient(to bottom, #09f, #f90, #09a);
    border: 20px solid rgb(200 30 120 / .5);
    padding: 20px;
}

另外,

  • 如果<image>没有固有尺寸但是有固有比例,效果同 contain
  • 如果<image>有一个长度与比例,则由此长度与比例计算大小
  • 如果<image>有一个长度但是没有比例,则使用此长度与背景区相应的长度

上面是 background-size 值都是 auto 时,背景图片的计算方式。接下来,再来看background-size 有一个值是auto,另一个值是 <length-percentage> 时背景图片的计算。比如:

.element {
    background-size: 100px auto; /* <length> auto */
    background-size: 50% auto; /* <percentage> auto */

    /* 等同于 */
    background-size: 100px;
    background-size: 50%;
}

.element {
    background-size: auto 100px; /* auto <length> */
    background-size: auto 50%; /* auto <percentage> */
}

background-size 属性只有一个值是auto,另一个值是 <length-percentage> 时,背景图片尺寸的计算也和 <image> 类型有关。

同样先看具有内在尺寸和宽高比的位图。

.element {
    --image: url(1600_900.jpg);
    --size: auto 75%;
}

背景图片1600_900.jpg,它的内在尺寸是 1600px x 900px,即 width=1600pxheight=900px,其宽高比是 ratio = 16 : 9。如果将其放置在一个600px x 300px的容器中(.element)中,此时auto 75%会致使背景图片:

  • 背景图片的高度是 <box> 的高度的 75%<box>类型由background-origin来决定,默认为padding-box),该例的<box>高度为 300px,相应的背景图片高度则会是 h' = 300px x 75% = 225px
  • 背景图片的宽高比是 16:9,即width ÷ height = 16 ÷ 9 = 1.777778,计算出来的背景图片宽度将会是 w' = h' x ratio = 225px x 1.777778 = 400px

计算之后背景图片尺寸就成了 400px x 225px

如果将 background-size 的值从 auto 75% 换成 75% auto

.element {
    --image: url(1600_900.jpg);
    --size: 75% auto;
}

此时,背景图片尺寸的计算:

  • 背景图片的宽度是 <box> 的宽度的 75%,该例的<box>宽度是 600px,相应的背景图片的宽度则会是 w' = 600px x 75% = 450px
  • 背景图片的宽高比是 16:9,约为1.777778,背景图片的高度则会是 h' = w' ÷ ratio = 450px ÷ 1.777778 = 253.15px(约253px

即,计算之后背景图片尺寸就是 450px x 253px

上面这个计算方式同样适用于 auto <length><length> auto,不同之处,使用<length>对应的就是指定方向的背景图片尺寸(如果是相对单位,也需要计算,但它的计算不再是相对于背景图片或<box>,比如vw相对的是视窗宽度,rem相对的是根元素的font-size等)。

.element {
    --size: auto 100px;
}

.element {
    --size: 100px auto;
}

从这四个示例,我们不难获知:

如果background-size取值为 auto,另一个值为<length-percentage>值时,且背景图片有内在尺寸和宽高比,则指定的长度使用指定值,未指定的长度由指定值与固有比例计算

那么,要是<image>没有内在尺寸或宽高比呢?比如前面的 <gradient>绘制的背景图像。比如:

.element {
    --image: linear-gradient(to bottom, #09f, #f90, #09a);
    --size: auto 75%; 
}

.element {
    --image: linear-gradient(to bottom, #09f, #f90, #09a);
    --size: 75% auto; 
}

.element {
    --image: linear-gradient(to bottom, #09f, #f90, #09a);
    --size: auto 10vw; 
}

.element {
    --image: linear-gradient(to bottom, #09f, #f90, #09a);
    --size: 10vw auto; 
}

也就是说,如果图像没有固有比例,则指定的长度使用指定值,未指定的长度使用图像相应的固有长度,若没有固有长度,则使用背景区相应的长度

最后有一点需要额外提出来的是,background-repeat 取值为 round 时,它对背景图片尺寸计算是有一定影响的(除background-size属性值为cover之外都会有影响)。即,如果background-repeat设置了round(可以是一个维度,也可以是两个维度),那么浏览器必须在该维度(或两个维度)中缩放背景图片,以使其适合背景定位区域的整数倍。

/**
*   W  » 元素背景层的宽度
*   H  » 元素背景层的高度
*   w' » background-size计算后的图片宽度
*   h' » background-size计算后的图片高度
*   x  » x轴方向要重复铺放背景图的数量
*   y  » y轴方向要重复铺放背景图的数量
* */

// » x轴 » 沿着元素背景层 x 轴方向(水平方向)
W ÷ w' =  Math.floor(W / w') = x

// » y轴 » 沿着元素背景层 y 轴方向(垂直方向)
H ÷ h' = Math.floor(H / h') = y

如果 background-repeat 只有一个维度设置了 round,并且background-size有一个值是auto时,背景图片的还需要根据图片宽高比进行缩放。

介绍完background-size属性之后,那么关于 background 所有子属性就全部介绍完了。接下来,我们来聊多背景!

多背景

CSS 中的多背景指的是在同一个元素拥有多个背景层。从background的语法上可以获知,该属性是支持多个背景的,并且以逗号(,)分隔。

background: [<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>

来看一个简单的示例:

body {
    --desk: url("https://www.w3cplus.com/sites/default/files/blogs/2022/2208/desk2.svg");
    --cake: url("https://www.w3cplus.com/sites/default/files/blogs/2022/2208/cake.svg");

    background: 
        var(--cake) center bottom 55vh / 20vh auto no-repeat,
        #557 var(--desk) center bottom / 80vh 80vh no-repeat;
}

这个示例中,在body元素上使用了两个背景层,每个背景层定位和大小都不一样。

这是多背景基本用法。你也可以把简写属性拆分成子属性,比如上面示例等同于:

body {
    --desk: url("https://www.w3cplus.com/sites/default/files/blogs/2022/2208/desk2.svg");
    --cake: url("https://www.w3cplus.com/sites/default/files/blogs/2022/2208/cake.svg");
    background-image: var(--cake), var(--desk);
    background-position: center bottom 55vh, center bottom;
    background-size: 20vh auto, 80vh 80vh;
    background-repeat: no-repeat; /* 也可以写成 no-repeat, no-repeat */
    background-color: #557;
}

在使用多背景的时候,需要注意的是:

如果使用简写属性background,并且需要显式设置背景颜色,那么颜色只能设置在 <final-bg-layer>层,不能设置在<bg-layer>层,否则背景颜色会渲染为transparent。比如:

body {
    background: 
        #557 var(--cake) center bottom 55vh / 20vh auto no-repeat,
        var(--desk) center bottom / 80vh 80vh no-repeat;

    /* 或 */

    background: 
        #557 var(--cake) center bottom 55vh / 20vh auto no-repeat,
        #557 var(--desk) center bottom / 80vh 80vh no-repeat;
}

像上面这样写法,浏览器计算出来的background-colortransparentbackground-imagenonebackground-sizeautobackground-positionleft top

简单地说,浏览器将会把background的所有子属性的计算值重置为各属性的初始值,等同于我们设置的background是无效的。所以大家在使用多背景,并且需要显式设置背景颜色时,它只能设置在 <final-bg-layer>层。除此之外,你还可以在简写属性的后面单独声明background-color属性:

body {
    background: 
        var(--cake) center bottom 55vh / 20vh auto no-repeat,
        var(--desk) center bottom / 80vh 80vh no-repeat;
    background-color: #557;
}

如果使用的是子属性时,某个子属性对应所有背景层的值相同时,可以只写一个。比如上例中的background-repeat属性,它在两个背景层中都是no-repeat,所以可以只写一个。但需要注意,只要有一个层的值不同,都不能简写。

多背景的堆叠顺序

熟悉 CSS 层叠上下文的同学都知道,当元素触发了层叠上下文时,可以使用 z-index 来控制它的层叠顺序。在同一个元素使用多背景时,它也会发生堆叠,只不过它的顺序不能使用 z-index 来控制,或者有一个类似background-index这样的属性。多背景中的堆叠顺序是依赖于背景层出现的先后顺序来决定的,越早出现越在上面。或者说根据向右原则来判断,越靠左的越在上面,越靠右则越在下面。比如上面的示例:

body {
    background: 
        var(--cake) center bottom 55vh / 20vh auto no-repeat,
        #557 var(--desk) center bottom / 80vh 80vh no-repeat;
}

最先出现(最左)的是--cake(蛋糕)背景层,接着是--desk(桌子)背景层,因此“蛋糕”(--desk)背景层是在“桌子”(--desk)背景层上面,最底下则是背景颜色(如果有的话):

反过来,把上面的代码调整一下:

body {
    background: 
        var(--desk) center bottom / 80vh 80vh no-repeat,
        #557 var(--cake) center bottom 55vh / 20vh auto no-repeat;
}

现在最先出现(最左)的是 --desk(桌子)背景层,接着是 --cake(蛋糕)背景层,因此“桌子”(--desk)背景层是在“蛋糕”(--cake)背景层上面,最底下则是背景颜色#557层:

也就是说,多背景的堆叠顺序是像下图这样:

使用多背景时,如果多个背景的大小相同,位置也相同,则其中一个将覆盖另一个背景;如果第一个背景图片尺寸大于或等于容器尺寸,则会覆盖其他背景,在整个容器中只能看到一个背景。

多背景的案例

多背景在Web开发中还是很广泛的。前面的示例中已经有多背景的身影,比如前面介绍提到的渐变边框:

div {
    --gradient: linear-gradient(red, gold);
    border: 10px solid transparent;
    background-image: linear-gradient(#222, #222), var(--gradient);
    background-origin: border-box;
    background-clip: padding-box, border-box;
}

常常使用多个<gradient>绘制的背景,来绘制一些背景图案:

.card {
    border-radius: 12px 72px 12px 12px;
    border: 1px solid transparent;
    background-repeat: no-repeat;
    background-size: 28px 28px, 49px 49px, 103px 103px, cover;
    background-position: calc(100% - 20px) calc(100% - 20px),
        calc(100% + 10px) calc(100% + 10px), -34px -28px, 0 0;
    background-image: radial-gradient(
        circle,
        rgba(255, 255, 255, 0.2),
        rgba(255, 255, 255, 0.2) 70%,
        rgba(255, 255, 255, 0) 70%
        ),
        radial-gradient(
        circle,
        rgba(255, 255, 255, 0.2),
        rgba(255, 255, 255, 0.2) 70%,
        rgba(255, 255, 255, 0) 70%
        ),
        radial-gradient(
        circle,
        rgba(255, 255, 255, 0.3),
        rgba(255, 255, 255, 0.3) 70%,
        rgba(255, 255, 255, 0) 70%
        ),
        linear-gradient(180deg, #ffe3ae 0%, #ffd34f 100%);
    border-color: rgba(184, 112, 0, 0.28);
    box-shadow: 0 8px 12px 0 rgba(234, 165, 55, 0.3),
        inset 0 1px 3px 0 rgba(255, 255, 255, 0.5);
    color: #870000;
}

在一些 Web 游戏上也可以使用多背景来构建(游戏的场景是较为复杂的),比如 LostGarden:

我将这个复杂示例简化了一下,比如构建下面这样的一个游戏场景:

你需要一些游戏素材包,你可以点击这里下载,该素材包是由lostgarden官网提供的

关键代码如下:

:root {
    --grass: url("https://www.w3cplus.com/sites/default/files/blogs/2022/2208/grass-block.png");
    --rock: url("https://www.w3cplus.com/sites/default/files/blogs/2022/2208/rock.png");
    --tree-short: url("https://www.w3cplus.com/sites/default/files/blogs/2022/2208/tree-short.png");
    --tree-tall: url("https://www.w3cplus.com/sites/default/files/blogs/2022/2208/tree-tall.png");
    --sky: linear-gradient(to bottom, #2b8ce7, #73b9f5);
    }

header {
    width: 100%;
    height: 250px;
    overflow: hidden;
    position: relative;
    background-image: 
        var(--rock), 
        var(--tree-short), 
        var(--tree-short),
        var(--tree-tall), 
        var(--tree-tall), 
        var(--grass), 
        var(--sky);
    background-position: 80% 50%, 90% 10%, 10% 90%, 40% top, 45% 10%, left bottom,left top;
    background-repeat: no-repeat, no-repeat, no-repeat, no-repeat, no-repeat,repeat-x, repeat-x;

    animation: move 3s ease 1;
}

@keyframes move {
    0% {
        background-position: 0 0, 0 0, 0 0, 0 0, 0 0, left bottom, left top;
    }
    100% {
        background-position: 80% 50%, 90% 10%, 10% 90%, 40% top, 45% 10%,left bottom, left top;
    }
}

注意,这里我们使用了CSS自定义属性来管理每个背景。示例中,使用了 CSS 的 @keyframesanimation 给背景提供了动画效果。有关于背景动效,我们单独放到后面一个章节来介绍。

利用多背景以及添加一点CSS动效,就可以制作一个视差效果。比如 @carpenumidium 在 Codepen上提供的示例:

尤其是有了 CSS 混合模式 特性,拥有多个背景是令人兴奋的。可以实现更多有创意的效果,比如 @Dre 提供的一个案例,就是多背景与混合模式结合在一起带来的效果

更为有意思的是,@Jimmy Chion 在他的博文《Grainy Gradients》中使用多背景、混合模式或CSS滤镜实现带有颗粒状的渐变、甚至是带有颗粒状的阴影效果:

是不是很有意思。你是不是也想知道多背景和混合模式为什么能做出这样有创意的效果。如果是的话,请接着往下阅读,我们接下来就要和大家一起探讨这方面的话题!

待续...

返回顶部