水平垂直居中的方法
方案1:Flex
方案2:Position + Transform
方案3:Grid
方案4:Position + Margin
普通的 position: abosulte 配合 margin: auto 是无法实现垂直水平居中的,之所以例子中可以实现主要是因为激活了 abosulte 的流体性,而激活的方式就是"对立方向发生定位",简单来说就是例子中上右下左都有定位,即使是0,也会激活横向(左、右定位激活)和竖向(上、下定位激活)方向的流体性,至于流体性的其他特性在后续会慢慢道来。
方案5:Table-Cell
方案6:Writing-Mode
兼容性:
知识扩展:
实际上writing-mode这个CSS属性在上古时代就诞生了,IE5.5浏览器就已经支持了(也就是2001年),那为啥一直沉寂了差不多20年呢?
那是因为,在很长一段时间里,FireFox, Chrome这些现代浏览器都不支持writing-mode,writing-mode基本上就是IE浏览器的私有产物,大家对IE一直没啥好感,爱屋及乌的,自然对writing-mode也不待见。
但随着前端技术的发展,大家逐渐摆脱了一叶障目的桎梏,各大现代浏览器纷纷对writing-mode实现了更加标准的支持(主要得益于FireFox浏览器的积极跟进),writing-mode的兼容性已经不成问题了,终于可以发挥它本身的逆天特性了。
特性:
- 水平方向也能margin重叠
The bottom margin of an in-flow block-level element always collapses with the top margin of its next in-flow block-level sibling, unless that sibling has clearance.
W3C清清楚楚写的bottom margin和top margin会重叠;然而,这是CSS2文档中的描述,在CSS3的世界中,由于writing-mode的存在,这种说法就不严谨了,应该是对立流方向的margin值会发生重叠。换句话说,如果元素是默认的水平流,则垂直margin会重叠;如果元素是垂直流,则水平margin会重叠。
- 可以使用margin:auto实现垂直居中
道理很简单,在块状元素里,margin: 0 auto;可以实现水平居中,现在水平流被writing-mode改为垂直流,那么auto生效的方向自然变为垂直方向,至于为什么块状元素内margin: auto 0;不能实现垂直居中,我们稍后会讲到。
-
可以使用text-align:center实现图片垂直居中
-
可以使用text-indent实现文字下沉效果
- 可以实现全兼容的icon fonts图标的旋转效果
方案7:::after
方案8:::before
最优雅的水平垂直居中的方法
方案9:Flex + Margin
这里的 display: flex 替换成 display: inline-flex | grid | inline-grid 也是可以的。
Flex布局中的margin
不知道大家有没有思考过,为什么在Block元素中,margin: auto;只能实现水平居中,而无非垂直居中呢?
CSS2 Visual formatting model details: 10.3.3
If both margin-left and margin-right are auto, their used values are equal, causing horizontal centring.
CSS2 Visual formatting model details: 10.6.3
If margin-top, or margin-bottom are auto, their used value is 0.
所以,垂直方向的auto的值都为0,这也就解释了为什么不能垂直居中了。
为什么水平方向可以居中但垂直方向的值就是0了呢?
想解释清楚这个问题,就不得不提到CSS中的width/height两个属性了。
我们先来看看W3C上对于这两个属性的定义:
可以看到,width/height的默认值都是auto。
那如果一个不添加任何属性的div会是什么样子呢?
这不由得又引发了我的疑问,为什么相同的默认值最后的表现却不一样呢?
这就涉及到了另一个关键字“auto”。
auto到底做了什么?
width:
-
对于块元素来说,width 取值为 auto 时,它的width就是其父容器的宽度(类似于100%)
-
对于内联元素或内联块元素,width 取值为 auto时,它的width就是内容的宽度(即min-content)
延伸:auto 和 100% 的区别
我们总说块级元素的宽度会自动填满父级,这时 width: auto 就相当于 width: 100%,但这俩个属性真的一样吗?
可以看出再子元素没有多余的尺寸样式时,auto 和 100% 表现一致。
于是,我们在子级中加入一个尺寸样式,padding: 0 20px
这时,width: 100% 的子元素边框已经超出父级宽度了,而auto状态下的子级表现依然良好。
这是因为块级元素自带容器的流体性,我们可以想象一下水倒入容器时,只要水足够多就会自然的充满整个容器的宽度。
而 width: auto 在块级元素中就具有水一般的原生流动性,这是一种 margin/padding/border/content 内容区域自动分配水平空间的机制。
但是,块级元素一旦被设置了宽度,就打破了这一规则,即“流动性丢失”,表现为浏览器无法根据原生流动性来计算元素的宽度了。
所以,大家在工作中,要记住“无宽度”这条准则,少了代码、计算、维护,何乐不为呢?
height
-
对于块元素或者内联元素来说,height的默认值是元素内容的高度,当内容为空时,则会显示为height: 0;
-
对于flex、grid等盒格式化模型除外。
那么,我们现在试着来解释一下为什么水平方向可以但垂直方向的值就是0了呢?
流动性:当元素在该方向上具备流动性,即默认撑满包裹它的父级时,则该方向具有 margin: auto 来分配剩余空间的特性,在 block 下就表现为水平方向的水平居中。
包裹性:当元素在该方向上表现为包裹性,即默认为内容的大小时,则该方向没有可以被分配的剩余空间,在 block 下就表现为垂直方向默认高度为0.
既然auto会根据盒格式化模型来改变auto值的计算方式,那么Flex下的margin又哪些特性呢?
Flex中的margin: auto
还是这个最优雅的垂直居中代码为例
这里要延伸2个概念:
-
FFC(flex formatting context)
-
GFC(grid formatting context)
上述的margin: auto实现垂直居中在这两种和格式化模型下都可以实现垂直居中,即下述dispaly取值范围:
display: flex;
display: inline-flex;
display: grid;
display: inline-grid;
我们今天主要围绕Flex来分析margin的表现,grid布局有机会我们再单独讲一期。
继续查看CSS文档
第二条的大意就是flex格式化上下文中,设置了 margin: auto 的元素,在通过 justify-content 和 align-self 进行对齐之前,任何正处于空闲的空间都会分配到该方向的自动 margin中去,最重要的是垂直方向也会自动去分配这个剩余空间。
注意:如果我们使用了margin: auto;来分配该维度的剩余空间,那对应维度的justify-content 和 align-self这些对齐方式就失效了。
自动margin的神奇效果
首先margin几乎可以实现justify-content 和 align-self对齐方式的每一种效果,这里主要说一下一些实用的布局小技巧。
- 居右的登录按钮
<ul class="g-nav">
<li>导航A</li>
<li>导航B</li>
<li>导航C</li>
<li>导航D</li>
<li class="g-login">登陆</li>
<li>注册</li>
</ul>
.g-nav {
display: flex;
}
.g-login {
margin-left: auto;
}
- 垂直方向上的多行居中
<div class="g-container">
<p>这是第一行文案</p>
<p>这是第二行文案</p>
<p class="s-third">1、剩余多行文案需要垂直居中剩余空间</p>
<p class="s-forth">2、剩余多行文案需要垂直居中剩余空间</p>
<p>这是最后一行文案</p>
</div>
.g-container {
display: flex;
flex-wrap: wrap;
flex-direction: column;
}
.s-third {
margin-top: auto;
}
.s-forth {
margin-bottom: auto;
}
- 实现粘性footer布局
<div class="g-container">
<div class="g-real-box">
Content
</div>
<div class="g-footer"></div>
</div>
.g-container {
height: 100vh;
display: flex;
flex-direction: column;
}
.g-footer {
margin-top: auto;
flex-shrink: 0;
height: 30px;
background: deeppink;
}
当然在flex下使用justify-content: space-between;也可以很好的解决。
总结:
-
块格式化上下文中margin-top 和 margin-bottom 的值如果是 auto,则他们的值都为0;
-
flex 格式化上下文中,在通过 justify-content; 和 align-self; 进行对齐之前,任何剩余空间都会分配到该方向的 margin: auto; 中去;
-
单个方向上的自动 margin 也非常有用,它的计算值为该方向上的剩余空间;
-
使用了margin: auto;的 flex 子项目,它们父元素设置的 justify-content 以及它们本身的 align-self 将不再生效。
剩余空间和溢出空间
在margin: auto;的案例中,有一个高频词——剩余空间。
要了解Flex中的剩余空间,首先要了解Flex中容器的概念。
剩余空间:子容器在父容器的“主轴”上还有多少空间可以“瓜分”,这个可以被“瓜分”的空间就叫做剩余空间。
既然是子容器瓜分父容器的空间,所以分配的属性一定属于子容器,也就是flex属性。
面试的时候,Flex相关的问题我第一个总会问flex属性的默认值什么?
flex默认值: 0 1 auto;
flex是一个聚合属性,它是由3个属性构成的,所以我们把默认值拆解就会变成flex-grow: 0; flex-shrink: 1; flex-basis: auto;
flex-basis
flex-basis是flex属性的最后一个属性,为什么会第一个讲呢?带着这个疑问我们来了解下flex-basis。
MDN: flex-basis 指定了 flex 元素在主轴方向上的初始大小。如果不使用 box-sizing 改变盒模型的话,那么这个属性就决定了 flex 元素的内容盒(content-box)的尺寸。
.wrap {
width: 500px;
height: 200px;
display: flex;
justify-content: center;
}
.item {
width: 150px;
flex-basis: 300px;
}
可见最后flex-basis覆盖了主轴方向上的width。
Note: 当一个元素同时被设置了 flex-basis (除值为 auto 外) 和 width (或者在 flex-direction: column 情况下设置了height) , flex-basis 具有更高的优先级.
这时,我不禁又产生了疑问,flex-basis: auto; 的表现又是什么样子呢?
于是,我设计了几种在 flex-basis: auto; 情况下的实验:
- 无内容 + 无宽度
.wrap {
width: 500px;
height: 200px;
display: flex;
justify-content: center;
}
.item {}
- 有内容 + 无宽度
.wrap {
width: 500px;
height: 200px;
display: flex;
justify-content: center;
}
.item {}
- 有内容 + 有宽度
.wrap {
width: 500px;
height: 200px;
display: flex;
justify-content: center;
}
.item {
width: 300px;
}
所以,我们可以得出如下结论:
如果未指定 flex-basis,即 flex-basis: auto;,flex-basis 将回退到 width 属性。如果同时未指定 width 属性,flex-basis 将回退到基于 Flex 项目内容的计算宽度值(computed width),如果改变主轴方向为垂直则宽度 width 属性,将被替换为 height 属性。
得出结论之后,又产生了新的疑问,flex-basis可以覆盖width,那 max-width/min-width 和 !import 之间谁的优先级更大呢?
想知道结果最简单的方式就是继续测试:
- max-width
.wrap {
width: 500px;
height: 200px;
display: flex;
justify-content: center;
}
.item {
max-width: 150px;
flex-basis: 300px;
}
- min-width
.wrap {
width: 500px;
height: 200px;
display: flex;
justify-content: center;
}
.item {
min-width: 300px;
flex-basis: 150px;
}
- !import
.wrap {
width: 500px;
height: 200px;
display: flex;
justify-content: center;
}
.item {
flex-basis: 300px;
width: 150px !important;
}
其实!import的测试没有必要,稍微思考下就能清楚,import改变的只是同属性的覆盖,并不能提升该属性的优先级。
最终可以得出结论:
flex-basis的优先级:max-width/min-width > flex-basis > width > content-width
知道了优先级我们才可以计算flex盒模型下的容器最终尺寸(final flex-basis),才会进一步的计算出剩余/溢出空间,这就是flex-basis的介绍放在最前面的原因。
flex-grow
flex-grow 定义子容器的瓜分剩余空间的比例,默认为0,即如果存在剩余空间,也不会去瓜分。
现在设置CSS为:
.item-3 {
width: 100px;
flex-grow: 3;
}
.item-4 {
width: 100px;
flex-grow: 1;
}
那么,现在各item的宽度分别为多少?(默认案例都是主轴为row情况下,如果主轴方向改为垂直,则width要替换为height)
计算flex-grow属性下的子容器宽度,就需要知道剩余空间和分配原则,grow是分配主容器的剩余空间
freeSpace = container-size - sum(item-final-flex-basis)
剩余空间 = 容器尺寸 - sum(子级的最终尺寸)
所以得出剩余空间为(500px - 100px * 4 = 100px)
知道剩余空间后,套用宽度计算公式:
width = width + freeSpace * (growRatio / sum(growRatio))
宽度 = 当前宽度 + 剩余空间 * (瓜分系数 / 总瓜分系数)
所以可以得出,item3.width = '175px',item4.width = '125px'
flex-shrink
说完flex-grow就该轮到flex-shrink了。
MDN: flex-shrink 属性指定了 flex 元素的收缩规则。flex 元素仅在默认宽度之和大于容器的时候才会发生收缩,其收缩的大小是依据 flex-shrink 的值。
依旧看的云里雾里,不过别急,上面说完了剩余空间,既然shrink是收缩规则,那自然要轮到溢出空间登场了。
现在设置CSS为:
.item-3 {
width: 150px;
flex-shrink: 3;
}
.item-4 {
width: 150px;
flex-shrink: 1;
}
这时我们可以得出溢出空间为:
freeSpace = container-size - sum(item-final-flex-basis)
溢出空间 = 容器尺寸 - sum(子级的最终尺寸)
所以,得出溢出空间为(500 - 4 * 150 = '-100px')
接着,套用宽度计算公式:
width = width + freeSpace * (shrinkRatio / sum(shrinkRatio))
宽度 = 当前宽度 + 溢出空间 * (收缩系数 / 总收缩系数)
注意:我们刚才提到了Flex的默认值是 0 1 auto,这也就意味着item1 和 item2 的 shrinkRatio 也为别为 1, 所以,sum(shrinkRatio) = 6
所以可以得出,item3.width = '100px',item1~4.width = '133.333px'
至此,有没有发现grow和shrink的计算公式几乎是一致的,具体公式总结如下:
空间宽度:freeSpaceWidth = wrapWidth - sum(itemWidth)
子元素宽度:itemWidth = itemWidth + freeSpaceWidth * (ratio / sum(ratio))
注:flex-grow 和 flex-shrink 都不支持负数。
聚合属性flex
现在我们知道了,flex属性默认值是(0 1 auto),也知道了这三个值分别对应哪几个子属性。
那么,我们通常写样式时最常用的 flex: 1; 平均分配宽/高时,到底它的值会是什么呢?
我们可以看到,flex-grow 由0变成了1,另外 flex-basis 也由 auto 变为了 0%,即在没有max/min尺寸属性的限制下,子级的final-flex-basis为0,这时所有 flex: 1 的子级均分剩余空间。
那么,继续测试:
- flex: none
- flex: 10px
- flex: content
- flex: 1 2
- flex: 1 10px
- flex: 1 0 1%
现在总结如下:
至此 flex 属性我们就基本了解了。
Flex中的不常用属性
order
MDN: order 属性规定了弹性容器中的可伸缩项目在布局时的顺序。元素按照 order 属性的值的增序进行布局。拥有相同 order 属性值的元素按照它们在源代码中出现的顺序进行布局,默认值为 0。
思考一下,如果使用vue循环来生成DOM,再通过order来打乱顺序,DOM的index是否会乱序
<div class="wrap">
<div v-for="(i, index) in 5" :key="index" class="item" :class="`item${index}`">
{{`item${index}`}}
<br/>
</div>
</div>
可以看到子级的排序变了,但是index并没有变。
通过Chrome审查DOM,可以发现DOM的结构并没有发生改变,而浏览器显示的排序已经改变,仔细分析一下不难发现,order只是CSS属性,它能改变的只是DOMTree和CSSOM结合后的RenderTree,而我们审查的仅仅是DOMTree,这也解释通了为什么排序乱了,而index分配并没有变化。
该属性的使用场景猜想:利于css进行排序、轮播。
flex-wrap
MDN: flex-wrap 指定 flex 元素单行显示还是多行显示 。如果允许换行,这个属性允许你控制行的堆叠方向。
- nowrap(默认)
flex 的元素被摆放到到一行,即不换行,这可能导致内容溢出 flex 容器。 子级排列方向与父级定义的 flex-direction 的方向一致。
- wrap
允许换行,子级元素被打断到多个行中。子级排列方向与父级定义的 flex-direction 的方向一致。
利用这个属性,我们可以很简单的实现瀑布流布局。
- wrap-reverse
与 wrap 的行为一样,但是排列方向与父级排列方向相反。
- wrap-reverse + vue循环 index的分配
- wrap-reverse + order
chrome审查元素发现 DOM Tree排序并没有变化
思考题
- 计算每个item宽度?
- 瀑布流到底如何布局?