You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

181 lines
7.7 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# CSS Flex排版为什么垂直居中这么难
你好我是winter。今天我们来谈谈Flex排版。
我们在前面多次讲过正常流排版的设计来源于数百年来出版行业的排版经验而HTML诞生之初也确实是作为一种“超文本”存在的。
但是自上世纪90年代以来Web标准和各种Web应用蓬勃发展网页的功能逐渐从“文本信息”向着“软件功能”过渡这个思路的变化导致了CSS的正常流逐渐不满足人民群众的需求了。
这是因为文字排版的思路是“改变文字和盒的相对位置,把它放进特定的版面中”,而软件界面的思路则是“改变盒的大小,使得它们的结构保持固定”。
因此在早年的CSS中“使盒按照外部尺寸变化”的能力非常弱。在我入行前端的时间大约2006年CSS三大经典问题垂直居中问题两列等高问题自适应宽问题。这是在其它UI系统中最为基本的问题而到了CSS中却变成了困扰工程师的三座大山。
机智的前端开发者们曾经创造了各种黑科技来解决问题包括著名的table布局、负margin、float与clear等等。在这种情况下Flex布局被随着CSS3一起提出最初叫box布局可以说是解决了大问题。
React Native则更为大胆地使用了纯粹的Flex排版不再支持正常流最终也很好地支持了大量的应用界面布局这一点也证明了Flex排版的潜力。
今天我们就从设计、原理和应用三个方面来学习一下Flex布局我们先从设计开始。
## Flex的设计
Flex在英文中是可伸缩的意思一些翻译会把它译作弹性我觉得有点不太准确但是确实中文中没有更好的词。
Flex排版的核心是display:flex和flex属性它们配合使用。具有display:flex的元素我们称为flex容器它的子元素或者盒被称作flex项。
flex项如果有flex属性会根据flex方向代替宽/高属性形成“填补剩余尺寸”的特性这是一种典型的“根据外部容器决定内部尺寸”的思路也是我们最常用的Windows和Apple窗口系统的设计思路。
## Flex的原理
说完了设计我们再来看看原理Flex的实现并不复杂我曾经写过一个基本实现提交给spritejs项目代码可以[参考这里](https://github.com/spritejs/sprite-core/commit/8757b4d3888b4f237b1089e94e075ab58ca952a6#diff-677d382da9f8d81f61d50af24f937b32R32)。
下面我们就来讲解一下如何实现一个Flex布局。
首先Flex布局支持横向和纵向这样我们就需要做一个抽象我们把Flex延伸的方向称为“主轴”把跟它垂直的方向称为“交叉轴”。这样flex项中的width和height就会称为交叉轴尺寸或者主轴尺寸。
而Flex又支持反向排布这样我们又需要抽象出交叉轴起点、交叉轴终点、主轴起点、主轴终点它们可能是top、left、bottom、right。
Flex布局中有一种特殊的情况那就是flex容器没有被指定主轴尺寸这个时候实际上Flex属性完全没有用了所有Flex尺寸都可以被当做0来处理Flex容器的主轴尺寸等于其它所有flex项主轴尺寸之和。
接下来我们开始做Flex排版。
**第一步是把flex项分行有flex属性的flex项可以暂且认为主轴尺寸为0所以它可以一定放进当前行。**
接下来我们把flex项逐个放入行不允许换行的话我们就“无脑地”把flex项放进同一行。允许换行的话我们就先设定主轴剩余空间为Flex容器主轴尺寸每放入一个就把主轴剩余空间减掉它的主轴尺寸直到某个flex项放不进去为止换下一行重复前面动作。
分行过程中,我们会顺便对每一行计算两个属性:交叉轴尺寸和主轴剩余空间,交叉轴尺寸是本行所有交叉轴尺寸的最大值,而主轴剩余空间前面已经说过。
**第二步我们来计算每个flex项主轴尺寸和位置。**
如果Flex容器是不允许换行的并且最后主轴尺寸超出了Flex容器就要做等比缩放。
如果Flex容器有多行那么根据我们前面的分行算法必然有主轴剩余空间这时候我们要找出本行所有的带Flex属性的flex项把剩余空间按Flex比例分给它们即可。
做好之后我们就可以根据主轴排布方向确定每个flex项的主轴位置坐标了。
如果本行完全没有带flex属性的flex项justify-content机制就要生效了它的几个不同的值会影响剩余空白如何分配作为实现者我们只要在计算flex项坐标的时候加上一个数值即可。
例如如果是flex-start就要加到第一个flex项身上如果是center就给第一个flex项加一半的尺寸如果是space-between就要给除了第一个以外的每个flex项加上“flex项数减一分之一”。
**第三步我们来计算flex项的交叉轴尺寸和位置。**
交叉轴的计算首先是根据align-content计算每一行的位置这部分跟justify-content非常类似。
再根据alignItems和flex项的alignSelf来确定每个元素在行内的位置。
计算完主轴和交叉轴每个flex项的坐标、尺寸就都确定了这样我们就完成了整个的Flex布局。
## Flex的应用
接下来我们来尝试用flex排版来解决一下当年的CSS三大经典问题简直易如反掌
垂直居中:
```HTML
<div id="parent">
<div id="child">
</div>
</div>
```
```CSS
#parent {
display:flex;
width:300px;
height:300px;
outline:solid 1px;
justify-content:center;
align-content:center;
align-items:center;
}
#child {
width:100px;
height:100px;
outline:solid 1px;
}
```
思路是创建一个只有一行的flexbox然后用align-items:center;和align-content:center;来保证行位于容器中,元素位于行中。
两列等高:
```HTML
<div class="parent">
<div class="child" style="height:300px;">
</div>
<div class="child">
</div>
</div>
<br/>
<div class="parent">
<div class="child" >
</div>
<div class="child" style="height:300px;">
</div>
</div>
```
```CSS
.parent {
display:flex;
width:300px;
justify-content:center;
align-content:center;
align-items:stretch;
}
.child {
width:100px;
outline:solid 1px;
}
```
思路是创建一个只有一行的flexbox然后用stretch属性让每个元素高度都等于行高。
自适应宽:
```HTML
<div class="parent">
<div class="child1">
</div>
<div class="child2">
</div>
</div>
```
```CSS
.parent {
display:flex;
width:300px;
height:200px;
background-color:pink;
}
.child1 {
width:100px;
background-color:lightblue;
}
.child2 {
width:100px;
flex:1;
outline:solid 1px;
}
```
这个就是Flex设计的基本能力了给要自适应的元素添加flex属性即可。
## 总结
今天我们从Flex的设计、原理和应用三个方面一起学习了Flex排版。
我们先从感性的角度介绍了Flex的设计Flex的设计是一种不同于流布局的自外而内的设计思路。
接下来我们讲解了Flex的实现原理也就是具体的排版算法。要想理解Flex排版的原理主轴和交叉轴是非常重要的抽象Flex排版三个步骤分行、计算主轴、计算交叉轴。
最后我们给出了几个例子解决了旧时代的CSS三大经典问题。
最后给你留一个小问题请根据我的代码和文字编写一段使用“position:absolute”来模拟Flex布局的js。大家可以根据自己的水平简化需求比如可以实现一个仅仅支持横向的、单行的所有flex项必须指定高度的Flex布局。