前言:
布局的传统解决方案,基于盒状模型,依赖 display属性 + position属性 + float属性。它对于那些特殊布局非常不方便,比如,垂直居中 就不容易实现。
2009年,W3C提出了一种新的方案----Flex布局,可以简便、完整、响应式地实现各种页面布局,2012年得到进一步完善。
2009年版本的语法已经过时(display: box),使用的时候为了兼容需要加上一些前缀
/* 形如: */ display: -webkit-box; /* Chrome 4+, Safari 3.1, iOS Safari 3.2+ */ display: -moz-box; /* Firefox 17- */ display: -webkit-flex; /* Chrome 21+, Safari 6.1+, iOS Safari 7+, Opera 15/16 */ display: -moz-flex; /* Firefox 18+ */ display: -ms-flexbox; /* IE 10 */ display: flex; /* Chrome 29+, Firefox 22+, IE 11+, Opera 12.1/17/18, Android 4.4+ */
所以还是建议使用新版形式
2012年将是往后标准的语法(display: flex),大部分浏览器已经实现了无前缀版本。
啰嗦那么多,正式开始
一、Flex 是什么,为什么要用?
就 W3C 官方给到的解释是,这是设计来实现更复杂的版面布局。
那我自己对他的定义是,Flexbox 从本质上就是一个 Box-model 的延伸,我们都知道 Box-model 定义了一个元素的盒模型,然而 Flexbox 更进一步的去规范了这些盒模型之间彼此的相对关系。而不需要去用一些很 cheat 的做法,去 hack 一些本来其实不应该用来做版面布局的属性。
Flex布局主要思想是让容器有能力让其子项目能够改变其宽度、高度(甚至顺序),以最佳方式填充可用空间(主要是为了适应所有类型的显示设备和屏幕大小)。Flex容器会使子项目(伸缩项目)扩展来填满可用空间,或缩小他们以防止溢出容器。
最重要的是,Flexbox布局方向不可预知,他不像常规的布局(块就是从上到下,内联就从左到右)。而那些常规的适合页面布局,但对于支持大型或者杂的应用程序(特别是当他涉及到取向改变、缩放、拉伸和收缩等)就缺乏灵活性。
围绕着三个主要问题,来了解Flex布局
1. 这能做什么?也就是他能解决什么问题?
2. 能用在哪裡?在哪些地方能用这个方法?
3. 为什么能用?他实现所用到的逻辑是什么?
当然了,这仨问题也直接贯穿在功能实现当中,所以还是来了解使用的方式。
二、Flex的基本概念
因为Flex布局是相对模块而言,而不是一个属性,它涉及很多东西,包括其整个组属性。他们当中一部分是容器(父元素,称为“伸缩容器”container),另一部分是子元素(称为“伸缩项目” flex item)。
常规布局是基于块和内联流方向,而Flex布局是基于flex-flow流。这张图,解释了flex布局的主要思想。
容器默认存在两根轴:水平的主轴(main axis)和垂直的交叉轴(cross axis)。
主轴的开始位置(与边框的交叉点)叫做main start,结束位置叫做main end;交叉轴的开始位置叫做cross start,结束位置叫做cross end。
项目默认沿主轴排列。单个项目占据的主轴空间叫做main size,占据的交叉轴空间叫做cross size。
三、Flex 的使用方法
跟常规布局操作一样,flex也是基于css属性的设置来实现。
如上图所示,主要包括 设置父容器的属性 和 设置子项目的属性(如果又有内嵌的容器那就同理)
(1)父容器的属性
1.display:flex | inline-flex;(适用于父容器)
这个是用来定义伸缩容器,是内联还是块取决于设置的值。这个时候,他的所有子元素将变成flex文档流,称为伸缩项目。
display: other values | flex | inline-flex;
如果是Webkit内核的浏览器,必须加上-webkit前缀。比如:
display: -webkit-flex;
但有两点要注意的是,父容器设置flex之后:
- CSS的columns在伸缩容器上没有效果。
- float、clear和vertical-align在子项目上没有效果。
2.flex-direction(适用于父容器)
flex-direction属性决定主轴的方向(即项目的排列方向)。
flex-direction: row | row-reverse | column | column-reverse
- row(默认值):在“ltr”排版方式下从左向右排列;在“rtl”排版方式下从右向左排列。
- row-reverse:与row排列方向相反,在“ltr”排版方式下从右向左排列;在“rtl”排版方式下从左向右排列。
- column:类似 于row,不过是从上到下排列
- column-reverse:类似于row-reverse,不过是从下到上排列。
注:
主轴起点与主轴终点方向分别等同于当前书写模式的始与结方向。 其中“ltr”所指文本书写方式是“left-to-right”也就是从左向右书写; 而“rtl”所指的刚好与“ltr”方式相反,其书写方式是“right-to-left”,也就是从右向左书写
那不如来个例子:
现在有一个父容器div,其中有5个子项目div.
为了保证效果展示,父容器暂设width: 40%; min-height: 250px; 子项目分别设置不同宽 width: 10%|15%|20%; 高度暂设固定高度30px(但设置高度会时stretch失效)
基本代码为:(后续例子都是基于这个展开,变动的部分为关键的各 flex属性)
<div class="box"> <div class="item item1 center">item1</div> <div class="item item2 center">item2</div> <div class="item item3 center">item3</div> <div class="item item4 center">item4</div> <div class="item item5 center">item5</div> </div>
<style type="text/css"> html,body,div {margin: 0;padding: 0} .center { padding-top: 5px; text-align: center; background: #abc; font: bold 14px/1.2em Arial, Helvetica, sans-serif ; color: #fff; } .item { border: 2px solid #0f0; } .item1 { width: 10%; height: 30px; } .item2 { width: 10%; height: 30px; } .item3 { width: 15%; height: 30px; } .item4 { width: 15%; height: 30px; } .item5 { width: 25%; height: 30px; } .box { display: flex; display: -webkit-flex; flex-direction: row; margin: 10px auto; width: 40%; min-height: 250px; overflow: hidden; border: 2px solid #0cf; } </style>
更新flex-direction的值,row | row-reverse | column | column-reverse 顺序变化
3.flex-wrap(适用于父容器)
这个主要用来定义伸缩容器里是单行还是多行显示,侧轴的方向决定了新行堆放的方向。
flex-wrap: nowrap | wrap | wrap-reverse
- nowrap(默认值):伸缩容器单行显示,“ltr”排版下,伸缩项目从左到右排列;“rtl”排版上伸缩项目从右向左排列。
- wrap:伸缩容器多行显示,“ltr”排版下,伸缩项目从左到右排列;“rtl”排版上伸缩项目从右向左排列。
- wrap-reverse:伸缩容器多行显示,“ltr”排版下,伸缩项目从右向左排列;“rtl”排版下,伸缩项目从左到右排列。(和wrap相反)
为了看到wrap效果,先增大子项目宽度
.item1 { width: 40%; height: 30px; } .item2 { width: 40%; height: 30px; } .item3 { width: 60%; height: 30px; } .item4 { width: 60%; height: 30px; } .item5 { width: 80%; height: 30px; } .box { display: flex; display: -webkit-flex; flex-direction: row; flex-wrap: nowrap; margin: 10px auto; width: 40%; min-height: 250px; overflow: hidden; border: 2px solid #0cf; }
更新flex-wrap的值,nowrap| wrap| wrap-reverse 顺序变化
4.flex-flow(适用于父容器)
这个是“flex-direction”和“flex-wrap”属性的缩写版本。同时定义了伸缩容器的主轴和侧轴。其默认值为“row nowrap”。
flex-flow: <‘flex-direction’> || <‘flex-wrap’>
比如 column nowrap 和 column wrap-reverse
.box { display: flex; display: -webkit-flex; flex-flow: column nowrap; ... }
5.justify-content(适用于父容器)
这个是用来定义伸缩项目沿着主轴线的对齐方式。当一行上的所有伸缩项目都不能伸缩或可伸缩但是已经达到其最大长度时,这一属性才会对多余的空间进行分配。当项目溢出某一行时,这一属性也会在项目的对齐上施加一些控制。
justify-content: flex-start | flex-end | center | space-between | space-around
- flex-start(默认值):伸缩项目向一行的起始位置靠齐。
- flex-end:伸缩项目向一行的结束位置靠齐。
- center:伸缩项目向一行的中间位置靠齐。
- space-between:伸缩项目会平均地分布在行里。第一个伸缩项目一行中的最开始位置,最后一个伸缩项目在一行中最终点位置,项目之间的间隔都相等。
- space-around:伸缩项目会平均地分布在行里,每个项目两侧的间隔相等。所以,项目之间的间隔比项目与边框的间隔大一倍。
这会儿先把子项目的宽度恢复咯
.item1 { width: 10%; height: 30px; } .item2 { width: 10%; height: 30px; } .item3 { width: 15%; height: 30px; } .item4 { width: 15%; height: 30px; } .item5 { width: 25%; height: 30px; } .box { display: flex; display: -webkit-flex; flex-direction: row; justify-content: flex-start; ... }
按顺序 更新 justfy-content
direction为row是这样,为column同理,自行联想吧
6.align-items(适用于父容器)
这个主要用来定义伸缩项目可以在伸缩容器的当前行的侧轴上对齐方式。可以把他想像成侧轴(垂直于主轴)的“justify-content”。
align-items: flex-start | flex-end | center | baseline | stretch
- flex-start:伸缩项目在侧轴起点边的外边距紧靠住该行在侧轴起始的边。
- flex-end:伸缩项目在侧轴终点边的外边距靠住该行在侧轴终点的边 。
- center:伸缩项目的外边距盒在该行的侧轴上居中放置。
- baseline:伸缩项目根据他们的基线对齐。
- stretch(默认值):伸缩项目拉伸填充整个伸缩容器。如果项目未设置高度或设为auto,将占满整个容器的高度
stretch的使用受到高度的影响,所以可先把item1-3-5取消高度的设置
.item1 { width: 10%; /* height: 30px; */ } .item2 { width: 10%; height: 30px; } .item3 { width: 15%; /* height: 30px; */ } .item4 { width: 15%; height: 30px; } .item5 { width: 25%; /* height: 30px; */ } .box { display: flex; display: -webkit-flex; align-items: flex-start; ... }
按顺序更新 align-items
7.align-content(适用于父容器)
这个属性主要用来调准伸缩行在伸缩容器里的对齐方式。类似于伸缩项目在主轴上使用“justify-content”一样。
注:请注意本属性在只有一行的伸缩容器上没有效果。
align-content: flex-start | flex-end | center | space-between | space-around | stretch
- flex-start:各行向伸缩容器的起点位置堆叠。
- flex-end:各行向伸缩容器的结束位置堆叠。
- center:各行向伸缩容器的中间位置堆叠。
- space-between:各行在伸缩容器中平均分布。
- space-around:各行在伸缩容器中平均分布,在两边各有一半的空间。
- stretch(默认值):各行将会伸展以占用剩余的空间。
因为只有一行的伸缩容器看不到效果,那就再把子项目的宽度改回来先,让它换行满足多行的条件
.item1 { width: 40%; height: 30px; } .item2 { width: 40%; height: 30px; } .item3 { width: 60%; height: 30px; } .item4 { width: 60%; height: 30px; } .item5 { width: 80%; height: 30px; } .box { display: flex; display: -webkit-flex; flex-wrap: wrap; align-content: flex-start; ... }
则按顺序更新 align-content
(2)子项目的属性
1.order(适用于子项目)
order属性定义项目的排列顺序。数值越小,排列越靠前,默认为0。
order: <integer>
先将各子项目宽度恢复为小值,设置item5的order值为 -1 ,item2 的为1
.item1 { width: 10%; height: 30px; } .item2 { width: 10%; height: 30px; order: 1; } .item3 { width: 15%; height: 30px; } .item4 { width: 15%; height: 30px; } .item5 { width: 25%; height: 30px; order: -1; } .box { display: flex; display: -webkit-flex; flex-direction: row; ... }
改变 direction row | column 可看到
2.flex-grow(适用于子项目)
根据需要用来定义伸缩项目的扩展能力。它接受一个不带单位的值做为一个比例。主要用来决定伸缩容器剩余空间按比例应扩展多少空间。
如果所有伸缩项目的“flex-grow”设置了“1”,那么每个伸缩项目将设置为一个大小相等的剩余空间。如果你给其中一个伸缩项目设置了“flex-grow”值为“2”,那么这个伸缩项目所占的剩余空间是其他伸缩项目所占剩余空间的两倍。负值无效。
flex-grow: <number> (默认值为: 0 即如果存在剩余空间,也不放大。)
暂去掉子项目的order属性,我们先来看看初始时 和 加了 flex-grow后(item1 设为1,item2设为2)的区别
当direction为row时,将剩余空间吃透
当direction为column 时,将剩余空间吃透
3.flex-shrink(适用于子项目)
根据需要用来定义伸缩项目收缩的能力。负值无效。
flex-shrink: <number> (默认值为: 1 即如果空间不足,该项目将缩小。)
好了,又有机会把子项目宽度设大了。默认 shrink值为1, 为0则不缩小,数字更大则缩小程度更大
.item1 { width: 40%; height: 30px; flex-shrink: 1; } .item2 { width: 40%; height: 30px; } .item3 { width: 60%; height: 30px; flex-shrink: 1; } .item4 { width: 60%; height: 30px; } .item5 { width: 80%; height: 30px; flex-shrink: 1; }
现在shrink值都为1,把item1值设为0不缩小,item3值设为3,item5值设为5 看看变化
4.flex-basis(适用于子项目)
flex-basis属性定义了在分配多余空间之前,项目占据的主轴空间(main size)。
浏览器根据这个属性,计算主轴是否有多余空间。它的默认值为auto,即项目的本来大小。 负值无效。
flex-basis: <length> | auto (默认值为: auto)
比如对于item1,设置其basis为100px,加上它的边框总width为104px ,计算后发现主轴还有剩余空间,则按一定规则计算伸展的长度
.item1 { width: 10%; height: 30px; flex-basis: 100px; flex-grow: 1; }
5.flex(适用于子项目)
这是“flex-grow”、“flex-shrink”和“flex-basis”三个属性的缩写。其中第二个和第三个参数(flex-shrink、flex-basis)是可选参数。默认值为“0 1 auto”。
flex: none | [ <‘flex-grow‘> <‘flex-shrink‘>? || <‘flex-basis‘> ]
该属性有两个快捷值:auto (1 1 auto) 和 none (0 0 auto)。
建议优先使用这个属性,而不是单独写三个分离的属性,因为浏览器会推算相关值。
6.align-self(适用于子项目)
align-self属性允许单个项目有与其他项目不一样的对齐方式,可覆盖align-items属性。
默认值为auto,表示继承父元素的align-items属性,如果没有父元素,则等同于stretch。
align-self: auto | flex-start | flex-end | center | baseline | stretch
.item1 { width: 10%; height: 30px; align-self: flex-start; } .item2 { width: 10%; height: 30px; align-self: flex-end; } .item3 { width: 15%; height: 30px; align-self: center; } .item4 { width: 15%; height: 30px; align-self: baseline; } .item5 { width: 25%; height: 30px; align-self: stretch; }
初始样式和加了 align-self的对比
四、grow shrink 的计算规则
可以看到,各子项目扩张收缩的程度是不同的,那么它们是怎么计算的呢?
flex-grow 计算的原理是跟flex flex-basis属性有关的,flex-shrink 的计算原理也和flex-basis有关
先来了解flex-basis ,这个属性在 flex 容器为横向的时候,其实就是宽度,当我们把 item 指定成 flex: 0 0 480px 时,其实就是把它的宽度设定成 480px。
flex-basis属性 它的默认值为auto,即项目的本来大小
好来看看是怎么计算的
1.先来看看grow
grow 表示在 item 总宽度比容器小的时候,为了让 item 填满容器,每个 item 增加的宽度。
假设有三个 basis 为 100px 的 item。
我们从左到右给予 grow 值分别为 3、2、1,那么当 flex 作用之后,最左边的 item 实际增加的宽度是多少?
从图中可以算到增加的宽度是 90px,于是最后最左边 item 的宽度是 190px。
2.再来看看 shrink
grow 跟 shrink 其实是双胞胎,其实很像
shrink 表示在 item 总宽度比容器大的时候,为了让 item 填满容器,每个 item 减少的宽度。
但是计算的公式却是不一样的。为什么?
因为当你在加的时候无所谓,但是在减的时候,如果只计算赋予的 shrink 值,那么很有可能最后减少的宽度比 basis 大,于是 item 的宽度就变成负值。
那我们该怎么修正?
把 basis 当成参数计算进去,这样就能保证减少的宽度永远小于 basis。
所以我们可以得到修正后的公式,一样以最左边为例子,最后计算出来减少 60px,于是 item 就变成 140px。
以上脑子不好使,没关系,实际上最常用的只是 flex: 1。
后记:
当然,知道属性的用法还不够,还需要更多的实例练习来掌握,Flex 布局教程:实例篇