响应式 Web 设计(Responsive Web Design)已然成为 现代 Web 布局 的主流布局技术之一,它可以让你构建出一个更能适配不同终端设备的 Web 应用(或页面)。在构建一个具有响应式能力的 Web 应用(或页面)时,其中图片的响应一直以来都很棘手,因为图片很容易被拉伸或挤压,甚至还会造成布局上的偏移。
如果你也碰到类似的现象,应该如何去避免,或者做得更好一些呢?你可以在接下来的内容中找到相应的答案。
如何给 Web 页面添加图片?
到目前为止,给 Web 页面上添加图片的方式是越来越丰富了,大家最为熟悉的是在 HTML 中直接使用 <img />
标签元素给 Web 页面添加图片:
<img src="thumbnail.jpg" alt="" />
除此之外,HTML 的 <img>
标签新增了 srcset
、sizes
等新属性,还新增了 <picture>
标签元素。让 Web 开发者有了更多的选择:
注意,有关于
<img>
的srcset
和sizes
特性以及<picture>
已超出本节课的范畴,因此不在这里做过多阐述。感兴趣的同学,可以自己搜索相应关键词进行扩展阅读!
除了使用 HTML 的 <img>
和 <picture>
标签元素给 Web 添加图片之外,还可以在 CSS 中使用 background-image
属性的 url()
和 image-set()
函数给 Web 添加图片:
.box {
width: 300px;
aspect-ratio: 4 / 3;
}
.background-image {
background-image: url('puppy.png');
}
.image-set {
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
);
}
也就是说,到目前为止,在 Web 开发中添加图片,我们有多种方式:
- 使用
<img>
和它的sizes
及srcset
属性,加载不同尺寸的图片,并且可以针对用户的使用环境,指定图片呈现时的尺寸; - 使用
<picture>
,在<source>
标签元素上使用srcset
、sizes
、media
、type
加载不同图片,并且可以针对用户的使用环境,加载不同图片; - 使用
image-set()
函数,可以像<img>
和<picture>
,在 CSS 中使用不同图片。
这么多技术,我们在实际使用的时候应该怎么选择呢?如果你不好做出选择,可以尝试使用下图的流程:
Web 图片呈现的常见现象
任何图片都有自己的原始尺寸,这个尺寸是设计师导出图片的原始尺寸:
如果我们在 HTML 的 img
元素和 CSS 中,都没对该元素显式设置 width
或 height
(或同时设置),那么浏览器将会识别的是 img
的 src
引入的图像源的原始尺寸大小:
<img src="./grapefruit-slice-332-332.jpg" alt="切好的血橙" />
我们再来看另一个场景,在 <img>
中显式设置了 width
或 height
(或两者同时设置):
<img src="./grapefruit-slice-332-332.jpg" alt="切好的血橙" width="180" />
这个时候 <img>
在浏览器渲染出来的 width
是在该元素中显式设置的 width
,而 height
会根据图像的宽高比例做出相应的计算:
在上面示例中,我们采用的是一个正方形图形,我们换一张不是正方形的图形来看看浏览器对其计算的方式,比如下面这张图:
该图像的原始尺寸是 632 x 475
(像素),我们在使用的时候,显式在 <img>
中设置了 width=300
:
<img src="./cat-632x475.jpg" alt="一只灰色的猫" width="300" />
浏览器渲染出来的尺寸是 300 x 225
:
简单来看看其计算原理。示例中图像的原始尺寸是 632 x 475
,即:
› nW = 632
› nH = 475
› R = nW ÷ nH = 632 ÷ 475 = 1.3305263157894738
示例中显式给 img
设置了 width="300"
,根据上面的公式可以计算出 height
的值:
› R = nW ÷ nH
› H = W ÷ R = W ÷ nW × nH
› H = 300 ÷ 632 × 475 = 225.47
上面公式计算出来的结果和浏览器渲染出来的结果是一样的。我们再来看另一情景,显式的在 img
中设置 height
,比如:
<img src="./cat-632x475.jpg" alt="一只灰色的猫" height="220" />
同样根据上面的公式来推导出 width
的值:
› R = nW ÷ nH
› nW = 632
› nH = 475
› w = R × h = nW ÷ nH × h
› w = 632 ÷ 475 × 220 = 292.72
结果和浏览器渲染出来的是一致的,即 width="293"
:
很多时候,也会同时显式地给 <img>
设置 width
和 height
,但是,在同时给 <img>
设置 width
和 height
时,如果值不是按照比例设置的话(图像源尺寸比例),你会发现图像会有扭曲的现象:
<img src="./cat-632x475.jpg" alt="一只灰色的猫" width="300" />
<img src="./cat-632x475.jpg" alt="一只灰色的猫" height="180" width="300" />
正如上面的示例所示,如果显式地给 <img>
元素同时添加 width
和 height
时,无法确认是否会对图像造成扭曲或变形,建议给它们设置图像的原始尺寸。
除了在 <img>
元素上显式设置 width
、height
属性来指定图像大小之外,还可以通过 CSS 的 width
和 height
属性指定。我们来看看:
<img src="./cat-632x475.jpg" alt="一只灰色的猫" />
<img src="./cat-632x475.jpg" alt="一只灰色的猫" height="300" />
<img src="./cat-632x475.jpg" alt="一只灰色的猫" width="300" />
<img src="./cat-632x475.jpg" alt="一只灰色的猫" height="180" width="300" />
只是在 CSS 中显式设置了 width:200px
:
img {
width: 200px;
}
我们来看一下结果:
从上图的结果我们可以得知,CSS 中的 width
或 height
会覆盖 <img>
元素中的 width
或 height
。如果 <img>
元素中未显式设置 width
或 height
,那么 CSS 就会重新计算图像的尺寸。
CSS 中只显式给 img
设置 width
或 height
其中一个属性的话,它也会根据图片的宽高比来计算,比如上例中的第一张图像和第三张图像,最终计算出来的 height
是 150px
:
› R = nW ÷ nH
› H = W ÷ R = W ÷ nW × nH
› H = 200 ÷ 632 × 475 = 150.316
如果在 <img>
元素中显式设置了 width
和 hegiht
的值,但只在 CSS 中显式设置了其中一个值的话,有可能会对图像造成扭曲、拉伸等现象,比如上例中的第二张图像。对于这种现象,你又不想去手动计算 height
的值,那么可以考虑在 CSS 中显式设置 height:auto
。
img {
width: 200px;
height: auto;
}
CSS中的 height:auto
将会覆盖 <img>
元素属性 height
的值,从而让图像不产生扭曲,拉伸等变形:
这个原理同样的可以用于 width
属性值按 height
值来做自动计算。
正如你所看到的,Web 图片除了会被挤压变形之外,还会因尺寸不匹配变得模糊。比如将一张 100px × 100px
的图片放置一个 500px × 500px
容器中,或者同时设置 width
和 height
值比图片原始尺寸大 ,都会致使图片变得模糊:
图片比例
简单地说,Web 图片呈现有这些乱象(变形、挤压和模糊等),主要原因是,设置图片尺寸时和原始尺寸比例(宽高比)不相匹配。所以说,要避免这些乱象,我们就需要按比例调整图片尺寸。而在 CSS 中,我们有多种方式可以让图片根据相应比例自动调整尺寸。
以 Web 中最常见的 UI 为例(用户信息),让大家更好地掌握开发过程中如何避免图片的变形、挤压和模糊等现象:
构建这样的用户信息卡片,你可能会用到像下面这样的 HTML :
<div class="card">
<img src="https://picsum.photos/136/136?random=1" alt="" width="136" height="136" />
<h3>大漠__W3cplus</h3>
<ul>
<li><svg></svg>Location</li>
<li><svg></svg>w3cplus.com</li>
</ul>
<button>view profile</button>
</div>
.card img {
display: block;
border-radius: 50%;
border: 4px solid #EAF1FC;
box-shadow: 0 0 .2em .25em rgb(0 0 0 / .0512);
}
图片看上去没啥问题,因为图片的原始宽高比就是 1:1
,并且 img
上的 width
和 height
设置的宽高比例也是 1:1
。
可现实中,输出的图片不总是能按照你所期望的尺寸输出。
比如,把上图这张非 1:1
的图片替换掉上面示例中 <img>
的 src
,你会发现,图片被挤压变形:
卡片中的用户头像是一个圆形的效果,因此给 <img />
的 width
和 height
指定了一个相等的值 136px
。把一张非 1:1
的图片放入了一个 1:1
的容器当中,势必造成图片被容器挤压而变形。
那该怎么办呢?假设我们图片的宽高比是 8:3
,那我们是不是把 <img>
的宽高比也调整成 8:3
就可以避免图片挤压变形?
<div class="card">
<img src="https://picsum.photos/800/300?random=1" alt="" />
<!-- 其他 HTML 标签 -->
</div>
.card img {
width: 136px;
aspect-ratio: 8 / 3;
}
正如你所看到的,事情并非那么容易。针对于这些情况,如果 Web 页面上只是一两个地方,Web 开发者可以考虑先在图片软件中处理好图片,然后再放到 Web 页面上。但要是很多地方都是这样,或者 Web 开发者无法控制输出的图片时,在设计软件中重新处理图片尺寸就会变得非常不现实。
庆幸的是,CSS 中为 img
提供了 object-fit
和 object-position
两个属性,可以帮助 Web 开发者更好处理图片的适配:
.card img {
width: 136px;
aspect-ratio: 1;
object-fit: cover;
object-position: center;
}
object-fit
可以决定图片的填充方式,其对应的几个值效果如下图所示:
对于背景图片,也有一个相似的属性 background-size
:
background-size
取值为cover
和contain
的表现与object-fit
取值为cover
和contain
相同。但object-fit
不能像background-size
那样,取<length-percentage>
值。
大家需要注意的是,background-size
、object-fit
包括从未介绍过的 mask-size
取 cover
和 contain
值时,它的计算都是复杂的。它们如何计算已经超出我们这个课程的范畴了,这里不详细阐述。但是为了满足大家的好奇心,我把 cover
和 contain
计算所涉及到的公式告诉大家。就拿 background-size
为例吧:
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
的效果:
它和 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 |
有一点需要注意的是,这里提到的计算公式只适用于具有内在尺寸和比例的背景图片,比如位图。如果我们把背景图片换成 <gradient>
或部分矢量图,就不适用了。
注意,CSS 的
object-position
的使用与background-position
相似。
在 Web 布局中,除了会因图片尺寸比例与容器比例不一致造成图片挤压变形之外,还会对 Web 布局造成偏移。很多时候,我们需要像下图这样的布局:
对应的布局:
<div class="cards">
<div class="card">
<figure><img src="https://picsum.photos/800/600?random=1" alt="" /></figure>
<h3>图片比例</h3>
<p>图片描述...</p>
</div>
<!-- Card -->
</div>
.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(min(100% - 2rem, 320px), 1fr));
place-content: center;
padding: 1rem;
gap: 1rem;
}
.card {
display: grid;
gap: 1rem;
}
.card img {
display: block;
max-width: 100%;
height: auto;
object-fit: cover;
objeft-position: center;
}
万一服务端输出的图片尺寸不一致,上面的布局方式是没有任何防御式可言的,会因为图片的尺寸比例不同,造成布局的偏移:
<div class="cards">
<div class="card">
<figure><img src="https://picsum.photos/800/300?random=1" alt="" /></figure>
<!-- 其他 -->
</div>
<div class="card">
<figure><img src="https://picsum.photos/1200/600?random=1" alt="" /></figure>
<!-- 其他 -->
</div>
<div class="card">
<figure><img src="https://picsum.photos/800/300?random=1" alt="" /></figure>
<!-- 其他 -->
</div>
</div>
在 CSS 中,我们可以给图片指定一个 aspect-ratio
值,避免源图片因尺寸比例不一致,造成布局的偏移:
.card img {
display: block;
max-width: 100%;
aspect-ratio: 4 / 3;
border-radius: 20px 20px 0 0;
object-fit: cover;
objeft-position: center;
}
正如你所看到的,图片上的 aspect-ratio: 4 / 3
可以使图片根据宽度比来呈现图片,让你的代码更具防御性。
需要注意的是,CSS 的 aspect-ratio
需要和 width
(inline-size
),或 height
(block-size
),或 min-width
(min-inline-size
),或 min-height
(min-block-size
),或 max-height
(max-block-size
)中的一个属性结合在一起使用。如果同时和元素的宽高一起使用的话,那么 aspect-ratio
将不会起任何作用。
另外,Web 开发过程中,图片 <img>
或 <picture>
会常常放在一个容器中,比如:
<figure>
<img src="https://picsum.photos/800/300?random=1" alt="" />
</figure>
很多时候,Web 开发者并不知道图片容器 figure
尺寸。Web 开发者也无法根据图片容器尺寸来指定自身的尺寸。因此,Web 开发者需要一个更好的方式,让图片变得更为灵活,能自动匹配图片容器宽度。在 CSS 中,我们可以通过 max-width: 100%
来实现:
img {
display: block;
max-width: 100%;
height: auto;
}
在此基础上,我们还可以给 img
添加 object-fit: cover;
,可以在维持原图片比例下,填满整个容器:
img {
display: block;
max-width: 100%;
height: auto;
object-fit: cover;
}
最后再来看大容器装小图片的情景。
.card {
width: 500px;
aspect-ratio: 1;
}
.card img {
display: block;
width: 100%;
height: 100%;
object-fit: cover;
objeft-position: center;
}
你已看到了, 100px x 100px
的源图放到了一个 500px x 500px
的容器中被拉伸了,图片也变得模糊了(像素失真)。
为了避免这种现象,Web 开发者在开发过程中会要求重新输出大于或等于容器尺寸的图片。
其实,CSS 中提供了一个新的属性 image-rendering
,该属性可以指定图片在缩放状态下的渲染算法。简单地说,image-rendering
的作用是在图像缩放时,提供不一样的渲染方式,让图片的展示形态更为多样化,或者说是尽可能减少图片的失真带来的信息损耗。
auto
:使用双线性算法进行重新采样(高质量);smooth
:使用能最大化图像客观观感的算法来缩放图像;high-quality
:与smooth
相同,但更倾向于高质量的缩放;crisp-edges
:必须使用可有效保留对比度和图像中的边缘的算法来对图像进行缩放,并且,该算法既不会平滑颜色,又不会在处理过程中为图像引入模糊。此属性值适用于像素艺术作品,例如一些网页游戏中的图像;pixelated
:放大图像时,使用最近邻居算法,因此,图像看着像是由大块像素组成的。缩小图像时,算法与auto
相同。
你可能发现了,image-rendering
取 pixelated
值的效果更好,这是由于示例中的原图是一张偏向于矢量的图片,细节不多,对于高精度的人物图,就不太适用于 pixelated
,容易把图片马赛克化。
其实,根据 W3C 规范中对 image-rendering
属性值的描述来看,希望在放大后让图片尽可能不失真的是 crisp-edges
,不幸运的是,到目前为止,还没有得到浏览器的实现。
小结
在这节课中,我们主要介绍了 img
、picture
以及 background-image
使用过程中常会遇到的情景,图片的尺寸、宽高比等,在图片的使用过程中是非常重要的。在现代 Web 布局中,大家对响应式 Web 设计很相当重视,因此,我们在编码的时候,就需要考虑图片的灵活性、可适配性这些细节。
正如课程中所介绍的,我们可以使用 CSS 的几个新特性,让全域的图片能满足现代 Web 布局所需:
aspect-ratio
:控制图片或图片容器的宽高比,避免产生布局的偏移和抖动;object-fit
:控制图片填充容器的方式,避免图片的扭曲与变形;image-rendering
:控制图片在缩放状态下的渲染算法。
合理利用它们,可以使 Web 上的图片有更好的效果,给用户带来更好的体验。