我们在日常开发页面的过程中,对于固定背景图的容器的宽高的处理,往往是按照设计稿写死,很少会出现需要固定宽高比例,而宽度自适应的场景。

但是这个情况,在低代码平台的基础组件中会比较常见,比如布局组件:

用户上传一张背景图,需要用图片的宽度撑满容器,高度采用图片的宽高比进行自适应,然后内容区域由用户继续拖拽其他组件完成设计。

此时,由于不确定是否在其他容器内,所以宽度只能设置为 100%,但是高度就比较难设置。

有几个方案,方案一是通过 getComputedStyle 获取当前元素的实际宽度,然后 JS 根据图片的宽高比直接计算出高度来设置给元素。

方案二则是父元素采用 relative 定位,子元素通过 img 元素来作为 children, 再增加一个 绝对定位撑满整个父元素的 div 包裹其他子元素,借助 img 的默认的高度自适应特性完成宽高比的保留,伪代码如下:

const Layout = () => (
    <div className="w-full relative">
        <img src="xxxx" width="xxx" height="xxx" />
        <div className="absolute top-0 bottom-0 left-0 right-0">
            {/* 其他拖拽进来的子元素 */}
        </div>
    </div>
)

方案三和方案二类似,只不过原本依赖 img 标签默认的高度自适应,调整为利用 padding top 设置为百分比时参照的是宽度来完成宽高比例的设置,伪代码如下:

//aspectRatio = width / height,假设是 16 / 9,那么 paddingTop = (height / width) * 100%,所以 paddingTop 设置为 1 / (16 / 9) * 100%,也就是 1 / aspectRatio * 100%
const Layout = (aspectRatio) => (
    <div className="w-full relative" style={{ paddingTop: `${1 / aspectRatio * 100}%` }}>
        <div className="absolute top-0 bottom-0 left-0 right-0">
            {/* 其他拖拽进来的子元素 */}
        </div>
    </div>
)

我一开始采用的便是方案三,毕竟简单。

但是很快就出现了问题,子元素本来配置出来,比如两行文案是可以正常展示的,但是当文案被多语言翻译后,可能就会超出这个背景图高度,这一点在移动端更为明显。

这个问题,方案二和方案三都不能满足,方案一倒是可以把计算出来的高度设置为最小高度来解决这个问题,但是还是略显麻烦。

此时就是本文要介绍的核心 CSS 属性了:aspect-ratio

aspect-ratio 在 2021 年提出,如今已是基础能力,现代浏览器广泛支持。

不过也正是由于是 2021年提出的,我自己对于 CSS 的学习,其实是停留在了工作前,后来新增的一些特性并没有进行新的学习,反而还是比较古老的思维。这次也是在和一个同事的探讨下才进行了新的尝试。

这个属性,其实本质上就是来解决这个问题的。

.card {
    width: 100%;
    aspect-ratio: 16 / 9;
}

在父容器设置后,当内容元素高度不足,则会保持同样的宽高比例,当超出时,使用子元素的高度来撑开。

可以说是非常适合我当下的场景了。

最终的伪代码如下:

const Layout = (aspectRatio) => (
    <div className="w-full" style={{
        aspectRatio,
        backgroundImage: `url('your-image.jpg')`,
        backgroundSize: 'cover',
        backgroundPosition: 'center',
        backgroundRepeat: 'no-repeat',
    }}>
        {/* 其他拖拽进来的子元素 */}
    </div>
)

通过设置 aspect-ratio 来完成固定宽高比的实现,背景图通过 cover 来保证铺满,在超出场景下,借助 aspect-ratio 的特性来完成父容器的撑开,图片由于是 cover,并不会显得突兀。

关于新旧两种方案,两者实现对比如下:

特性 旧的 “Padding Hack” 方式 新的 aspect-ratio
原理 利用 padding-bottom: 56.25%(9/16)来撑开高度 直接声明 aspect-ratio: 16 / 9
代码量 冗长,需要额外的容器或伪元素 一行代码
内容溢出 内容容易被切断,必须绝对定位内部元素 智能处理:如果内容太多,盒子会自动长高
直观性 需要拿计算器算百分比(100 * 9 / 16 直接写比例,所见即所得

可以说是非常方便的一个属性了。

也给我自己提了个醒,现在早已不是只有 CSS3 的天下了,新特性往往好用也方便,得持续保持学习才行。

如果你也在做低代码画布组件,推荐先从这个属性开始替换旧方案。