深入理解 flex 布局以及计算

Neargod 7年前
   <h2>起因</h2>    <p>对于Flex布局,阅读了 大漠老师和其他老师写的文章后,我还是不太理解Flexbox是如何弹性的计算子级项目的大小以及一些其他细节。在大漠老师的帮助下,我去查阅Flexbox 的 W3C 规范文档。</p>    <p>注:本篇博文不适合未接触过Flex 布局的人, 如果想了解flex 布局基础。</p>    <h2>对于flex盒模型的设计期望</h2>    <p>flex盒模型是被期望设计成:</p>    <ul>     <li>在任何流动的方向上(包括上下左右)都能进行良好的布局</li>     <li>可以以逆序 或者 以任意顺序排列布局</li>     <li>可以线性的沿着主轴一字排开 或者 沿着侧轴换行排列</li>     <li>可以弹性的在任意的容器中伸缩大小(今天重点研究的主题)</li>     <li>可以使子元素们在容器主轴方向上 或者 在容器侧轴方向上 进行对齐</li>     <li>可以动态的 <strong>沿着主轴方向</strong> 伸缩子级的尺寸,与此同时保证父级侧轴方向上的尺寸</li>    </ul>    <h2>主轴和侧轴</h2>    <p>很有必要先向大家解释清楚 3个问题</p>    <ul>     <li>什么是主轴</li>     <li>什么是侧轴</li>     <li>他们是如何切换的</li>    </ul>    <p>首先每一根轴都包括 三个东西:维度、方向、尺寸</p>    <p>什么意思呢?</p>    <ul>     <li>所谓的维度实际上就是意思就是子元素 横着排还是竖着排( x 轴 或 y 轴)</li>     <li>方向 即排列子元素的顺序 顺序还是逆序</li>     <li>尺寸 即 width [ height ] : <strong>每一个子元素在主轴方向所占的位置的总和</strong> 如果主轴是水平的,那么尺寸就是父元素内所有 item 的 outerWidth 总和,如果主轴是垂直的,那么尺寸就是父元素的 outerHeight</li>    </ul>    <p>主轴是依靠 flex-direction 和 所有子元素在主轴方向上的 item-size 的总和确定的, flex-direction 这个属性可以控制子元素的排列方向和排列顺序。</p>    <p>侧轴是依靠 flex-wrap 和 所有子元素在主轴方向上的 item-size 的总和确定的, flex-wrap 可以控制子元素 在侧轴方向上的排列方式以及顺序。</p>    <p>而关于不同种类不同情况下的 item-size 我们会在下面讨论,现在您可以简单将它理解为 width [ height ]。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/94311ac2b216d350168f3988a9f93956.png"></p>    <p>盗规范中的一张图</p>    <p>为了方便 flex-direction + flex-wrap 合并成了一个属性 flex-flow</p>    <p>通过这个简单而复杂的属性,我们就能够控制所有子元素的水平和垂直方向,逆序排列和顺序,换行和不换行。</p>    <p>主侧轴的切换十分简单,当主轴设定的时候,它的垂直面,就默认被设定成了侧轴。如:</p>    <pre>  <code class="language-javascript">flex-flow: row-reverse wrap-reverse;</code></pre>    <p>这条CSS属性能够告诉我们那些信息?</p>    <ul>     <li>子元素是横着排列的,主轴是水平的横轴,侧轴是竖直的纵轴</li>     <li>子元素是逆序并沿着主轴排列的,从右到左</li>     <li>子元素是换行的</li>     <li>子元素是逆序并沿着侧轴排列的,从下到上</li>    </ul>    <h2>FFC (flex formatting context)</h2>    <p>Flexbox 布局新定义了格式化上下文,类似 BFC(block formatting context)。有多类似呢? 就是 除了布局和一些细节不同以外的一切规则都和 BFC 是相同的 。</p>    <p>注意: 我所指的Flexbox 是指设置了 display: flex; 或 display: inline-flex; 的盒子。不是指单单设置了 display: flex; 的盒子。</p>    <p>例如,设置了 display: flex; 或 display: inline-flex 的元素,和BFC一样,不会被浮动的元素遮盖,不会垂直外边距坍塌等等。</p>    <p>而对于设置了 display: inline-flex 的盒子来说,我们可以类比 display: inline-box; 行理解。即 一个被行列化后的 Flexbox。它不会独占一行,但是可以设置宽和高。</p>    <h3>与BFC的细微区别</h3>    <p>但需要注意的是以下几点细节,Flexbox 布局 和 Block 布局是有细微区别的</p>    <ul>     <li>Flexbox 不支持 ::first-line 和 ::first-letter 这两种伪元素</li>     <li>vertical-align 对 Flexbox 中的子元素 是没有效果的</li>     <li>float 和 clear 属性对 Flexbox 中的子元素是没有效果的,也不会使子元素脱离文档流(但是对Flexbox 是有效果的!)</li>     <li>多栏布局 (column-*) 在 Flexbox 中也是失效的,就是说我们不能使用多栏布局在Flexbox 排列其下的子元素(鱼和熊掌不可得兼嘛)</li>     <li>Flexbox 下的子元素不会继承父级容器的宽</li>    </ul>    <h2>flex item(flex 子元素)</h2>    <p>CSS解析器会把 定义了 display: flex; 和 display: inline-flex; 的 Flexbox 下的子元素外部装进一个看不见的盒子里,我们通过排列这些盒子来达到排序、布局、 伸缩的目的。</p>    <p>规范中把这种盒子 称为 flex item ,而子元素中包括了 标签节点 以及 文本节点。标签节点很容易理解,需要注意的是文本节点。</p>    <p>默认情况下, flex 会将 <strong>连续的文本节点</strong> 装进 flex-item 之中,使文本可以和标签节点一起排序和定位。</p>    <p>值得注意的是,空格也是文本节点,所以 white-space 会影响Flexbox 中的布局:</p>    <p><img src="https://simg.open-open.com/show/e6c67f76ed1feb097f7dfa94a0e76e40.png"></p>    <p>设置了 white-space: pre 的Flexbox</p>    <h2>flex-item-size 如何计算的</h2>    <p>item-size (尺寸)为 <strong>主轴方向上</strong> item 的 content 再加上自身的 margin 、 border 和 padding 就是这个 item 的尺寸。</p>    <p>在规范中 介绍了 flex-item content 的计算方式</p>    <p>分为以下这几种情况</p>    <h3>1. flex-basis 的优先级比 width[height]: 非auto; 高</h3>    <p>如果子元素没有内容和默认固定宽高,且设置了 flex-basis 。 flex-item content 以 flex-basis 来决定,无论 width[height] 设置了多少。</p>    <p>(可理解为 flex-basis 比 width[height]: 非auto; 的优先级高)</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/7eaba507563a462c5ebd70deda69a2e1.png"></p>    <p><em>flex-basis 的优先级比 width[height] 高,无论 width[height] 设置多少,flex-item content都以 flex-basis 来决定 </em> 。</p>    <h3>2.元素存在默认宽高</h3>    <p>如果子元素有默认固定宽高(例如 input 标签)、并且设置了 flex-basis ,那么它的 content 以 固定宽高为下限,如果 flex-basis 超过了固定宽高,那么 flex-basis 则成为其 content ,如果 flex-basis 比固定宽高小,那么以固定宽高为 content 。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/ad867339f6cf828882eb7715ceed3853.png"></p>    <p>对于固定元素的尺寸设定</p>    <h3>3.元素存在 min-width[height] 或者 max-width[height]</h3>    <p>如果 flex-item 有 min-width[min-height] 的限制,那么 flex-item content 按照 min-width 值为下限,如果 flex-basis 的值大于 min-width[min-height] 那么 flex-item content 以 flex-basis 计算。</p>    <p>如果 flex-basis 的值小于 min-width[min-height] 那么 flex-item content 以 min-width[min-height] 计算:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/8b512c5394446498dcf14c3aa757eb98.png"></p>    <p>如果 min-width[min-height] 的值已经超出了容器的尺寸,那么即使设置了 flex-shrink 。 CSS解析器也不会进行将这个 item 的 content shrink,而是坚持保留它的 min-width[min-height] :</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/b053bcfb6bd48e7266dacb4dfb194a7c.png"></p>    <p><em>如果Flexbox 设置的 min-width 超出了flex container 的范围, 不会对其进行压缩 </em> 。</p>    <p>反之,如果设置了 max-width[height] 的值,那么设置 flex-basis 无法超过这个值,对于 flex-grow 也仅仅只会增长到 max-width[height] 这个上限。</p>    <p>在下面的章节,我们会仔细讨论这种情况下,布局的计算。</p>    <h3>4.width[height]: auto; 优先级等于 flex-basis。</h3>    <p>前面提到,如果给item同时设置了 width[height] 和 flex-basis 的话。flex-item content以 flex-basis 来决定。但是实际上默认的 width[height]: auto; 优先级是等于 flex-basis 的。</p>    <p>CSS解析器对比两者的值,两者谁大取谁 作为item的基本尺寸,如果一个item没有内容,flex-item content就会以 flex-basis 来决定。</p>    <p>但是如果item有了内容,且内容撑开的尺寸比 flex-basis 大,那么flex-item content就会以 width[height]: auto; 来决定,且 <strong>无法被 shrink</strong> 。反之,如果比 flex-basis 小,flex-item content就会以 flex-basis 来决定:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/c4f128e565171ba0c423e3964808e245.png"></p>    <p>width: auto; 内容长度比 flex-basis 大,则 flex-item content以内容长度来决定,且无法shrink</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/4ae5b9401f07c9d8c555e6fb18ebd851.png"></p>    <p>如果 flex-basis 的长度大于文字内容长度,那么flex-item content以 flex-basis 来决定</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/1ce784360766ac174063d540f5d4efe3.png"></p>    <p>同时设置了 flex-basis: 800px; 和 width: 1px; flex-item content以 flex-basis 来决定,可以发生shrink</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/d3c2e6dceb25e8f588c98e9fa36ddfb4.png"></p>    <p>注意2号盒子我设置了 flex-shrink: 1; 1号盒子和3号盒子我设置了 flex-shrink: 0; 意思就是我将所有的需要shrink的空间都压到了2号盒子上,总共的需要 shrink的空间为 0 * 600 + 1 * 20 + 0 * 100 = -20 ;而2号盒子只有 20 的空间,理应被完全shrink变为 0 ,但是值得注意的是2号盒子并没有被完全 shrink,还保留了一个文字的距离。</p>    <p>除此之外, overflow: hidden; 也会影响</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/ac2c5e9ad2e67df67b6824cf94ece976.png"></p>    <p><em>overflow: hidden; 把文字长度限制在了 600px ; 小于 flex-basis: 700px; 所以flex-item content以 flex-basis 来决定,可以 shrink </em> 。</p>    <h2>隐藏属性对 items-size 的影响</h2>    <p>我针对了 display: none; visibility: hidden; visibility: collapse; transform: scale; 等属性对 items 进行测试。</p>    <p>结果如下:</p>    <ul>     <li>如果设置了 visibility: hidden; | visibility: collapse; | transform: scale; 的flex-item content 依然被算进主轴尺寸,CSS 解析器依然会以他们 flex-grow | flex-shrink 将可用空间 或者 负可用空间 分配给他们</li>     <li>如果设置了 display: none; CSS解析器不会对该item的空间进行计算,也不会对其grow空间</li>    </ul>    <h2>关于position: absolute 对item影响</h2>    <p>position: absolute 也是适用 Flexbox 中的子元素的,并且,设置了 position: absolute 属性的子元素,也会受到 Flexbox 排列的影响。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/6963750c9c15388eaa460821cf41a00f.png"></p>    <p>设置了 absolute 的子元素重叠在了一起,但是依然会受到 align-items: center; 的影响而居中 。对于 Flexbox 来说,设置了 position: absolute; 并不会对其下的子元素产生任何影响。</p>    <p>我们重点看 Flexbox 下的子元素设置了 absolute 后有什么结果。</p>    <p>根据我做的实验,我得到了如下的结论:</p>    <p>flexbox 下设置了absolute的子元素的位置受3个方面的影响:</p>    <ul>     <li>flexbox 流下面的 justify-content 和 align-items</li>     <li>单个子元素的 top 、 left 、 right 、 bottom</li>     <li>单个子元素的 margin</li>    </ul>    <p>这里我们不讨论 translate 因为 translate 只是视觉上位置的改变:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/861e14d239a13b0083591a931b50e63a.png"></p>    <p><em>设置了 absolute 的item 不会影响布局, </em> 如图,其中1 2 3 4 5 号是设置了 absolute 的item,而 6 7 8 9号是没有设置 absolute 的item Flexbox 我设置了 justify-content: center; 和 align-items: center; 每一个item我都给了 margin: 20px; :</p>    <ul>     <li>我们可以看到,由于 absolute 的原因, 12345号的item 不会影响 6789号的排布。结论: <strong>脱离了文档流的 item 不会影响 正常的flex 布局</strong> 。</li>     <li>如图上 4号 item, 设置了 absolute 但是没有设置 top / left 这些值,位置居中偏下。结论: 如果对子元素设置了 position: absolute; 属性而没有设置 top left 这些值。子元素受 Flexbox 的 justify-content: center 、 align-items: center 和 margin 的影响</li>     <li>如图上1235 号item, 我给他们分别设置 top 、 left 、 right 、 bottom 等值。5号元素设置了 margin-left: 50px; 和 padding-bottom: -999999px; 结论: top 、 left 、 right 、 bottom 等值会覆盖 justify-content: center; 和 align-items: center; 设置的位置,使item 自由定位。 margin 自始至终都会影响item的位置,而 padding 不会(我试过 padding 设 500px 的情况, padding 会影响item的大小)</li>     <li>如果对上图 12345号item 不设置 top 、 left 、 right 、 bottom 等值。对父级的 justify-content 和 align-items 设置 center 以外的其他值的话:如果设置了 flex-start 所有元素不分开,定位在 主轴起点;如果设置了 flex-start 所有元素不分开,定位在 主轴终点;如果设置 justify-content: space-around; 效果等同于 center ,即所有的元素叠在一起居中,且items不会产生间隔;如果设置了 justify-content: space-between; 效果等同于 flex-start , 且items不会分开;如果设置了 align-items: flex-start; 所有元素不分开,定位在 侧轴起点;如果设置了 align-items: flex-end; 所有元素不分开,定位在 侧轴终点;如果设置了 align-items: stretch | baseline; 也是没有任何效果, items 不会跟随侧轴拉伸 或是 根据baseline 对齐</li>     <li>如果对单个item 设置 align-self,除了 flex-start | flex-end | center 有效之外,其他都失效</li>    </ul>    <p>通过上面一系列的测试我们可以清楚的认识到 justify-content 、 align-items 和 top 、 left 、 right 、 bottom 都是位置属性,而且 top 、 left 、 right 、 bottom 会覆盖 justify-content 和 align-items 的值。(以上前提是一定要设置 position: absolute 不然 top 、 left 、 right 、 bottom 无效)。</p>    <p>而 margin 的优先级是和 top 、 left 、 right 、 bottom 一样的,也就是说 margin 和 top 、 left 、 right 、 bottom 所设置的值会同时生效。</p>    <p>优先级排序为: margin = justify-content | align-items > top、left、right、bottom 。</p>    <h3>flex-basis、flex-grow、flex-shrink 以及相应的计算</h3>    <p>flex-basis 、 flex-grow 、 flex-shrink 是FFC下特有的属性,只有父级元素设置了 display: flex | inline-flex; 才会生效,并且只针对主轴方向生效。</p>    <p>如果 主轴是水平的,即 flex-direction: row; 那么 flex-basis 、 flex-grow 、 flex-shrink 控制的就是单个item的宽度。</p>    <p>如果 主轴是垂直的,即 flex-direction: column; 那么 flex-basis 、 flex-grow 、 flex-shrink 控制的就是单个item的高度。而 flex-grow 和 flex-shrink 是用于 主轴方向上对 (负)可用空间 进行伸缩的。</p>    <p>这要分两种情况,换行或者不换行。</p>    <h3>1.如果 flex-wrap: nowrap; 即不换行。</h3>    <p>那么所有items 都会在主轴方向上的一条线上排列,CSS解析器会计算 items 在主轴方向上所占的空间 相对于 Flexbox 在主轴方向的所占的空间进行比较计算。</p>    <p>如果 items 所占的空间是小于Flexbox的 那么说明Flexbox 还没有填满,CSS解析器就会计算还有多少空间没有填满,根据每一个item所设置的 flex-grow 设置的值,将这些空间分配按比例分配给每一个item。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/d87dc77677d978c1be3540154b748192.png"></p>    <p>可用空间</p>    <p>如果 items 所占的空间是大于Flexbox的 那么说明Flexbox 被填满了,CSS解析器就会计算超出了多少空间,根据每一个item所设置的 flex-shrink 设置的值,将这些空间分配按比例缩小每一个item</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/772fdea0b85ee70aa476c994f2a2df2c.png"></p>    <p>超出的空间</p>    <p>那么CSS解析器在这种情况下是怎样计算的呢?上一章我们劳神费力理解的item-size终于派上用场了。 flex-grow 计算流程是:</p>    <pre>  <code class="language-javascript">可用空间 = 将flexbox-content - 每个item-size的总和</code></pre>    <p>将每一个item所设置的 grow 全部加起来,将可用空间除以grow,得到单位分配空间。</p>    <p>根据每一个item 设置的 grow 来算,如果一个item 的grow 为 2,那么 这个 item 在主轴上的尺寸就需要延伸 2*单位分配空间的大小。</p>    <p>那么 每一个 item 就需要在原基础上 加上被分配的大小 就完成了grow:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/a541830170cd573cc659e1d0e77ba298.png"></p>    <p>分配前</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/a515a10cfaa39c4020b4171f24bc9bb7.png"></p>    <p>分配后</p>    <p>简单理解就是,将超出的部分,可能多,也可能少,根据 grow 来分配成 x 份,在根据每个 item 所设置的份数,将相差的部分分割给每一个item。</p>    <p>注意:flex-shrink 的计算流程和flex-grow的计算流程不同。</p>    <p>flex-shrink 计算流程是:先讲所有项目 按照 flex-shrink * item-size 的方式加起来 得到一个加权和,然后计算出 每一份 item 的 shrink比例:</p>    <pre>  <code class="language-javascript">shrink比例 = flex-shrink * item-size / 之前的总和;</code></pre>    <p>然后计算 子元素超出父级的部分(负可用空间),每一个item 减去这个 shrink比例 * 负可用空间即可 :</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/edbd9bcaf346ed5196b6cbef511673d0.png"></p>    <p>shrink前</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/88a99f3279ac66a68a003f50c1cc5bdb.png"></p>    <p>shrink后</p>    <h3>2. 如果flex-wrap: wrap[wrap-reverse]; 即换行</h3>    <p>那么items 都会先在主轴方向上的多条线上排列,CSS解析器先会计算 每一条线 在主轴方向上尺寸 相对于 Flexbox 容器的 width[height] 进行比较计算,每条线之间互不干扰:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/234e6951492bcbe71d250b3296648841.png"></p>    <p>未分配之前</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/c7f5be2091593c0cd6c726de0d329a8f.png"></p>    <p>平均分配后</p>    <p>由于在一行内 如果item-size 累加超过了Flexbox 的尺寸就会另起一行进行排列,所以在这种情况下,不会存在 shrink 的情况,而只有 grow 的情况。</p>    <h2>max-width[height] 情况下 flex-grow 的计算流程</h2>    <p>由于可能存在某一个或多个item 设置了有 max-width[height] 。所以,CSS引擎会先进行一次分配,分配后,统计那些有 max-width[height] 的items, 分配后是否有超出的剩余空间,然后对这些剩余空间再分配给那些没有设置 max-width[height] 的item</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/f9725fe5575a90bb5d6e450d4985a48a.png"></p>    <p>再分配流程</p>    <h2>min-width[height] 情况下 flex-shrink 的计算流程</h2>    <p>由于可能存在某一个或多个item 设置了有 min-width[height] 。所以,CSS引擎会先进行一次 shrink, shrink后,统计那些有 min-width[height] 的items, shrink后是否有的剩余的未 shrink空间,然后对这些剩余空间再分配给那些没有设置 min-width[height] 的item。</p>    <p>注意:第一次 shrink的算法和第二次分配未 shrink剩余空间的算法不同!</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/2bd4ba0b2e379272ae61a1cb608b19da.png"></p>    <h2>总结</h2>    <p>Flexbox 布局很棒。免去了我们大量关于适配方面的工作,但是深入理解,并用好它还是需要一点门槛的。 再次感谢 @大漠老师 的鼎力帮助,谢谢。</p>    <h2> </h2>    <p>来自:http://www.w3cplus.com/css3/flexbox-layout-and-calculation.html</p>    <p> </p>