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

背景

flex佈局更有效的實現對齊,空間分配。最近又學習下flex子元素的尺寸計算規則,主要是flex-grow, flex-shrink的計算規則的學習。css

1、基本概念

image

1.1 主軸(Main axis)

定義了flex元素佈局起始點和方向,flex子元素在主軸上依次放置。
主軸有4個方向,經過flex-direction指定:git

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

1.2 主軸的尺寸(Main axis size)

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

1.3 交叉軸(Cross axis)

交叉軸就是跟主軸錘子的方向,主要用於flex元素的對齊。ide

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元素和空白空間)在交叉軸方向對齊。

Tipflex

  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元素。ui

  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
    指定順序

2、計算自由空間和flex-basis

flex子元素在主軸上的比例依賴這三個CSS屬性:flexbox

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

其中:
flex-basis + flex-grow組合控制着伸
flex-basis + flex-shrink組合控制着縮
因此要肯定flex子元素在主軸上的比例,首先要肯定使用哪一種組合。spa

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組合。

3、深刻了解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>
        </>
    )
}

clipboard.png

解析:

  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>
        </>
    )
}

clipboard.png
解析:

  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>
        </>
    )
}

clipboard.png
解析:

  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子元素從新分配。

4、深刻了解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>
        </>
    )
}

clipboard.png

解析(過長跟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>
        </>
    )
}

clipboard.png
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>
        </>
    )
}

clipboard.png

解析:

  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
相關文章
相關標籤/搜索