尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

CSS水平垂直居中全方案:从Flex、Grid到绝对定位的布局选择

CSS水平垂直居中全方案:从Flex、Grid到绝对定位的布局选择 水平垂直居中这四个字基本是每个前端面试的第一道送命题。我带过的新人里十个有九个背了 flex 的三行代码可真到开发时遇到一个弹窗又不会写了——因为父容器高度是百分比算出来的或者元素本身宽高不固定。其实“怎么居中”这个问题的答案不是一个技巧而是你在什么布局上下文里。CSS 中水平垂直居中的实现方式不下十种每一种都对应了不同的场景约束。这篇我就把这几种方式从头到尾捋一遍每个方案的代码、原理、坑都会展开讲你看完就能直接用。1. 在讲方案之前先把“居中”这件事拆明白1.1 居中的本质你在哪个“布局世界”里说话很多新手写不出居中不是不知道代码而是没搞明白自己在哪个布局环境里。水平居中相对简单块级元素用margin: 0 auto行内元素用text-align: center这两句话背下来就能应付绝大多数水平居中问题。但垂直居中不是这么回事普通文档流里没有一个和text-align对称的属性可以直接说“把这个块在垂直方向上放中间”。为什么因为 CSS 默认的块级排版垂直方向的尺寸和位置是由文档流一个个推下来的而不是像水平方向那样有“剩余空间自动分配”的机制。margin: 0 auto之所以可行是因为块级元素在水平方向上有一个明确规则如果左右 margin 都是 auto并且元素宽度小于父容器宽度那么剩余空间会被两边的 auto margin 平分。而垂直方向没有与之对应的“上下 margin auto 平分剩余空间”的规则在普通文档流里上下 margin 设成 auto 会直接变成 0。一旦切换到 Flexbox 或 Grid 布局世界情况就变了。这两个现代布局模型引入了“主轴”和“交叉轴”的概念默认就会把伸缩项或网格项分配在容器内auto margin 也有机会参与剩余空间的分配。所以你会发现很多居中方案的关键不是那句居中的代码本身而是外层display属性的改变这一步决定了你进入哪个“布局世界”。1.2 拿到需求先问自己三个问题我每次处理居中的需求不会先想代码而是先问自己三个问题第一个问题子元素的宽高是已知的还是未知的固定尺寸可以走负 margin 的路线动态内容的弹窗只能依赖 flex、grid 或 transform 这类不关心尺寸的方案。第二个问题垂直方向的约束来自哪里父容器有固定高度吗还是高度由内容撑开如果父容器根本没有高度那垂直居中本身就没有考量的意义。第三个问题你的代码要跑在什么环境里现代浏览器可以随便用 grid 和 flex但如果你的项目里还有老掉牙的 IE 需求那 transform 方案和 table-cell 方案就得纳入考虑。这三个问题问完方案基本就自动浮现了。接下来我按实际使用频率把主流的几个方案全部过一遍。2. 三个高频方案Flex、Grid、绝对定位2.1 Flex最顺手但别只记得三行代码Flex 可能是我在实际项目里用得最多的居中方案它最简单的写法长这样.parent { display: flex; justify-content: center; /* 主轴方向居中 */ align-items: center; /* 交叉轴方向居中 */ }justify-content控制的是主轴方向align-items控制的是交叉轴方向。主轴默认是水平方向交叉轴垂直方向所以这三行代码就是水平垂直居中。这里有个新人特别容易踩的坑只写了display: flex和justify-content: center子元素水平居中没问题垂直方向却纹丝不动。原因在于 flex 容器默认的align-items是stretch也就是让子元素在交叉轴上拉伸填满容器。你看到的内容没有居中是因为子元素已经拉伸到和容器一样高了再在容器内部“垂直居中”已经没有意义。解决办法就是显式地写align-items: center。还有一点值得提:place-items: center在 CSS 里也能用来居中它是align-items和justify-items的简写。但在 flex 容器中justify-items本身不参与布局计算所以这个简写在 flex 环境下的语义并不完整。我自己的习惯是 flex 场景老老实实写justify-contentalign-itemsgrid 场景才用place-items。Flex 方案最大的优势是子元素宽高是什么都行内容变多框架也不会变形父容器的高度是动态的也行。它不依赖尺寸不怕溢出代码量又少所以现在很多组件库的弹窗、抽屉默认就是用 flex 做的居中。2.2 Grid用place-items: center一行搞定Grid 是在 flex 之后被我用得越来越多的方案尤其当整个页面骨架就是网格布局时居中往往是顺手的事.parent { display: grid; place-items: center; }place-items是align-items和justify-items的简写。align-items控制的是每个网格项在网格区域里的垂直对齐方式justify-items控制水平对齐方式。都设成center网格项就同时在两个方向上居中了。这里要稍微注意一下place-items和place-content在单个元素居中的场景下很容易被搞混。place-items是让网格项在自己的单元格内居中place-content是把整个网格轨道作为一个整体去居中。如果一个网格容器里只有一个元素这两种写法视觉上几乎一样。但如果网格里有多个格子、或者用了grid-template-columns划分了多列它们的差别就很大了。我用下来最顺手的写法是单元素居中直接用place-items: center想控制多个网格项整体在容器里的位置才用place-content。Grid 的居中方案还有一个隐藏优势如果子元素设置了width: 100px这类固定宽度place-items: center的网格项不会被拉伸它保持自己的尺寸并居中但如果给子元素写justify-self: stretch或align-self: stretch覆盖默认值它又会铺满容器。这种行为的可预测性比 flex 好一些适合做精细控制。2.3 绝对定位加 margin: auto老方案但依然能打如果遇到浮动层、弹窗这类场景并且父容器设置了position: relative那我更愿意用绝对定位加 auto margin 的方式.parent { position: relative; } .child { position: absolute; top: 0; right: 0; bottom: 0; left: 0; margin: auto; width: 100px; height: 100px; }这个方案的原理值得多说两句。当绝对定位元素的top和bottom同时设置了具体值并且元素高度固定时浏览器会这样理解包含块的高度 top margin-top height margin-bottom bottom。现在 top 是 0bottom 是 0height 是 100px剩下来的高度去哪里如果 margin-top 和 margin-bottom 都是 auto那么剩余空间会被它们平分所以元素视觉上就被推到了垂直中央。水平方向同理left 和 right 是 0width 是 100pxmargin-left 和 margin-right 是 auto剩余宽度被平分。这个方案的优点是对内容尺寸的要求比负 margin 低很多你只要不给死宽高、让内容自己撑开也行。我写过很多次弹层结构就是外层 fixed 定位铺满全屏内层用这个方式居中。它的兼容性也非常好IE8 时代就有这个玩法了。要注意的是子元素必须得有可分配的空间。如果你给子元素写了width: 100%或者inset: 0又把尺寸挤满auto margin 没有剩余空间可分自然就居中失败了。3. 被低估的四个方案以及它们的适配场景3.1 绝对定位加负 margin只在固定尺寸时靠谱这个方案在移动端早期非常流行现在代码库里还能看到不少类似写法.child { position: absolute; left: 50%; top: 50%; width: 100px; height: 100px; margin-left: -50px; margin-top: -50px; }它的逻辑很简单先把子元素的左上角放到父容器中心点再通过负 margin 往回挪自身尺寸的一半。问题也写在代码里了——你必须提前知道宽高是 100px才能写出 -50px 的 margin。一旦内容是动态的比如后端返回了一段不确定长度的文字这个方案就废了。所以我现在一般只在图片、图标这类固定尺寸的元素上用它或者在一批高度统一的卡片里用它。另外一个变体是相对定位配合这个思路但相对定位本身不脱离文档流使用场景更窄我就不展开了。3.2 transform 位移不知道尺寸也能居中的定位方案不知道宽高又不想依赖 flex 或 grid 的布局上下文可以用 transform.child { position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%); }left: 50%和top: 50%把子元素的左上角放在父容器中心translate(-50%, -50%)再把子元素往回平移自身宽高的一半。因为 translate 的百分比是相对于元素自身尺寸的所以不需要知道子元素的宽高。这个方案在弹窗和居中浮层里很好用但它有两个要注意的点。第一transform 会创建层叠上下文对一个元素设置 transform 后它的 z-index 基准会改变可能影响后续定位。第二如果同一个元素身上还要做别的 transform 动画比如呼吸缩放、位移动画那再叠加 translate 就会冲突两者会互相覆盖。我的习惯是如果这个元素本身没有动效需求只管居中那可以用 transform如果要动就老实切换到 flex 方案把居中交给父容器子元素只管自己的动画。另外多一句使用了 transform 居中后如果父容器有overflow: hidden而且子元素很高可能出现上下被裁剪的情况。这个在 set 里踩过坑后面常见问题部分我会细说。3.3 table-cell 与行内块老项目里的守旧派如果你的项目里有大量基于表格布局的老页面或者需要兼容到很老的环境这两个方案可能会救你一命。table-cell 方案是这样的.parent { display: table-cell; text-align: center; vertical-align: middle; width: 300px; height: 300px; }display: table-cell会让这个元素表现得像表格里的一个单元格单元格里的内容天然支持vertical-align: middle的垂直居中text-align: center实现水平居中。我一般会给 parent 再套一层display: table的外壳不然某些浏览器对直接设置宽高的 table-cell 元素宽度计算会有些奇怪。这个方案在 IE8 里都能跑兼容性是顶级水平缺点是 table-cell 本身不支持margin布局拖拽能力很弱加之内联元素间的空白符问题现代项目里已经很少主动用它了。行内块的方案稍微绕一点.parent { text-align: center; font-size: 0; /* 消除行内块之间的空白间隔 */ } .parent::before { content: ; display: inline-block; height: 100%; vertical-align: middle; } .child { display: inline-block; vertical-align: middle; font-size: 16px; /* 恢复子元素字号 */ }原理是::before伪元素的高度撑满父容器并作为行内块参与基线对齐然后子元素也用vertical-align: middle对齐到这一行的中间位置于是看起来就是水平垂直居中了。这个方案不需要固定宽高兼容性也不错代价是代码结构复杂而且要处理行内块自带的空隙问题现在我已经很少用了但如果你接手的是老站点看到这种代码至少要知道它是在做什么。3.4 文本和行内内容的居中逻辑是另一套很多人把图片、块级元素居中和文本居中混在一起记其实文本居中的底层逻辑不一样。单行文本水平垂直居中很简单.box { width: 300px; height: 100px; text-align: center; line-height: 100px; }水平方向是text-align: center这个没争议垂直方向靠的是line-height等于容器高度。因为每一行文本的行框高度是 line-height 决定的当行框高度和容器高度一致时文字在行框内天然的垂直方向上是居中的。但这里有个隐藏条件这是单行文本。一旦内容变成两行三行line-height等于高度的方案就会溢出多行文本不能这么玩。多行文本的居中处理我一般会分两种情况。如果外层是 flex直接给父级写align-items: center文本块整体垂直居中。如果是在老项目里就借助 inline-block 的vertical-align: middle先把文本块包成 inline-block再放进一个 text-align: center 的父容器里居中。很多搜索词里问到的“怎么调整 css 容器里的文本位置”本质就是这几套逻辑的组合。4. 各种方案的常见坑我一个个踩给你看4.1 未知尺寸内容方案选择优先级内容尺寸不确定是居中场景里最麻烦的情况。比如后端返回一段很长的介绍文字比如用户头像要适配不同尺寸。这种情况下负 margin 方案直接出局绝对定位加负 margin 也是同样的命运。可用的是 flex、grid、绝对定位加 auto margin 和 transform 这四个方向。我个人的优先级是flex 和 grid 优先因为布局引擎自己推算尺寸最稳妥transform 其次适合不想引入 flex/grid 上下文的场景绝对定位加 auto margin 放在第三位因为代码结构要写一堆 top/right/bottom/left有点啰嗦。反正从实战结果看flex 和 grid 的出问题概率显著低于其他方案。4.2 溢出和裁剪transform 方案的隐形风险用transform: translate(-50%, -50%)居中时如果子元素的高度大于父容器而且父容器设了overflow: hidden会出现一个反直觉的现象元素顶部被裁掉一块。原因是 transform 只是视觉上把元素挪上去了但元素的布局盒仍然是从父容器中心点往下延伸的一旦上半部分超出父容器边界就会被 overflow 裁掉。解决这个问题我常用的字典是不要把 overflow 直接写在父容器上而是给父容器再加一层 wrapperwrapper 负责 overflow: hidden真正的居中结构放在 wrapper 内部。或者干脆换 flex 方案因为 flex 容器默认会让 overflow 的表现更符合直觉。这个坑在写弹窗遮罩时特别容易踩遮罩层本身 overflow 若设了 hidden内容一长就会出现顶部被切断的奇怪现象。4.3 移动端和动画场景下的隐藏坑移动端上flex 方案基本是主流因为 iOS 和 Android 的现代浏览器都对 flex 支持得不错。但如果你要兼容特别老的 iOS Safari比如 iOS 9 以下flex 的老写法需要加display: -webkit-box等前缀还是比较麻烦的。现在用户系统一般不会那么老但企业内部一些老设备的 WebView 要额外注意。动画场景里transform 居中方案有一个很隐蔽的问题如果子元素已经用了transform: translate(-50%, -50%)来居中再给它加一个transform: rotate(...)或scale(...)的动画动画会覆盖掉原有的 translate元素一下子就跳到了左上角。我一般会把居中的逻辑尽量放在父容器上用 flex 或 grid 解决子元素单独留出自己的 transform 空间这样动画变动就不会影响布局位置。还有一个小细节绝对定位加margin: auto的方案如果用于弹窗要注意给弹窗设置max-width和max-height并且搭配overflow: auto。如果不这么做当弹窗内容超过视口尺寸时上下边会被挤出可视区域用户想滚动都找不到滚动条。可以说所有定位类居中方案都需要考虑“内容超过容器时怎么办”这个问题flex 和 grid 因为自带伸缩机制风险会小一些。4.4 隔着几层祖先居中上下文要搞清楚很多时候元素不在父元素里直接居中而是要隔着好几层祖先。比如一个组件最深层的文本要在整个页面的某个区域内居中你给父容器写了相对定位但中间层又被 transform 或 filter 影响创建了新的包含块绝对定位的参考对象就会变成中间层而不是你以为的父容器。这个问题的排查方法很简单按祖先链一层层往上查看哪个元素设置了position: relative、transform、filter、perspective、contain: layout这类属性。只要存在这些属性它就可能是绝对定位元素的包含块。遇到这种场景我的建议是把居中结构尽量扁平化不要套太多层不行就用 flex 直接在需要布局的那一层搞定。5. 我的选择逻辑不同项目不同历史包袱不同取舍5.1 一套能直接用的判断流程我这两年处理居中的思路已经稳定了。拿到需求先分三个维度是不是单元素居中、宽高是否固定、宿主浏览器环境是什么。单元素居中优先 grid 的place-items: center需要在多个子元素场景里做居中用 flex如果项目里有老 IE 或极端兼容需求再回退到 table-cell 或行内块。宽高固定的图片图标可以直接上绝对定位加负 margin宽高不固定的弹层浮层要么 flex 要么绝对定位加 auto margin文本内容单行用line-height多行用 flex 或行内块。这个判断顺序不是绝对的但基本能覆盖我日常遇到的九成场景。5.2 一张表对比所有方案方案实现关键尺寸要求兼容性典型场景高风险点Flexjustify-content align-items不限IE10 现代可用组件居中、弹窗、工具栏忘写 align-items拉伸行为Grid place-itemsdisplay: grid place-items不限IE10 部分支持页面骨架、单元素居中与 place-content 混淆绝对定位 margin: autoinset 0 margin auto需要宽高或内容可撑开IE8弹层、固定浮窗无尺寸时易失败绝对定位 负 marginleft/top 50% 负 margin必须是固定宽高老到离谱也能用固定尺寸图标、卡片尺寸一变就废transform 位移left/top 50% translate(-50%,-50%)不限IE9浮层、未知内容居中影响层叠上下文、动画冲突table-celldisplay: table-cell vertical-align: middle父级需给宽高IE8老项目维护不支持 margin、代码冗余行内块 vertical-aligninline-block 伪元素撑高不限IE8老项目文本块空白符间隙、代码绕line-heightheight 和 line-height 相等适合单行文本全兼容按钮文字、单行标题多行必溢出5.3 给新人一个的练习思路如果你现在还在死记硬背这些居中代码我建议你换个练法。做一个需求父容器宽高不定子元素内容不定要保持水平垂直居中且在内容增加时不被裁剪、不撑破布局。用 flex 做一遍用 grid 做一遍用绝对定位加 auto margin 做一遍用 transform 做一遍。你会发现每个方案的代码在同样的需求下会产生不同的边界表现这个过程比背十遍代码都有用。真正理解每个方案的局限之后你不需要再搜“水平垂直居中”怎么写因为看到布局需求的那一刻脑子里会自动弹出对应的方案。另外一个小习惯在团队项目里居中这种小东西也有“同一样式不要实现两次”的说法。很多团队都有自己的工具类比如.flex-center、.grid-center这种全局类建议你在项目里也沉淀一个免得每个组件都写一遍三行 flex。我一般把这类基础布局类放到全局样式文件的第一层团队所有人都默认使用后面维护起来非常省心。我个人在实际操作里的体会是水平垂直居中这个题目之所以能成为经典面试题不是因为它难而是因为它能检验一个前端对布局模型的理解程度。你会背 flex 三行代码不算什么能在不同场景里快速选对方案、知道每个方案的副作用才是这题想考的。
返回列表