Skip to content

响应式 Web 设计(Responsive Web Design)已然成为 现代 Web 布局 的主流布局技术之一,它可以让你构建出一个更能适配不同终端设备的 Web 应用(或页面)。在构建一个具有响应式能力的 Web 应用(或页面)时,其中图片的响应一直以来都很棘手,因为图片很容易被拉伸或挤压,甚至还会造成布局上的偏移。

如果你也碰到类似的现象,应该如何去避免,或者做得更好一些呢?你可以在接下来的内容中找到相应的答案。

如何给 Web 页面添加图片?

到目前为止,给 Web 页面上添加图片的方式是越来越丰富了,大家最为熟悉的是在 HTML 中直接使用 <img /> 标签元素给 Web 页面添加图片:

HTML
<img src="thumbnail.jpg" alt="" />

除此之外,HTML 的 <img> 标签新增了 srcsetsizes 等新属性,还新增了 <picture> 标签元素。让 Web 开发者有了更多的选择:

img

注意,有关于 <img> srcset sizes 特性以及 <picture> 已超出本节课的范畴,因此不在这里做过多阐述。感兴趣的同学,可以自己搜索相应关键词进行扩展阅读!

除了使用 HTML 的 <img><picture> 标签元素给 Web 添加图片之外,还可以在 CSS 中使用 background-image 属性的 url()image-set() 函数给 Web 添加图片:

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

这么多技术,我们在实际使用的时候应该怎么选择呢?如果你不好做出选择,可以尝试使用下图的流程:

img

Web 图片呈现的常见现象

任何图片都有自己的原始尺寸,这个尺寸是设计师导出图片的原始尺寸:

img

如果我们在 HTML 的 img 元素和 CSS 中,都没对该元素显式设置 widthheight(或同时设置),那么浏览器将会识别的是 imgsrc 引入的图像源的原始尺寸大小:

HTML
<img src="./grapefruit-slice-332-332.jpg" alt="切好的血橙" />

img

我们再来看另一个场景,在 <img> 中显式设置了 widthheight(或两者同时设置):

HTML
<img src="./grapefruit-slice-332-332.jpg" alt="切好的血橙" width="180" />

这个时候 <img> 在浏览器渲染出来的 width 是在该元素中显式设置的 width ,而 height 会根据图像的宽高比例做出相应的计算:

img

在上面示例中,我们采用的是一个正方形图形,我们换一张不是正方形的图形来看看浏览器对其计算的方式,比如下面这张图:

img

该图像的原始尺寸是 632 x 475 (像素),我们在使用的时候,显式在 <img> 中设置了 width=300

HTML
<img src="./cat-632x475.jpg" alt="一只灰色的猫" width="300" />

浏览器渲染出来的尺寸是 300 x 225

img

简单来看看其计算原理。示例中图像的原始尺寸是 632 x 475,即:

Plaintext
› nW = 632
› nH = 475
› R = nW ÷ nH = 632 ÷ 475 = 1.3305263157894738

示例中显式给 img 设置了 width="300",根据上面的公式可以计算出 height 的值:

Plaintext
› R = nW ÷ nH
› H = W ÷ R = W ÷ nW × nH
› H = 300 ÷ 632 × 475 = 225.47

上面公式计算出来的结果和浏览器渲染出来的结果是一样的。我们再来看另一情景,显式的在 img 中设置 height,比如:

HTML
<img src="./cat-632x475.jpg" alt="一只灰色的猫" height="220" />

同样根据上面的公式来推导出 width 的值:

Plaintext
› R = nW ÷ nH
› nW = 632
› nH = 475
› w = R × h = nW ÷ nH × h
› w = 632 ÷ 475 × 220 = 292.72

结果和浏览器渲染出来的是一致的,即 width="293"

img

很多时候,也会同时显式地给 <img> 设置 widthheight,但是,在同时给 <img> 设置 widthheight 时,如果值不是按照比例设置的话(图像源尺寸比例),你会发现图像会有扭曲的现象:

HTML
<img src="./cat-632x475.jpg" alt="一只灰色的猫" width="300" />

<img src="./cat-632x475.jpg" alt="一只灰色的猫" height="180" width="300" />

img

正如上面的示例所示,如果显式地给 <img> 元素同时添加 widthheight 时,无法确认是否会对图像造成扭曲或变形,建议给它们设置图像的原始尺寸。

除了在 <img> 元素上显式设置 widthheight 属性来指定图像大小之外,还可以通过 CSS 的 widthheight 属性指定。我们来看看:

HTML
<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

CSS
img {
    width: 200px;
}

我们来看一下结果:

img

从上图的结果我们可以得知,CSS 中的 widthheight 会覆盖 <img> 元素中的 widthheight。如果 <img> 元素中未显式设置 widthheight,那么 CSS 就会重新计算图像的尺寸。

CSS 中只显式给 img 设置 widthheight 其中一个属性的话,它也会根据图片的宽高比来计算,比如上例中的第一张图像和第三张图像,最终计算出来的 height150px

Plaintext
› R = nW ÷ nH
› H = W ÷ R = W ÷ nW × nH
› H = 200 ÷ 632 × 475 = 150.316

如果在 <img> 元素中显式设置了 widthhegiht 的值,但只在 CSS 中显式设置了其中一个值的话,有可能会对图像造成扭曲、拉伸等现象,比如上例中的第二张图像。对于这种现象,你又不想去手动计算 height 的值,那么可以考虑在 CSS 中显式设置 height:auto

CSS
img {
    width: 200px;
    height: auto;
}

CSS中的 height:auto 将会覆盖 <img> 元素属性 height 的值,从而让图像不产生扭曲,拉伸等变形:

img

这个原理同样的可以用于 width 属性值按 height 值来做自动计算。

正如你所看到的,Web 图片除了会被挤压变形之外,还会因尺寸不匹配变得模糊。比如将一张 100px × 100px 的图片放置一个 500px × 500px 容器中,或者同时设置 widthheight 值比图片原始尺寸大 ,都会致使图片变得模糊:

img

图片比例

简单地说,Web 图片呈现有这些乱象(变形、挤压和模糊等),主要原因是,设置图片尺寸时和原始尺寸比例(宽高比)不相匹配。所以说,要避免这些乱象,我们就需要按比例调整图片尺寸。而在 CSS 中,我们有多种方式可以让图片根据相应比例自动调整尺寸。

以 Web 中最常见的 UI 为例(用户信息),让大家更好地掌握开发过程中如何避免图片的变形、挤压和模糊等现象:

img

构建这样的用户信息卡片,你可能会用到像下面这样的 HTML :

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>
CSS
.card img {
    display: block;
    border-radius: 50%;
    border: 4px solid #EAF1FC;
    box-shadow: 0 0 .2em .25em rgb(0 0 0 / .0512);
}

img

Demo 地址:https://codepen.io/airen/full/GRXOomr

图片看上去没啥问题,因为图片的原始宽高比就是 1:1 ,并且 img 上的 widthheight 设置的宽高比例也是 1:1

可现实中,输出的图片不总是能按照你所期望的尺寸输出。

img

比如,把上图这张非 1:1 的图片替换掉上面示例中 <img>src ,你会发现,图片被挤压变形:

img

Demo 地址:https://codepen.io/airen/full/mdGqVzw

卡片中的用户头像是一个圆形的效果,因此给 <img />widthheight 指定了一个相等的值 136px 。把一张非 1:1 的图片放入了一个 1:1 的容器当中,势必造成图片被容器挤压而变形。

那该怎么办呢?假设我们图片的宽高比是 8:3 ,那我们是不是把 <img> 的宽高比也调整成 8:3 就可以避免图片挤压变形?

HTML
<div class="card">
  <img src="https://picsum.photos/800/300?random=1" alt=""  />
  <!-- 其他 HTML 标签 -->
</div>
CSS
.card img {
    width: 136px;
    aspect-ratio: 8 / 3;
}

img

Demo 地址: https://codepen.io/airen/full/PodOLZg

正如你所看到的,事情并非那么容易。针对于这些情况,如果 Web 页面上只是一两个地方,Web 开发者可以考虑先在图片软件中处理好图片,然后再放到 Web 页面上。但要是很多地方都是这样,或者 Web 开发者无法控制输出的图片时,在设计软件中重新处理图片尺寸就会变得非常不现实。

庆幸的是,CSS 中为 img 提供了 object-fitobject-position 两个属性,可以帮助 Web 开发者更好处理图片的适配:

CSS
.card img {
    width: 136px;
    aspect-ratio: 1;
    object-fit: cover;
    object-position: center;
}

img

Demo 地址:https://codepen.io/airen/full/poOdYNb

object-fit 可以决定图片的填充方式,其对应的几个值效果如下图所示:

img

对于背景图片,也有一个相似的属性 background-size

background-size 取值为 covercontain 的表现与 object-fit 取值为 covercontain 相同。但 object-fit 不能像 background-size 那样,取 <length-percentage> 值。

大家需要注意的是,background-sizeobject-fit 包括从未介绍过的 mask-sizecovercontain 值时,它的计算都是复杂的。它们如何计算已经超出我们这个课程的范畴了,这里不详细阐述。但是为了满足大家的好奇心,我把 covercontain 计算所涉及到的公式告诉大家。就拿 background-size 为例吧:

img

background-size 取值为 cover 时,背景图片的尺寸计算:

Plaintext
/** 
* 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 的效果:

img

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

Plaintext
/** 
* 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 
}
covercontain描述
Rimage ≥ RcontainerH' = Hcontainer;``W' = H' x Rimage = Hcontainer x RimageW' = Wcontainer;``H' = W' ÷ Rimage = Wcontainer ÷ Rimage背景图片是横屏的,width > height
Rimage ≤ RcontainerW' = Wcontainer;``H' = W' ÷ Rimage = Wcontainer ÷ RimageH' = Hcontainer;``W' = H' x Rimage = Hcontainer x Rimage背景图片是竖屏的,width < height

img

Demo 地址:https://codepen.io/airen/full/dyqZrJQ

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

注意,CSS 的 object-position 的使用与 background-position 相似。

在 Web 布局中,除了会因图片尺寸比例与容器比例不一致造成图片挤压变形之外,还会对 Web 布局造成偏移。很多时候,我们需要像下图这样的布局:

img

Demo 地址:https://codepen.io/airen/full/BaOmbVN

对应的布局:

HTML
<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>
CSS
.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;
}

万一服务端输出的图片尺寸不一致,上面的布局方式是没有任何防御式可言的,会因为图片的尺寸比例不同,造成布局的偏移:

HTML
<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>

img

Demo 地址:https://codepen.io/airen/full/LYJOaMM

在 CSS 中,我们可以给图片指定一个 aspect-ratio 值,避免源图片因尺寸比例不一致,造成布局的偏移:

CSS
.card img {
    display: block;
    max-width: 100%;
    aspect-ratio: 4 / 3;
    border-radius: 20px 20px 0 0;
    object-fit: cover;
    objeft-position: center;
}

img

Demo 地址:https://codepen.io/airen/full/ExebMzp

正如你所看到的,图片上的 aspect-ratio: 4 / 3 可以使图片根据宽度比来呈现图片,让你的代码更具防御性。

需要注意的是,CSS 的 aspect-ratio 需要和 widthinline-size),或 heightblock-size),或 min-widthmin-inline-size),或 min-heightmin-block-size),或 max-heightmax-block-size)中的一个属性结合在一起使用。如果同时和元素的宽高一起使用的话,那么 aspect-ratio 将不会起任何作用。

另外,Web 开发过程中,图片 <img><picture> 会常常放在一个容器中,比如:

HTML
<figure>
    <img src="https://picsum.photos/800/300?random=1" alt="" />
</figure>

很多时候,Web 开发者并不知道图片容器 figure 尺寸。Web 开发者也无法根据图片容器尺寸来指定自身的尺寸。因此,Web 开发者需要一个更好的方式,让图片变得更为灵活,能自动匹配图片容器宽度。在 CSS 中,我们可以通过 max-width: 100% 来实现:

CSS
img {
    display: block;
    max-width: 100%;
    height: auto;
}

Demo 地址:https://codepen.io/airen/full/rNZYbxr

在此基础上,我们还可以给 img 添加 object-fit: cover; ,可以在维持原图片比例下,填满整个容器:

CSS
img {
    display: block;
    max-width: 100%;
    height: auto;
    object-fit: cover;
}

最后再来看大容器装小图片的情景。

CSS
.card {
    width: 500px;
    aspect-ratio: 1;
}

.card img {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
    objeft-position: center;
}

img

Demo 地址: https://codepen.io/airen/full/dyqZLOb

你已看到了, 100px x 100px 的源图放到了一个 500px x 500px 的容器中被拉伸了,图片也变得模糊了(像素失真)。

为了避免这种现象,Web 开发者在开发过程中会要求重新输出大于或等于容器尺寸的图片。

其实,CSS 中提供了一个新的属性 image-rendering ,该属性可以指定图片在缩放状态下的渲染算法。简单地说,image-rendering 的作用是在图像缩放时,提供不一样的渲染方式,让图片的展示形态更为多样化,或者说是尽可能减少图片的失真带来的信息损耗

img

Demo 地址:https://codepen.io/airen/full/ZEMaZvq

  • auto:使用双线性算法进行重新采样(高质量);
  • smooth:使用能最大化图像客观观感的算法来缩放图像;
  • high-quality:与 smooth 相同,但更倾向于高质量的缩放;
  • crisp-edges:必须使用可有效保留对比度和图像中的边缘的算法来对图像进行缩放,并且,该算法既不会平滑颜色,又不会在处理过程中为图像引入模糊。此属性值适用于像素艺术作品,例如一些网页游戏中的图像;
  • pixelated:放大图像时,使用最近邻居算法,因此,图像看着像是由大块像素组成的。缩小图像时,算法与 auto 相同。

你可能发现了,image-renderingpixelated 值的效果更好,这是由于示例中的原图是一张偏向于矢量的图片,细节不多,对于高精度的人物图,就不太适用于 pixelated,容易把图片马赛克化。

其实,根据 W3C 规范中对 image-rendering 属性值的描述来看,希望在放大后让图片尽可能不失真的是 crisp-edges ,不幸运的是,到目前为止,还没有得到浏览器的实现。

小结

在这节课中,我们主要介绍了 imgpicture 以及 background-image 使用过程中常会遇到的情景,图片的尺寸、宽高比等,在图片的使用过程中是非常重要的。在现代 Web 布局中,大家对响应式 Web 设计很相当重视,因此,我们在编码的时候,就需要考虑图片的灵活性、可适配性这些细节。

正如课程中所介绍的,我们可以使用 CSS 的几个新特性,让全域的图片能满足现代 Web 布局所需:

  • aspect-ratio :控制图片或图片容器的宽高比,避免产生布局的偏移和抖动;
  • object-fit :控制图片填充容器的方式,避免图片的扭曲与变形;
  • image-rendering :控制图片在缩放状态下的渲染算法。

合理利用它们,可以使 Web 上的图片有更好的效果,给用户带来更好的体验。