CSS 奇技淫巧 —— 绝对居中布局
2023-07-10 · 1,104 chars · 6 min read
本文来聊一个 flex 布局的小技巧 —— 绝对居中布局。这名字是我瞎起的,可能不是特别恰当,不过我讲完背景,大家就应该都清楚了。
背景#
APP 顶部由上到下,一般是「状态栏」和「导航栏」。状态栏通常就是系统默认的样式,最多根据底色的深浅,把文字和图标切换为反色,保证用户可以清晰地看到时间等信息。但是导航栏,经常需要适配页面的视觉风格,特别是在一些「全屏 H5」中。比如下面这张图片:
前端在实现这个组件的时候,考虑到要兼容各种场景,通常会分为三个部分:
- 左边:返回按钮,可能要适配 Android 和 iOS 的不同风格,看 APP 的精细程度
- 中间:内容区。可以是文字,也可以是组件,比如上图的
<Tab />
- 右边:自定义区域。常见的内容有「确认」,「搜索」,「帮助」等等...
那么问题来了,当左边和右边都是正常按钮时,两边宽度是一致的,中间文字默认居中即可。
但是,当右边的自定义内容,宽度与左边不同,或者干脆没有右边自定义区域。这种情况下,中间文字就不居中了:
那么,如何让中间的文字部分,相对于整个导航栏绝对居中呢?
绝对居中布局#
可能很多人首先想到的是,让左右两部分绝对定位在两侧。但是这样布局的问题是,中间区域是无法「感知」到左右两部分的宽度,很容易出现内容覆盖的情况:
所以,绝对定位的方案,样式上不够「安全」。而使用 JS 的话,由于设备和字体的差异,需要在运行时计算宽度,非常不利于 SSR 和 SSG 直接输出顶部内容。最好方案还是能继续使用 flex 三列布局,中间部分自动撑满余下全部区域,不产生重叠。
但是,使用 flex 布局要解决一个问题,由于左右两部分,存在不等宽的可能。那么在不等宽的情况下,如何保证中间区域仍然居中呢?
从问题到结论的过程中,经过了大量尝试和思考,这部分比较难用文字记录下来。此处直接说结论,中间区域居中的核心,是要保证左右两部分始终等宽。而让左右两部分始终等宽的方式,就是左右两边同时渲染左右两个组件,通过 overflow: hidden
裁掉多余的部分。看个示意图就一目了然了:
因为导航栏的高度,在一台设备上通常是固定的。所以左右两个组件,左边按照「左上右下」,右边按照「右上左下」的布局排列好,外部容器裁掉高出的部分,即可保证左右等宽。
HTML 代码:
<div class="container"> <div class="left-wrapper"> <div class="back">后退</div> <div class="report">反馈问题</div> </div> <div class="center">这里是绝对居中的标题</div> <div class="right-wrapper"> <div class="report">反馈问题</div> <div class="back">后退</div> </div> </div>
CSS 代码:
.container { width: 100%; height: 60px; line-height: 60px; display: flex; background-color: #ddd; border: 2px solid #000; } .left-wrapper, .right-wrapper { width: auto; height: 60px; background-color: #d6e8d5; flex-direction: column; } .back { width: 60px; height: 60px; text-align: center; background-color: #ccc; } .report { width: 100px; height: 60px; text-align: center; background-color: #999999; } .center { flex: 1; text-align: center; background-color: #fff2cc; }
这个方案比较 hack,最初这么做的原因是不想使用 JS 计算,纯 CSS 的方案性能更好,也比较适配我们的 SSR、SSG 架构。但毕竟会重复渲染两次,所以如果有一些特 别复杂的组件,最好能在外层包一个定宽的容器,动态讲复杂组件渲染到容器内。
除了重复渲染的问题,此方案还存在一些其他的短板,比如加了 overflow: hidden
后,右边如果有下拉菜单的话,可能要特殊处理下。但总的来说,还是比较稳的,在线上使用很久了,没发现什么明显的问题。
PS:如果你有其他更好的方案,欢迎评论区留言讨论!