利用padding和包含块特性,保持DOM元素的宽高比

414 阅读5分钟

在我们开发的过程中,我们很经常有碰到这样的需求,要让某个dom元素始终保持一个固定的宽高比,并且需要随着视窗的宽度 / 高度的变化自适应改变其宽度 / 高度。

直接通过js计算当然能够实现此需求。 我们可以将元素的宽度设置为百分比,在页面resize的时候再通过js获取元素的宽度,并根据宽高比计算出元素的高度。但是,我们先不说此方案的性能问题,但作为一名有追求的切图仔,秉着能用css解决的事情就不要用js的观念,我还是不太能接受这种方式。

如果是纯css方案的话,一般我们会使用媒体查询或者通过vh/vw + calc来动态计算样式,但这两种方案都有一定的限制。

媒体查询

媒体查询只能在屏幕宽高达到指定的阈值时才会触发计算,自适应效果不理想。如下面这个例子只有在屏幕宽度为1365px1680px1920px的情况下会触发样式的计算。


@media (max-width: 1365px) {
  .video {
    width: 400px;
    height: 300px;
  }
}

@media (min-width: 1366px) and (max-width: 1679px) {
  .video  {
    width: 600px;
    height: 450px;
  }
}

@media (min-width: 1680px) and (max-width: 1919px) {
  .video  {
    width: 800px;
    height: 600px;
  }
}

@media (min-width: 1920px) {
  .video  {
    width: 1000px;
    height: 750px;
  }
}

vh/vw + calc

vh/vw + calc是一个简单且直观的自适应方案,我们可以将元素的宽度设置为固定vw/vh,并通过calc计算出对应的高度:

.video{
  width:40vw;
  height:30vw;
}

但是有个比较鸡肋的地方,因为vw/vh是相对于视窗的宽高的,因为当窗口上存在滚动条的话,他会把滚动条的宽高给计算上,那么你计算的时候就要把滚动条的宽高也要考虑上。

通过padding和包含块特性进行实现

实现原理

今天我们来看看一个比较冷门的方式,我们试下利用padding和包含块特性保持DOM元素宽高比。

首先, 我们先来看看包含块的概念:

  1. 如果 position 属性是 staticrelative 的话,包含块就是由它的最近的祖先块元素(比如说inline-block, block 或 list-item元素)或格式化上下文BFC(比如说 a table container, flex container, grid container, or the block container itself)的内容区的边缘组成的。

  2. 如果 position 属性为 absolute ,包含块就是由它的最近的 position 的值不是 static (也就是值为fixedabsoluterelative 或 sticky)的祖先元素的内边距区的边缘组成。

  3. 如果 position 属性是 fixed,在连续媒体的情况下(continuous media)包含块是 viewport ,在分页媒体(paged media)下的情况下包含块是分页区域(page area)。

  4. 如果 position 属性是 absolute 或 fixed,包含块也可能是由满足以下条件的最近父级元素的内边距区的边缘组成的:

    1.  transform 或 perspective 的值不是 none
    2.  will-change 的值是 transform 或 perspective
    3.  filter 的值不是 none 或 will-change 的值是 filter(只在 Firefox 下生效).
    4.  contain 的值是 paint (例如: contain: paint;)

然后,再来明确两个结论:

  1. 绝对定位元素的大小是由top、right、 bottom、 left四个属性决定的,当这四个属性的值是一个比例值(如百分比等)的话,那这些属性的计算是基于包含块内容区进行的。
  2. 垂直方向上的内外边距使用百分比做单位时,是基于包含块内容区的宽度来计算的。

实现思路

我们可以在目标元素外层使用一个容器元素进行包裹,并将容器元素的position设置为relative,此时容器元素的包含块为他最近的祖先块元素或BFC。由于垂直方向上的内外边距使用百分比做单位时,是基于包含块内容区的宽度来计算的,所以我们将容器元素widthpadding-bottom的单位设置为百分比,此时width / padding-bottom的值就是我们想要得到的宽高比。如果此时width === 100%,padding-bottom = 想要的高度 / 想要的宽度 * 100%, 如:

//宽高比为 16 : 9
padding-bottom = 9 / 16 * 100% = 56.25%;

//宽高比为 4 : 3
padding-bottom = 3 / 4 * 100% = 75%;

又因为绝对定位元素的大小是由top、right、 bottom、 left四个属性决定的,当这四个属性的值是一个比例值(如百分比等)的话,那这些属性的计算是基于包含块内容区进行的。,我们将目标元素的position设置为absolute,此时目标元素的包含块为容器元素,我们再将目标top、right、 bottom、 left都设置为0,此时目标元素的宽高就能够完美贴合容器元素的内容区的宽高了。

代码实现

下面我们通过借助padding-bottom来使元素div.video保持16 : 9的宽高比:

<div class="video-wrapper">
  <div class="video"></div>
</div>
.video-wrapper {
  position: relative;
  width: 100%;
  height: 0;
  padding: 0;
  padding-bottom: 56.25%;
}
.video{
  background-color: yellow;
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
}

实现效果

2021-12-28 19-15-45 00_00_02-00_00_06.gif