Flexbox最大的优点是计算空间能力强,尤其是当对一个容器里未知数量的子项目进行布局时。更具体来说,当给导航栏或者网格布局时,它们所需的空间有可能远超过现在计算的数值。想象一下在CMS里面当用户增删导航栏的项目时,既不想更改样式,又要填充其空间。你甚至可以用它同时计算不同类型的单元。
对于不熟悉Flexbox的人来说,在CSS里它就是一种计算并分布空间的智能布局模式,同时也解决了一些困扰多年的布局问题以及hack。
开始使用Flexbox时会产生一些困惑,比如从基础的表格布局到浮动布局的变化有些困难。
了解Flexbox第一个基本概念是理解父元素(伸缩容器)和子元素(伸缩项目)的关系,只有在父元素添加 display:flex
才可以控制其子元素的布局,通常我会让子元素控制其内容。
第二个基本概念是缩写,之前你也许看过 flex:1 0 auto
这样的缩写,它包含Flexbox的3个属性,依次是 flex-grow
, flex-shrink
, flex-basis
flex-basis
属性决定着其他两个属性的行为,正如它的名字 flex-basis
是Flexbox项目的基准值,它决定着Flexbox项目可伸缩的剩余空间大小。 这个属性是用来设置伸缩的基准值,不允许使用负值。
在上面的例子,中间的项目没有设置伸缩比例,使其保持在 150px
,那么,其余两个项目剩余 450px
的空间可以进行伸缩,它们的扩展比例都为 2
并且伸缩基准值相同,所以每个平均分配 225px
。
有一点需要注意的是 flex-grow
值不依赖于其他项目。容器里一个 flex-grow
为 4
的项目不一定是另一个 flex-grow
为 2
的项目的 2
倍,它实际上做的是得到其它项目两倍的可用空间。
另一个例子展示 flex-grow
是如何工作的,如果将第三个项目的 flex-basis
改为 50px
,将看到不同的结果,如下例所示:
这里有两个大小不同的项目,正如前面所说项目一和项目三的大小不一定相同,那是因为 flex-basis
也会影响布局的分配。
尝试创建一个公式来计算每个项目实际分配的像素值,首先,第一个公式很简单:
可用空间 = (容器大小 - 所有相邻项目flex-basis的总和)
可用空间是Flexbox可用来分配的空间,创建一个公式计算每个项目可扩展的大小。在这个例子中可用空间为: 可用空间(600) - 伸缩基准值总和(300) = 300
。
可扩展空间 = (可用空间/所有相邻项目flex-grow的总和)
首先,计算上面的例子的所有相邻项目 flex-grow
总和( 2 + 0 + 2 = 4
)。再用 可用空间300 / 4 = 75
.那么,每个扩展空间大小为 75px
。
每项伸缩大小 = (伸缩基准值 + (可扩展空间 x flex-basis值))
所以,项目一( flex: 2 0 100px
)最后计算结果为 100px + (75 x 2)
等于 250px
。项目二( flex: 0 0 150px
)等于 150px
。项目三( flex: 2 0 50px
)为 100px + (75 x 2)
等于 200px
。我们再简化一下公式:
可用空间 =(容器大小 - 所有相邻项目flex-basis总和) 每项伸缩大小 = 伸缩基准值 + ((可用空间 / 所有相邻项目flex-grow总和) x 此项的flex-basis值)
下面的例子添加四项以及较大的容器,以作进一步测试。
假设上面的例子是在一个较大的屏幕上查看, 容器(1000) - flex-basis总和(150 + 200 + 175 + 225) = 可用空间(250)
。
可用空间 (250) / flex-grow总和(1 + 3 + 2 + 4) = 每项伸缩大小(25)
。最终每项计算结果如下。
150 + (25 x 1) = 175 200 + (25 x 3) = 275 175 + (25 x 2) = 225 225 + (25 x 4) = 325
在Firefox 32会以一种奇怪的方式舍入数字,我曾经注意到在Firefox 32计算伸缩值时允许超过一像素,而在其他浏览器只能是容器的最大尺寸。这也有可能关系到之前我在Firefox看到一个弹性项目与相邻项目之间存在半像素的间隔的bug。
更新:我收到一些确认信息,"bug"其实是由于火狐devtools误报每项的宽度,而计算出的宽度其实是正确的。
flex-shrink
也就是 flex:1 1 10px
中的第二个值,它应用于当项目超过容器计算值时就会将项目压缩。
本来,我以为可以应用一个公式计算 flex-shrink
:
每项flex收缩大小 = 伸展基准值总和 - (收缩比例 / 收缩比例总和 x 溢出的空间)
可是,经过无数次试验,我持续遇到不适用这种方式的情况,所以, flex-shrink
实际上是有不同之处的。在阅读 规范 之后,尤其 这个部分 ,在Opera Blog里查阅 the Flexbox basics 文章中找到了它的计算方法。
上面 flex-shrink
的例子并没有达到我所期望的,浏览器中如何处理shrink看似是很普遍的, 这意味着它的值是我们可以预想到的,但是我却无法计算出它的值。有些例子违背了我的计算方式,如 flexbox shrink测试 和 flex-shrink测试
之后我一直都在想这个问题导致失眠,后来我的朋友 Mike Riethmuller 最终帮我解决了这个问题,我可以安心的睡觉了。例如,像之前的例子中一个宽度最大为 600px
的容器和可用负空间( -200px
),通过每项收缩比例值乘以伸展基准值并累加。
项目一(1x100) + 项目二(2x200) + 项目三(3x200) + 项目四(1 x 200) = 所有项目总和(1400)
然后我们将每一项除以所有项目之和。例如项目二:
项目二收缩因数 = 项目二(2 x 200px)/所有项目之和(1400) = 0.286
然后乘以负可用空间(-200px)
第二项移除空间 = 项目二收缩因数(0.286) x 负可用空间(-200px) = -57.142向下舍入为57。
要使每个项目是另一个项目的精准倍数只能将 flex-basis
设置为 0
.
在这个例子中,因为每个项目 flex-basis
值为 0
,因此,它们彼此有相对关系,扩展比例为 3
的是值为 1
的 3
倍,是值为 2
的 1.5
倍。但是,如果这样做没有换行的话是因为没有设置换行的属性。
Flex也提供一种解决资源排序的方法,通过改变 order
属性使定位在Flexbox的对象与另一个对象交换顺序,虽然对于在所有地方都使用键盘的用户来说是有有困难的,但是这个选择是不错的,至少是为更好地给不同大小的设备设定内容提供了一个起点。
你有可能想知道到底什么时候需要使用资源排序。使用它有助于内容管理系统处理视觉'牵制'的项目。如果你有一个新闻列表是通过日期进行排序布局的,但你想添加一个有可能不是最新日期的新闻项目又不想改变其整体结构,那么,在不影响关键功能的前提下将其进行排序是一种很好的解决办法。
将项目的 order
设置为 -1
将始终显示在项目的是首位。下面是具体的css。
/* Every item in the grid */ .d-flex-order > li { display:flex; /* be 33% but don't be flexible */ flex:0 0 33%; list-style:none; } /* Featured item */ .d-flex-order > li.d-flex-pinned { /* always put this item first in the visual order */ order:-1; /* double the size of this in our grid */ flex:0 0 66%; } /* The parent element */ .d-flex-order { /* makes all items on a row equal height */ align-items:stretch; display:flex; /* wrap onto the next line rather than stretching or shrinking */ flex-wrap:wrap; /* tells flexbox to start items from the left */ justify-content:flex-start; max-width:960px; margin:0 auto; }
同样的,你可能以前用过 row-reverse
或者 column-reverse
,但你却不知道它们会打乱键盘 tab
的顺序。如果你将逆转看做是一种增强行为的方式,那么,它会帮助你解决排序机制的问题。 它提供了一种非常简单的办法通过使用这一条CSS属性就可以逆转搜索结果项、图像效果,图片库的顺序。如果你没有访问它,它也不会破坏功能。这样做是一个很好的办法。
Flexbox也提供了一种较少hack的水平垂直居中方法,有些开发者使用 table
来替代 float
制作垂直居中效果。在之前一个例子中你有可能注意到,我们可以在行或列中拉伸Flex项目,实现列的等高布局.
使用flex垂直居中非常简单只需要几个属性就可以解决,比 其他的hack 要简单的多。
只需要将每个项目设置 flex
值,并且在父元素添加 align-items:center
即可垂直居中。
/* Vertical align */ .d-flex-vertical { /* Tell flexbox to start vertically from the center */ align-items:center; display:flex; /* I'm also aligning it horizontally */ justify-content:center; } /* all the child element needs is to be a flex item */ .d-flex-vertical > div { display:flex; }
之前我们有很多方法解决每列等高问题,现在我们有更简单的方法。
注意:如果你用小窗口查看此例子,你将不会看到每列等高,而是每个项目都会换行这样不是不能实现,只不过不在这个例子的范围。
用非常简单的代码实现此样式,父元素添加 align-items
属性,每个项目设置 display:flex
。
/* Equal height columns */ .d-flex-parent { display:flex; align-items:stretch; } .d-flex-child { display:flex; }
你可以利用Flexbox属性设置几乎所有的动画,可以产生过渡效果。但由于我感觉大部分动画是比较多余,所以,动画属性一向不是我的重点。
我只喜欢动画的增加和减少。过渡是没有足够的能力检测到一个项目的增加或减少。所以只添加过渡就插入的话,动作的发生就仅仅会是一个突然的抖动。
为了使插入或者减少动画时有较小的抖动,就需要使用JavaScript的 transitionStart/transitionEnd
或者 animationStart/animationEnd
事件使动画变得更加流畅。
尽管现在企业仍然在兼容近十年前的浏览器和操作系统,因此,几乎不支持Flexbox。
甚至现在较主流的浏览器,例如:一些移动设备Windows手机和Android 4.x还是需要一些旧的语法支持,或者使用 grunt-autoprefixer
工具进行兼容。但是以我个人经验并没有感觉使用它是很完美的。
为了详细说明这一点,将上述列表在使用alaxy S3的Android4.1.2系统进行测试,发现手动添加 -webkit-box-orient:vertical
也接近于正常显示。由此看来,它有可能错误的匹配了系统,或者我用新的属性诠释了旧的属性。无论是哪种情况这都不是期望的结果,还需进一步研究。
当然,我毫无疑问推荐使用这些优秀的工具,因为它们会让代码编写工作变得更加容易。但是在我写这篇文章时,我觉得它们并不是很可靠,也许是我使用上的问题或者是工具本身的问题。但你需要明确当你兼容较老设备时,不会因为这些工具为了兼容而添加的许多属性感到头疼。
/* Here are some examples of differences between old and new. This is why we can't have nice things. But also, autoprefixer saves headaches. */ .display { /* Current */ display:flex; display:inline-flex; /* older */ display:-webkit-flex; display:-ms-flexbox; display:-webkit-box; } .axes { /* Current */ justify-content: flex-start; align-items: stretch; /* older */ -ms-flex-pack: start; -webkit-justify-content:flex-start; -webkit-box-pack:start -webkit-box-align:stretch; -webkit-align-items:stretch; -ms-flex-align:stretch; } .directions { /* Current */ order: 1; flex-wrap: wrap; flex-direction: column; /* older */ -webkit-box-wrap:wrap; -webkit-flex-wrap:wrap; -ms-flex-wrap:wrap; -ms-flex-direction:column; -webkit-box-orient:vertical; -webkit-box-direction: normal; } .flexin { /* current */ flex:1 0 30%; /* older */ -webkit-box-flex:1; -webkit-flex:1 0 30%; -ms-flex:1 0 30%; }
Flexbox 可以解决的事情太多了,以至于那些不支持该属性的浏览器所无法应对的场景也很多,所以这是一场渐进增强和优雅降级之间的斗争。
/* An example of this approach Enhancing navigation using Modernizr for feature detection to tell us if we should add flexbox */ /* Base */ .d-navigation { text-align:center; } .d-navigation li { display:inline-block; } /* Enhanced */ .flexbox .d-navigation { display:flex; justify-content: space-between; } .flexbox .d-navigation li { display:flex; }
自2009年以来,Flexbox一直以不同的形式展现在我们面前。但却只有极少数的浏览器和设备支持。我推荐暂且在小的组件上使用Flexbox用来管理改进用户界面组件。
便携式设备并不完全支持Flexbox,所以,最好使用autoprefixer,经过仔细核对后可以为较老的设备添加必要的属性。
虽然在很多年里用户依然会继续使用老的技术,但这并不意味着在日新月异的今天我们不能使用新的技术。特征检测已逐步增强,我们不必停留在2009年,逃离浏览器的限制,学习和尝试新的技术,比如:Flexbox,只要我们提供一个用户体验的基准就不必担心旧版本的IE浏览器或者Android用户。Flexbox的瓶颈问题是解决旧的语法,所以最好将它应用于较小的组件上比较安全,而不是应用于大型的框架上。