1. 程式人生 > >CSS 子元素依次收縮的實現

CSS 子元素依次收縮的實現

當子元素的寬度(或高度)超過父元素時,如果父元素設定了 display: flex,則子元素將按比例縮小自己的寬度(或高度),但現在我們希望子元素按一定的優先順序縮小。即:當寬度不足時,優先縮小某一個子元素,當達到該元素的最小寬度(min-width)時,再開始縮小另外的元素。下面是一個示例圖:

可以看到,當寬度不足時,上例中優先縮小了最右的元素,當最右元素達到最小寬度 100px 時開始縮小左邊的元素,依此類推。

本文我們將分析 flexbox 中的 flex-shrink 屬性,來實現上述效果。本文假設你熟悉 flexbox 的基本用法。

#基本設定

話不多說,先把主要的框架搭上,參見下面的

jsfiddle

在上面的例子中,只設置了父元素為 display: flex 及第一個元素為 flex: none ,即不參與 flexbox 的伸縮的計算,只佔與它本身寬度相同的寬度。

如果在輸入框中輸入,可以看到:當寬度不足時,三個子元素一起收縮,出現 ... 字樣。這是因為,flex 元素的子元素的預設 flex 設定為 flex: 0 1 auto。即收縮的比值為 1

題外話,設定 flex 屬性相當於設定 3 個值,即 flex: flex-grow, flex-shrink, flex-basis。所以官方提倡直接設定 flex 屬性而不是分別設定三個。

#內建的支援?

我們通過查詢文件發現,flex-shrink

的作用是指定縮小的係數,即如果一個元素 A 的 flex-shrink: 2 而元素 B 為 flex-shrink: 1,當需要縮小 30px 才能不超出父元素寬度時,A 與 B 按 2:1 收縮,即 A 元素縮小 20px 而 B 元素縮小 10px。

這樣看來,flex-shrink 並不適合我們上面所說的任務。因為只要設定了 flex-shrink,那麼元素就一定會縮小,也就達不到前文需要的效果了。

也許你從標題裡也猜到了,是的,flexbox 並沒有提供任何機制,來設定伸縮的優先順序。所以我們只能進一步思考,如何才能做到?

#合適的比例?

於是我們的第一反應就是:如果把其中一個元素的 flex-shrink

設定得很大,而另一個很小不就可以了?嗯,有道理!於是我們先忽略第二個子元素,將它設定為 flex:none,第三個元素設定為 flex: 0 1,第四個元素 flex: 0 1000,即它的縮小的係數是第三個元素的 1000 倍!

輸上幾個字元,居然就成功了!

然後如果仔細觀察輸入的情況,會發現輸入到一定長度時,雖然最後一個元素還沒有達到 min-width,第三個元素也發生了收縮:

難道是縮小系數還不夠大?調成 10000 試試。還真的就成功了!但為什麼 1000/1 不夠大,而 10000/1 就能正常工作;需要多大的比例還能實現一個元素優先收縮的效果?

#flex-shrink 如何計算?

顯然我們之前據說的計算方法是錯誤的,那 flex-shrink 真正的作用方式如何呢?

首先,縮放的係數是由 flex-shrink 的值乘於 flex-basis 的值共同決定的,而這麼做的原因即使指定了相同的 flex-shrink,較大的元素(較大的 basis)也會顯得收縮了更多,也符合人的直觀印象。

得到這個比例之後,再除於所有子元素的係數之和作為最終的縮小的比例,用這個比例乘於總共需要縮小的寬度,就是該元素需要縮小的寬度了。還是看看例子吧:

.flex-container{ width: 600px; }.flex-item-1{ flex-basis: 100px; flex-shrink: 1; }.flex-item-2{ flex-basis: 400px; flex-shrink: 1; }.flex-item-3{ flex-basis: 400px; flex-shrink: 1; }Total basis: 900pxSpace remaining: -300pxItem 1 shrink factor: (1×100) / (100px + 400px + 400px) = .111 × -300px = -33.333pxItem 2 shrink factor: (1×400) / (100px + 400px + 400px) = .444 × -300px = -133.333pxItem 3 shrink factor: (1×400) / (100px + 400px + 400px) = .444 × -300px = -133.333px

#寬度可以是小數?

我們知道 CSS 的最小單位是畫素,即 px,那計算出來的小數有何作用?經過一番搜尋,又學習到了新的知識: getBoundingClientRect(),它可以返回一個 DOM 元素的位置和大小資訊,最為重要的是,它返回資訊的型別是 浮點型!這意味著瀏覽器是需要儲存小數級別的資訊的!

我們打印出了一些資訊:

下面是在我本機上得到的輸出結果,下面是在第三個元素縮小之前:

下面是第三個元素縮小之後:

上面這兩張圖只能對比第三個元素的資訊,這裡直接說下,在第四個元素收縮之前,它的大小是 191px。在第三個元素收縮時(即變成 ...)時:

總寬度: 600px (去掉父元素的 border 2px)總縮小寬度:600-60-176-186-191 = -13px第3個元素的 shrink factor: (1×186)    / (186px + 1000×191px)                           = 0.00094674680015273484 × -13px = -0.01230770840198555292px第4個元素的 shrink factor: (1000×191) / (186px + 1000×191px)                           = 0.99905325319984726515 * -13px = -12.98769229159801444695px

因此,計算後第 3 個元素的寬度為 186 - 0.01230770840198555292 = 185.98769229159801444708 並不等於圖片中的結果 185.98333740234375。恭喜,發現了一個很深的坑。

問題其實在於,瀏覽器會保留多少精度。這是一個沒有標準定義的內容,叫作 “subpixel rendering”。一個簡單的例子就是指定 3 個 width: 33.33333% 的 div 時,由於精度問題,瀏覽器可能並不會佔滿 100%。

那精度到底是多少呢?這個精度叫作 LayoutUnit,Chrome 是 1/64px,而 Firefox 是 1/60px

我們這裡取整:

185.98769229159801444708 * 60=> 11159.2615374958808668248011159 / 60=> 185.98333333333333333333

當然,由於 1/60 的精度是無限的,還是會有精度丟失,這裡看到 185.98 < 186 因此導致元素 3 發生了 overflow。

最後,如果你願意計算,這個 flex-shrink 的大小是跟各個元素的寬度相關的,在這個特定的例子裡,假設元素 3 寬度為 a,元素 4 寬度為 b,元素 4 的 min-widthc,則要使元素 3 保持正常,則要滿足 a/(a + bx) * (b-c) < 1/60,即使 a 元素縮小的量小於一個 LayoutUnit,即 x > (60*a*(b-c) -a)/b,算得約 5316。

當然,由於我們輸入的字元並不是 1px 的,所以可能相差幾倍也不太重要。

#小結

  1. 想讓子元素按優先順序收縮,可以通過設定大倍率的 flex-shrink 完成。
  2. flex-shrink 的演算法與 flex-grow 不同,需要先與 flex-basis 相乘得到 shrink factor。
  3. 瀏覽器的 pixel 最小單位稱為 LayoutUnit,Chrome 為 1/64px,Firefox 為 1/60px

#擴充套件閱讀