控制Flex子元素在主轴上的比例

40次阅读

共计 10193 个字符,预计需要花费 26 分钟才能阅读完成。

背景

flex 布局更有效的实现对齐,空间分配。最近又学习下 flex 子元素的尺寸计算规则。

一、基本概念

1.1 主轴(Main axis)

定义了 flex 元素布局起始点和方向,flex 子元素在主轴上依次放置。
主轴有 4 个方向,通过 flex-direction 指定:

  • row
    水平方向,从左到右,默认的
  • row-reverse
    水平方向,从右到左
  • column
    垂直方向,从上到下
  • column-reverse
    垂直方向,从下到上

1.2 主轴的尺寸(Main axis size)

就是 flex 容器 content 矩形(不包含 padding, border, margin 区域)在主轴方向的尺寸。

1.3 交叉轴(Cross axis)

交叉轴就是跟主轴锤子的方向,主要用于 flex 元素的对齐。

1.4 交叉轴的尺寸(Cross axis size)

就是 flex 容器 content 矩形(不包含 padding, border, margin 区域)在 Cross 轴方向的尺寸。

1.5 flex 盒模型(flex box)

display 为flexinline-flex,的元素,也叫 flex 容器。

1. flex 容器包含的不仅是 flex 元素,也包含空白空间。

2. 涉及的 CSS

  • flex-direction
  • flex-wrap
  • flex-flow
    flex-direction 和 flex-wrap 的简写。
  • justify-content
    控制 flex 容器内容(flex 元素和空白空间)在主轴方向对齐。注意区分 align-items。
  • align-content
    控制多行 flex 容器个行的对齐方式。
  • align-items
    控制 flex 容器内容(flex 元素和空白空间)在交叉轴方向对齐。

Tip

  1. 这些 CSS 属性都是有相关性的:
    首页先指定 flex 容器的主轴方向(flex-direction), 如果 flex 子元素超过在主轴 尺寸,那就涉及是否换行(flex-wrap)。如果没有超过主轴尺寸,那就涉及行内对齐(justify-content), 如果存在多行每个行直接也要对齐(align-content)。
  2. 可能比较容易混淆 justify-content,align-content,align-items。
    记住 content 是指 flex 元素和空白空间,items 指的是 flex 元素。这样就容易就是这三个属性的用处了。

1.6 flex 元素(flex items)

1. 语法

flex box 的子元素,不包含流外子元素(absolute, fix 元素),但是包含 float 元素。

  1. flex 子元素相邻的 margin 不会发生合并。
  2. float 元素作为 flex 子元素时,float 属性无效(因为要参与 flex 布局,不是流式布局)。

2. 涉及 CSS 属性

  • flex-basis
    指定 flex 元素在主轴方向上占用 flex 容器的尺寸,默认为 auto,即以 flex 元素的尺寸作为其占用的尺寸(主轴是 row 取值 flex 元素的宽度,主轴是 column 取值 flex 元素的高度),根据该属性计算是否有空余空间。
    注意:flex 元素占用的尺寸跟 flex-basis 有关,跟元素的宽高没直接关系。
  • flex-grow
    指定各个 flex 元素弹性增长因数,即占用正空白空间(positive free space)的比例份额数量。0 值(默认值)表示不占用空白空间
  • flex-shrink
    指定各个 flex 元素弹性收缩因数,即分配负空白空间(negative free space)的比例份额数量。但是元素不能无限收缩为 0,也不能小于元素的最小尺寸(如 min-width 或者 min-height)。
  • flex
    flex-grow flex-shrink flex-basis 简写
  • align-self
    调整自己在交叉轴的对齐方式,只有在不撑满交叉轴时,这个属性才有效。
  • order
    指定顺序

二、计算自由空间和 flex-basis

flex 子元素在主轴上的比例依赖这三个 CSS 属性:

  • flex-basis
  • flex-grow
  • flex-shrink

其中:
flex-basis + flex-grow 组合控制着伸
flex-basis + flex-shrink 组合控制着缩
所以要确定 flex 子元素在主轴上的比例,首先要确定使用哪种组合。

2.1 规则

flex-basis 属性指定在任何空间分配发生之前初始化 flex 子元素的尺寸,更确切的说 flex-basis 属性指的 flex 子元素盒模型(box-sizing)的尺寸,所以跟 flex 子元素 width(height)取值逻辑类似,如果 box-sizing=content,则 flex-basis 也不包含 padding 和 border 区域。

2.2 剩余自由空间计算

  1. 自由空间计算
    flex 容器在主轴方向的 content 矩形的尺寸
  2. 期望自用空间
    在计算 flex 容器的自由空间前要先统计 flex 子元素占用的尺寸,注意这里指的是 flex 子元素的 margin 区域的尺寸,并且相邻的 flex 子元素 margin 是不会发生合并的。
  3. 剩余自由空间计算 = 自由空间计算 – 期望自用空间
  4. 正自由空间
    正值的剩余自由空间,此时采用flex-basis + flex-grow 组合。
  5. 负自由空间
    负正值的剩余自由空间,此时采用flex-basis + flex-shrink 组合。

三、深入了解 flex-grow

3.1 规则

如果存在正自由空间(positive free space),则采用 flex-basis + flex-grow 组合计算 flex 子元素在主轴上的比例。把正自由空间比作蛋糕的话,flex-grow表示希望分得蛋糕的量:

  • flex-grow: 0.2 表示希望获得 20% 的蛋糕;
  • flex-grow: 1 表示希望获得 100% 整个蛋糕(有点过分啊,不考虑其他兄弟);
  • flex-grow: 2 表示希望获得 200% 的蛋糕(这是明抢啊,态度很明确)。

但毕竟蛋糕就一个,flex 容器尽量满足 felx 子元素的要求,采用一种简单的按照比例分蛋糕方式:

  1. 累加 flex 子元素的 flex-grow 得出总和,简称 SUM_flex_grow;
  2. 如果 SUM_flex_grow=0,则不发生弹性增长,结束;
  3. flex 子元素增长的尺寸 = 正自由空间尺寸 * flex_grow / Max(SUM_flex_grow, 1)

3.2 Demo1:按照比例分蛋糕

function demo1() {
    return (
        <>
            <div className="flex">
                <div className="item" style={{flexBasis: 100, flexGrow: 1, marginRight: 10}}>One</div>
                <div className="item" style={{flexBasis: 150, flexGrow: 2,}}>Two</div>
            </div>
            <style jsx>{`
                .flex {
                    display: flex;
                    width: 600px;
                    outline: 1px dashed red;
                }
                .item {
                    padding: 10px;
                    border: 10px solid #666666;
                }
            `}</style>
        </>
    )
}

解析:

  1. 计算剩余自由空间

    • flex 容器主轴尺寸 = 600px
    • 元素 one 的希望尺寸 = 100px(flex-basis) + 20px(padding-left/right) + 20px(border-left/right) + 10px(margin-right) = 150px
    • 元素 two 的希望尺寸 = 150px(flex-basis) + 20px(padding-left/right) + 20px(border-left/right) = 190px
    • 剩余自由空间 = 600px - 150px - 190px = 260px,即存在正剩余空间。
  2. 计算各个 flex 子元素增长尺寸

    • SUM_flex_grow = 1 + 2 = 3,即大于 1,一个蛋糕不够分,只能按照比例分了。
    • 元素 one 的实际增长尺寸 = 260px * 1 / Max(1, 1 + 2) = 260px * 1 / (1 + 2) = 86.67px
    • 元素 two 的实际增长尺寸 = 260px * 2 / Max(1, 1 + 2) = 260px * 2 / (1 + 2) = 173.33px

3.3 Demo2:SUM(flex-grow) < 1

function demo3() {
    return (
        <>
            <div className="flex">
                <div className="item" style={{flexBasis: 100, flexGrow: 0.2, marginRight: 10}}>One</div>
                <div className="item" style={{flexBasis: 150, flexGrow: 0.3,}}>Two</div>
            </div>
            <style jsx>{`
                .flex {
                    display: flex;
                    width: 600px;
                    outline: 1px dashed red;
                }
                .item {
                    padding: 10px;
                    border: 10px solid #666666;
                }
            `}</style>
        </>
    )
}


解析:

  1. 计算剩余自由空间

    • flex 容器主轴尺寸 = 600px
    • 元素 one 的希望尺寸 = 100px(flex-basis) + 20px(padding-left/right) + 20px(border-left/right) + 10px(margin-right) = 150px
    • 元素 two 的希望尺寸 = 150px(flex-basis) + 20px(padding-left/right) + 20px(border-left/right) = 190px
    • 剩余自由空间 = 600px - 150px - 190px = 260px,即存在正剩余空间。
  2. 计算各个 flex 子元素增长尺寸

    • SUM_flex_grow = 0.2 + 0.3 = 0.5,即小于 1,一个蛋糕能满足大家需求,直接分给各个 flex 子元素。
    • 元素 one 的实际增长尺寸 = 260px * 0.2 / Max(1, 0.5) = 260px * 0.2 = 52px
    • 元素 two 的实际增长尺寸 = 260px * 0.3 / Max(1, 0.5) = 260px * 0.3 = 78px

注意

  • 如果 SUM(flex-grow)小于 1,此时剩余空间没有全部分配给各个 flex 子元素。

3.3 Demo3 跟 max-width 冲突

留意该栗子中:

  1. 元素 one, two, threebox-sizing=border-box
  2. 元素 one 的max-width=150px
function demo4() {
    return (
        <>
            <div className="flex">
                <div className="item" style={{flexBasis: 100, flexGrow: 1, marginRight: 10, maxWidth: 150}}>One</div>
                <div className="item" style={{flexBasis: 150, flexGrow: 2}}>Two</div>
                <div className="item" style={{flexBasis: 100, flexGrow: 3}}>Three</div>
            </div>
            <style jsx>{`
                .flex {
                    display: flex;
                    width: 800px;
                    outline: 1px dashed red;
                }
                .item {
                    padding: 10px;
                    border: 10px solid #666666;
                    box-sizing: border-box;
                }
            `}</style>
        </>
    )
}


解析:

  1. 计算剩余自由空间

    • flex 容器主轴尺寸 = 800px
    • 元素 one 的希望尺寸 = 100px(flex-basis) + 10px(margin-right) = 110px
      box-sizing=border-box
    • 元素 two 的希望尺寸 = 150px(flex-basis) = 150px
      box-sizing=border-box
    • 元素 three 的希望尺寸 = 150px(flex-basis) = 150px
      box-sizing=border-box
    • 剩余自由空间 = 800px - 110px - 150px - 150px = 390px,即存在正剩余空间。
  2. 计算各个 flex 子元素增长尺寸

    • SUM_flex_grow = 1 + 2 + 3 = 6,即大于 1,一个蛋糕不够分,只能按照比例分了。。
    • 元素 one 的增长尺寸 = 390px * 1 / Max(1, 6) = 390px * 1/6 =65px
      这样元素 one 的尺寸就是 100px + 65px = 165px,大于其max-width=150px 指定的最大值,所以最终元素 one 的尺寸是 150px。即元素 one 吃不完分配的蛋糕,把吃不完的蛋糕还回去了,让其他兄弟多分些(先抛个问题:这些吃不完的蛋糕如何分配呢?)。
  3. 元素 two 和元素 three 重新分配剩下是自由剩余空间,即回到步骤 1 重新计算。

    • flex 容器主轴尺寸 =800px - 元素 one 占领的尺寸(150px - 10px) = 640px
    • 剩余空间 = 640px - 150px - 150px = 340px
    • SUM_flex_grow = 2 + 3 = 5
    • 元素 two 的增长尺寸 = 340px * 2 / Max(1, 5) = 340px * 2 / 5 = 136px
    • 元素 three 的增长尺寸 = 340px * 3 / Max(1, 5) = 340px * 3 / 5 = 204px

3.4 小结:

  1. 计算剩余自由空间永远是第一步;
  2. 增长是个绝对值,即 flex 子元素会增加个绝对值(这是跟 flex-shrink 不同的地方);
  3. 当遇到 max- 属性冲突时,即元素 one 吃不完的蛋糕会放入总蛋糕中,由后面的 flex 子元素重新分配。

四、深入了解 flex-shrink

4.1 规则

如果存在负自由空间(negative free space),则采用 flex-basis + flex-shrink 组合计算 Flex 子元素在主轴上的比例。flex-shrink取值表达了个 flex 子元素贡献的愿望:

  • flex-shrink: 0.2 表示希望分摊负自由空间的 20%;
  • flex-shrink: 1 表示希望分摊 100% 负自由空间(够兄弟,其他兄弟不用分摊);
  • flex-shrink: 2 表示希望分摊 200% 负自由空间(分摊的态度很明确)。

flex 容器都感动哭了,但为了照顾各个 flex 子元素的感受,采用了一个“更合理”的分摊规则:

  1. 计算 flex 子元素的 content 矩形(内容矩形)在主轴尺寸 和 flex-shrink 乘积值,记作 A;
  2. 累加步骤 1 的乘积值,记作 SUM_A;
  3. 被分摊的负自由空间 valid_negative_free_space = negative_free_space * Min(1, SUM(flex-shrink))
  4. 每个 flex 子元素的收缩值 = valid_negative_free_space * A / SUM_A

计算的规则比上面的要复杂一些,不是简单的切分 negative-free-space。收缩量不仅依赖 flex-shrink, 还依赖 flex-basis。这样做只是为了“更合理”,即相同的 flex-shrink 情况下,flex-basis 越小的 flex 元素收缩的越慢(跟纳税一样,收入越高交的越多)。
注意:如果 flex-shrink 总和小于 1,则表示部分负自由空间被分摊了(即有些尺寸没有被收缩)。

4.2 Demo1:“减少贫富差距”

function demo5() {
    return (
        <>
            <div className="flex">
                <div className="item" style={{flexBasis: 100, flexGrow: 1, marginRight: 10}}>One</div>
                <div className="item" style={{flexBasis: 150, flexGrow: 2, flexShrink: 2}}>Two</div>
            </div>
            <style jsx>{`
                .flex {
                    display: flex;
                    width: 300px;
                    outline: 1px dashed red;
                }
                .item {
                    padding: 10px;
                    border: 10px solid #666666;
                }
            `}</style>
        </>
    )
}

解析(过长跟 flex-grow 过程类似):

  1. 计算剩余自由空间

    • flex 容器主轴尺寸 = 300px
    • 元素 one 的希望尺寸 = 100px(flex-basis) + 20px(padding-left/right) + 20px(border-left/right) + 10px(margin-right) = 150px
    • 元素 two 的希望尺寸 = 150px(flex-basis) + 20px(padding-left/right) + 20px(border-left/right) = 190px
    • 剩余自由空间 = 300px – 150px – 190px = -40px,即存在负剩余空间。
    • 被分摊的负剩余空间 = -40px * Min(1, 1 + 2) = -40px
  2. 计算各个 flex 子元素收缩尺寸

    • SUM_A = 100px * 1 + 150px * 2 = 400px
    • 元素 one 的实际收缩尺寸 = 40px * 100px * 1 / 400px= 10px,即最终宽度 = 100px - 10px = 90px
    • 元素 two 的实际收缩尺寸 = 40px * 150px * 2 / 400px = 30px,即最终宽度 = 150px - 30px = 120px

4.2 Demo: SUM(flex-shrink) < 1

function demo8() {
    return (
        <>
            <div className="flex">
                <div className="item" style={{flexBasis: 100, flexShrink: 0.2, marginRight: 10}}>One</div>
                <div className="item" style={{flexBasis: 150, flexShrink: 0.3}}>Two</div>
            </div>
            <style jsx>{`
                .flex {
                    display: flex;
                    width: 300px;
                    outline: 1px dashed red;
                }
                .item {
                    padding: 10px;
                    border: 10px solid #666666;
                }
            `}</style>
        </>
    )
}


flex 子元素超出了 flex 容器。
解析:

  1. 计算剩余自由空间

    • flex 容器主轴尺寸 = 300px
    • 元素 one 的希望尺寸 = 100px(flex-basis) + 20px(padding-left/right) + 20px(border-left/right) + 10px(margin-right) = 150px
    • 元素 two 的希望尺寸 = 150px(flex-basis) + 20px(padding-left/right) + 20px(border-left/right) = 190px
    • 剩余自由空间 = 300px - 150px - 190px = -40px,即存在负剩余空间。
    • 有效负剩余空间 = -40px * Min(1, 0.2 + 0.3) = -40px * 0.5 = -20px
  2. 计算各个 flex 子元素收缩尺寸

    • SUM_A = 100px * 0.2 + 150px * 0.3 = 65px
    • 元素 one 的实际收缩尺寸 = 20px * 100px * 0.2 / 65px= 6.15px,即最终宽度 = 100px - 6.15px = 93.85px
    • 元素 two 的实际收缩尺寸 = 20px * 150px * 0.3 / 65px= 13.85px,即最终宽度 = 150px - 13.85px = 136.15px

4.4 Demo3: box-sizing =border-box

留意:元素 one, twobox-sizing= border-box

function demo6() {
    return (
        <>
            <div className="flex">
                <div className="item" style={{flexBasis: 100, flexGrow: 1, marginRight: 10}}>One</div>
                <div className="item" style={{flexBasis: 150, flexGrow: 2, flexShrink: 2}}>Two</div>
            </div>
            <style jsx>{`
                .flex {
                    display: flex;
                    width: 200px;
                    outline: 1px dashed red;
                }
                .item {
                    padding: 10px;
                    border: 10px solid #666666;
                    box-sizing: border-box;
                }
            `}</style>
        </>
    )
}

解析:

  1. 计算剩余自由空间

    • flex 容器主轴尺寸 = 200px
    • 元素 one 的希望尺寸 = 100px(flex-basis) + 10px(margin-right) = 110px
    • 元素 two 的希望尺寸 = 150px(flex-basis) = 150px
    • 剩余自由空间 = 200px - 110px - 150px = -60px,即存在负剩余空间。
    • 被分摊的负剩余空间 = -60px * Min(1, 1 + 2) = -60px
  2. 计算各个 flex 子元素收缩尺寸

    • SUM_A = 60px * 1 + 110px * 2 = 280px
      注意:此时不是直接用 flex-basis 去乘 flex-shrink。本质上是使用 flex 子元素的 content 矩形宽度值去乘 flex-shrink。
    • 元素 one 的实际收缩尺寸 = 60px * 60px * 1 / 280px = 12.86px,即最终宽度 = 60px - 12.86px = 47.14px(不包含 padding,border)
    • 元素 two 的实际增长尺寸 = 60px * 110px * 2 / 280px = 47.14px,即最终宽度 = 110px - 47.14px = 62.86px(不包含 padding,border)

4.5 Demo5 跟 min-width 冲突

留意该栗子中:

  1. 元素 one 的min-width=60px
function demo7() {
    return (
        <>
            <div className="flex">
                <div className="item" style={{flexBasis: 100, flexShrink: 2, marginRight: 10, minWidth: 60}}>One</div>
                <div className="item" style={{flexBasis: 150, flexShrink: 2}}>Two</div>
                <div className="item" style={{flexBasis: 100, flexShrink: 1}}>Three</div>
            </div>
            <style jsx>{`
                .flex {
                    display: flex;
                    width: 300px;
                    outline: 1px dashed red;
                }
                .item {
                    padding: 10px;
                    border: 10px solid #666666;
                }
            `}</style>
        </>
    )
}

解析:

  1. 计算剩余自由空间

    • flex 容器主轴尺寸 = 300px
    • 元素 one 的希望尺寸 = 100px(flex-basis) + 20px(padding-left/right) + 20px(border-left/right) + 10px(margin-right) = 150px
    • 元素 two 的希望尺寸 = 150px(flex-basis) + 20px(padding-left/right) + 20px(border-left/right)= 190px
    • 元素 three 的希望尺寸 = 100px(flex-basis) + 20px(padding-left/right) + 20px(border-left/right) = 140px
    • 剩余自由空间 = 300px - 150px - 190px - 140px = -180px,即存在负剩余空间。
    • 被分摊的负剩余空间 = -180px * Min(1, 1 + 2 + 2) = -180px
  2. 计算各个 flex 子元素收缩尺寸

    • SUM_A = 100px * 2 + 150px * 2 + 100px * 1 = 400px
    • 元素 one 的实际收缩尺寸 = 180px(负剩余空间的绝对值) 100px 2 / 700px = 51.43px,
      这样元素 one 的尺寸最 100px – 51.43px = 48.57px,小于其min-width=60px, 即最终宽度为 60px。即分配给元素 one 的税负需要由其他兄弟分摊了。
  3. 元素 two 和元素 three 重新分配剩下是自由剩余空间,即回到步骤 1 重新计算。

    • flex 容器主轴尺寸 = 300px – 元素 one 占领的尺寸(60px + 20px + 20px + 10px)= 190px
    • 剩余空间 = 190px - 190px - 140px = -140px,即元素 two,three 要总缩减 140px。
    • SUM_A = 150px * 2 + 100px * 1 = 400px
    • 元素 two 的收缩尺寸 = 140px * 150 * 2 / 400px = 105px,即最终宽度 = 150px – 105px = 45px
    • 元素 three 的收缩尺寸 = 140px * 100 / 400px = 35px,即最终宽度 = 100px – 35px = 65px

4.6 小结

  1. 缩减的规则稍稍复杂些,这背后是有原因的,主要防止宽度小的元素缩减太快导致为负宽度。
  2. flex 子元素发生弹性伸缩只是 content 矩形,其 margin,border, padding 不会发生弹性伸缩的,所以他们也不参与弹性伸缩的计算公式内(如弹性收缩的公式)
  3. 当遇到 min- 属性冲突时,即元素不能再收缩时,由后面的 flex 子元素重新分摊剩余空间。
    除了 min- 属性指定最小尺寸时,每个元素都存在最小尺寸的。

参考

  1. css-tricks: A Complete Guide to Flexbox
  2. 规范
  3. Understanding flexbox

正文完
 0