【面试题拆解】--- css 水平垂直居中的实现方式和原理揭秘

1,405 阅读11分钟

1.文本、图片的水平垂直居中

(1)单行文本居中

设置文本内容

text-align:center; //水平居中
line-height:100px; //垂直居中, 行高等于高度,

为什么 line-height 等于高度就可以实现文本垂直居中?

要了解其中缘由, 先弄清楚几个概念。

行高

行高, 顾名思义, 就是一行文本的高度,在规范上讲就是两条基线之间的距离。行高由上间距、文本高度、下间距组成,上间距的距离与下间距的距离是相等的。 默认情况下一行文本的行高分为:上间距,文本的高度,下间距,并且上间距是等于下间距的,所以文字默认在这一行中是垂直居中的。

基线

基线: 书写英语字母时,字母 x 的下边缘就是基线(baseline)所在的位置,以下图为行高,基线,以及 vertical-align 属性中,baseline,bottom,middle,top 几个值的关系。

而字母 x 的高度就是 x-height, ex 是一个尺寸单位,其大小是相对字母 x 的来计算的,即 1ex 就表示 1 个字母 x 的高度。

tips: ex 是个相对单位。我们可以利用 ex 就是一个 x-height 的特性来实现图标与文字的垂直居中,这样如论字体大小如何变化,都不会影响垂直居中的效果。
想了解更多关于字母 x,请狠狠点击

代码如下:

<div>
    <i class="store-icon"></i>
    <span>第八号当铺</span>
</div>
.store-icon{
  display: inline-block;
  width: 20px;
  height: 1ex;
}

效果如下:

四种内联盒子

行高是作用在每一个行框盒子(line-box)上的,而行框盒子则是由内联盒子组成,因此,行高与内联元素可以说是非常紧密,行高直接决定了内联元素的高度(注意:这里的内联元素不包括替换元素);对于块级元素和替换元素,行高是无法决定最终高度的,只能决定行框盒子的最小高度。

在 css 中包含了四种内联盒子,inline box、line box、containing box、content area。

内联盒子(inline box) “内联盒子”不会让内容成块显示,而是排成一行,这里的内联盒子指的是元素的“外在盒子”,用来决定元素是内联还是块级.

行框盒子(line box) 每一行就是一个行框盒子,每个行框盒子都是由一个个内联盒子组成,注意:line-height 是作用在行框盒子上的,并最终决定高度(替换元素除外)

包含盒子(containing box) 此盒子由一行一行的“行框盒子”组成(css 规范中,并没有“包含盒子”的说法,更准确的称呼是“包含块”(containing block)。

内容区域(content area) 内容区域指的是一种围绕文字看不见的盒子,其大小仅受字符本身特性控制,本质上是一个字符盒子(character box);但是图片这样的替换元素,其显示内容不是文字,因此内容区域可以看成是元素自身。

行距与半行距

有时在还原设计图时,明明已经设置居中了,却还是与设计稿有偏差,这是为什么呢?
原来是行距的影响。那么什么是行距呢,我们可以想象一下在文字排版的时候,如果行与行之间的间距为 0,则文字是会紧紧贴在一起的,因此,行距就是用来协助排版的。

行距的计算:line-height - em-box,
em-box 指的是 1em 的大小,因此行距可以表示为:line-height - font-size,
距为 10,而这个行距会平均作用于文字的上边和下边。但 em-box 我们是无法感知这个盒子在哪的,而内容区域我们则可以理解为我们选中文字后的背景色所在区域,而当字体是宋体的时候,内容区域和 em-box 是等高的,因此我们可以利用此揪出 ex-box 的藏身之处。如下图所示:

正是因为行距的存在,我们给元素设置 margin 值时,要减去相应的半行距值,这样才能比较精确地还原设计图。

行高计算的基数

如果行高的单位不是px,那么如何计算?
这个计算需要一个基数,这个基数是当前标签的字体大小,而不是浏览器默认字体大小。 以chrome为例, 默认字体为16px,此时我们把line-height设置为150%,那么文字的行高将变为24px(16px*1.5=24)。

结论

那么现在我们就可以理解设置 line-height=height 为什么可以实现垂直居中了,
文本的高度是由 font-size 决定的, 假设文本 font-size 为 14px;然后设置 line-height:36px;那么文本的上下边距增加了, 但文本的高度依然是 14, 并且一直默认在行框中垂直居中, 而上下边距平分了 36px-14px 的高度。所以,容器被这一行文本占满,而本身文字在自己的一行中是垂直居中的,所以看起来就像是在容器中垂直居中。

(2)多行文本垂直居中

方法1:table
<div style="display:table;height:400px;">
  <span style="display:table-cell;vertical-align:middle;">
  我有很多很多行</span>
</div>
方法2:flex
.flex-container{
  display: flex;
  justify-content: center;
  flex-direction: column;
  height: 400px;
}

(3)大小不固定, 图片垂直居中

方法1: display:table-cell和文字大小控制居中
div {
  display:table-cell; 
  width:144px; 
  height:144px; 
  font-size:118px; 
  border:1px solid #beceeb; 
  text-align:center; 
  vertical-align:middle;
}/*这里的大小是根据高宽上限128像素图片设置的*/
img{
  vertical-align:middle;
}

效果如下:

方法2: display:inline-block和文字大小控制居中

这里需要将图片用a标签包裹,其关键是将a标签默认的inline属性值设置为inline-block,这样a标签既支持宽度,又支持vertical-align:middle,配合img的vertical-align:middle就可以实现图片的水平垂直居中显示了。

a{
  display:inline-block; 
  width:1.2em; 
  font-size:128px; 
  text-align:center; 
  vertical-align:middle;}
img{
  vertical-align:middle;
}

效果如下:

方法3: 透明图片拉伸对齐实现垂直居中显示

将要显示的图片与与其他不可见元素vertical-align:middle对齐。

 <div>
   <img class='show_img' src="XXXX" /><img  class='alpha_img' src='XXXX'>
</div>
.alpha_img{
  height:100%; 
  width:0; 
  vertical-align:middle;
}
.show_img{
  vertical-align:middle;
}

需要注意: img 外容器宽度要大于要显示的图片的最大宽度+1像素;透明图片也可换成span等元素, 只需多设置display:inline-block。

vertical-align的理解

vertical-align是什么? 从字面意思理解, vertical: 垂直,align:对齐;
那么它有什么作用呢?
先了解一下它支持的属性:

描述
数值上下的高度值,支持负值
%通过距离(相对于1line-height1值的百分大小)升高(正值)或降低(负值)元素。'0%'等同于'baseline'
baseline默认。元素的基线与父元素的基线对齐
sub降低元素的基线到父元素合适的下标位置。
super升高元素的基线到父元素合适的上标位置。
text-top把元素的顶端与父元素内容区域的顶端对齐。
top把对齐的子元素的顶端与line box顶端对齐。
middle元素的中垂点与 父元素的基线加1/2父元素中字母x的高度对齐。
bottom把对齐的子元素的底端与line box底端对齐。
text-bottom把元素的底端与父元素内容区域的底端对齐。
inherit采用父元素相关属性的相同的指定值。

起作用的前提:
vertical-align起作用的前提是元素为inline水平元素或table-cell元素,包括span, img,input, button, td 以及通过display改变了显示水平为inline水平或者table-cell的元素。这也意味着,默认情况下,div, p等元素设置vertical-align无效。

那么vertical-align:middle是如何起作用的呢?

手册上说是将当前元素放在父元素的中间,这样就可以实现居中了。这个概念有些模糊, 我们可以引用张大神的测试面板,通过改变其属性去更加透彻的了解。

将vertical-align改成bottom, 可以发现图片下移了,而文字纹丝不动, 可见vertical-align与line不存在直接的关系。

vertical-align:middle属性的表现与否,仅仅与其父标签有关,至于我们通常看到的与后面的文字垂直居中显示那都是假象.

那么有时vertical-align不生效又是什么原因造成的呢?这就要说说vertical-alignline-height之间的关系了。

line-height 与 vertical-align 的关系

两者之间的联系是百分比, vertical-align设置百分比是基于line-height来计算的。 对于内联元素,vertical-align 与 line-height 虽然看不见,但实际上「到处都是」

一个经典的例子, 一个有文本内容的盒子里面加一张图片,图片底部总是会有一定的间隙, 如图: 这还是在默认的情况下,除了边框外没加任何样式, 如果改变行高为150%, 则间隙会更大。

这是为什么呢?

vertical-align默认值是baseline, 也就是基线对齐。前面讲了基线所在位置也就是字母x下边缘,于是图片和文字就下边缘对齐了,而文字本身是有高度的,除了文字本身的高度以外, 它还有上边距和下边距, 扩大了line-height后, 文本的上下边距也就相应增加了, 所以间隙也就增加了。

那怎么解决呢?

(1)将图片设置为 display:block (利用 vertical-align 的生效前提)
(2)将 vertical-align 设置为 top,bottom,或者 middle 等值(利用属性值的表现行为)
(3)将 line-height 设置为 0 (利用 line-height 为 0 时,基线上移)
(4)将 font-size 设置为 0 (如果 line-height 的值为相对值)
(5)将 img 设置浮动或者绝对定位 (如果布局允许的话)

再看另一个例子, 我们肉眼以为的垂直居中实际上并没有真的垂直居中。

<div class="box">
  <span class="son"></span>
  x
</div>
.box {
width: 300px;
height: 150px;
line-height: 150px;
font-size: 20px;
border: 1px solid #ddd;
position: relative;
}
//  绘制父元素的垂直中心线
.box::after  {
content: "";
position: absolute;
display: block;
width: 100%;
height: 1px;
background-color: red;
top:0;
bottom: 0;
margin: auto;
left: 0; 
}  
.son {
 display: inline-block;
 width: 100px;
 height: 100px;
 vertical-align: middle;
 background-color: purple;
 position: relative;
}
.son::after  {
  content: "";
  position: absolute;
  display: block;
  width: 100%;
  height: 1px;
  background-color: #317ffd;
  top:0;
  bottom: 0;
  margin: auto;
  left: 0;  
}

效果图: 现在看到的是子元素的垂直中心线与父级元素基线的位置往上二分之一 X 高度(X 的中心) 所在线对齐,也就是 图中红线表示父元素的垂直中心线,蓝线表示子元素的垂直中心线,可以明显的看到 蓝线 与 X 的中心保持一致,但较红线偏低。如果绝对居中的话,两条线应该完全重合。

为什么会这样呢?

原因在于文字具有下沉特性,从而导致蓝线无法绝对与红线对齐。当文字大小足够小时,我们可以忽略。从而近似的实现居中效果。但是文字越大,影响就越明显。

怎么解决呢?

设置父元素的font-size为0, 此时content area高度是0,各种乱七八糟的线都在高度为0的这条线上,绝对中心线和中线重合。自然全垂直居中。
(此处引用大神文章《深入理解vertical-align和line-height

2.盒子的水平垂直居中问题

创建元素

<div class="parent">
  <div class="child">child</div>
</div>

方案1: 知道宽度的情况下 absolute+margin负值

.parent {
  width:400px;
  height:400px;
  background: red;
  position: relative;
}
.child {
  position: absolute;
  left:50%;
  top:50%;
  background: yellow;
  width:50px;
  height:50px;
  margin-left:-25px;
  margin-top:-25px;
}

方案2: 不知道宽高的情况下 absolute+transform

.parent {
  width:400px;
  height:400px;
  background: red;
  position: relative;
}
.child {
  position: absolute;
  left:50%;
  top:50%;
  transform: translate(-50%,-50%);
}

方案3: position+margin:auto

.parent {
  position:relative;
  width:200px;
  height:200px;
  background: red;
}
.child {
  width:80px;
  height:40px;
  background: yellow;
  position: absolute;
  left:0;
  top:0;
  right:0 ;
  bottom:0;
  margin:auto;
}

方案4: display: flex

(不需要兼容时,推荐)

.parent {
  width:400px;
  height:200px;
  background:red;
  display: flex;
  justify-content:center;
  align-items:center;
}
.child {
  height:100px;
  width:100px;
  background:green;
}

方案5: 伪元素

.parent {
  width:200px;
  height:200px;
  background:red;
  text-align: center;
}
.child {
  height:100px;
  width:100px;
  background:yellow;
  display: inline-block;
  vertical-align: middle;
}
.parent:before {
  content:"";
  height:100%;
  vertical-align: middle;
  display: inline-block;
}

总结

本文讲解了文本,图片和盒子的垂直居中实现方式以及line-height和vertil-align之间的关系,包括vertical-align:middle不生效的原因及其解决方案, 由于时间关系, margin:auto是如何实现居中的,这个有空再补充。如有不对, 欢迎评论区留言指正,感谢!