在當下,可用于 Web 布局的 CSS 特性有很多,而且這個集合越來越強大。自從 Flexbox 的兼容性越來越完善,它替代了浮動布局,成為主流的布局技術。只不過,近幾年來,CSS Grid 快速得到主流瀏覽器的支持,在圈中不乏有了新的聲音,CSS Grid 布局將替代 Flexbox 布局,而且對于 CSS Flexbox 和 CSS Grid 哪個更好的爭執也越來越多。
這節課我們就一起來聊聊這個話題,在構建 Web 布局時,什么時候使用 Flexbox 布局,什么時候又應該使用 Grid 布局。我希望通過學習這節課,你在構建 Web 布局時,能更好地做出選擇。
Grid 布局會替代 Flexbox 布局嗎?
CSS Grid 和 CSS Flexbox 都是當前 Web 布局的兩大主流工具。可在萬千世界中,任何事情都具有雙面性, CSS Flexbox 能讓前端開發人員更好地構建 Web 布局或 Web 組件,但也讓前端開發人員不時地誤解和誤用它。許多 Web 布局問題可能用 CSS Grid 更好解決,卻被 CSS Flexbox 所取代。
當然,我們也不能絕對地說,使用 Grid 布局就比 Flexbox 布局更好。
正如我們將要看到的某些布局,顯然知道是用 Flexbox 或 Grid 構建會更適合,但可以想象其他布局,用一個(只用 Flexbox 或 Grid)或兩個(同時用 Flexbox 和 Grid)構建,所以你可能有一個組件,其中外層是用 Grid 構建的,里面的東西是用 Flexbox 構建的,反之亦然!它們沒有對與錯,只要有效即可。
除此之外,CSS Grid 和 CSS Flexbox 兩者之間還有很多相互重疊的特性:
所以說,如果你是一名 Web 開發者,不應該考慮的是 CSS Grid 會替代 CSS Flexbox ,也不應該考慮 CSS Grid 和 CSS Flexbox 兩者誰更好。而是要考慮,什么時候使用 CSS Grid,什么時候使用 CSS Flexbox。要掌握這一點,那我們就需要對 CSS Grid 和 CSS Flexbox 有較深的了解,了解它們之間異同,只有這樣,你才能做出正確的、適合的選擇!
CSS Grid vs. CSS Flexbox
CSS Grid 和 CSS Flexbox 的差異,我們主要可以從三個方面來對比:
- CSS Grid 是二維,CSS Flexbox 是一維;
- CSS Grid 布局優先(外在),CSS Flexbox 內容優先(內在);
- CSS Grid 用于頁面布局(宏觀布局),CSS Flexbox 用于組件布局(微觀布局)。
二維 vs. 一維
CSS Grid 和 CSS Flexbox 最核心的區別就是維度方面:二維 vs. 一維 。
CSS Grid 是二維的。 即, Grid 是為二維布局而設計的,你可以同時沿著內聯軸和塊軸排列元素。
而 CSS Flexbox 是一維布局, 這意味著可以將元素按行或列排列,但不能同時按行或列排列:
通常情況之下,如果一個布局是二維的(同時需要在行和列排列元素),則使用 CSS Grid 來布局;如果一個布局是一維的(只在行或列排列元素),則使用 CSS Flexbox 來布局。
當然,Grid 也可以用于一維布局,所以大家在使用 Grid 來布局時不要陷入到這樣思維中,即 構建單維的布局而不能使用 Grid 布局 。比如下面這個示例,Flexbox 和 Grid 容器中都有多個元素:
<div class="flex"><div class="item">1</div><!-- 省略多個 --><div class="item">6</div>
</div><div class="grid"><div class="item">1</div><!-- 省略多個 --><div class="item">6</div>
</div>
在 Flex 容器 .flex
上設置了 flex-wrap
的值為 wrap
,并且 Flex 項目的基礎主尺寸 flex-basis
為 220px
,同時 Flex 項目能根據 Flex 容器剩余(或不足)空間進行擴展(或收縮):
.flex {display: flex;flex-wrap: wrap;
}.flex .item {flex: 1 1 220px;
}
Demo 地址: https://codepen.io/airen/full/KKeoJoY
你可以看到 Flex 項目會因為 Flex 容器空間不足自動換行。換行的 Flex 項目共享了同一行的中主軸(Main Axis)方向的可用空間,并且有可能會和上一行的 Flex 項目無法對齊。這是因為當你允許 Flex 項目換行時,每個新行都變成了一個新的 Flex 容器。空間分布只在同一行主軸方向進行。
如果你希望換行的元素能與前一行的元素對齊,這時就需要使用二維布局了,即使用 CSS 網格布局。我們使用 CSS 網格布局中的 RAM 布局技術,來實現網格項目因網格容器空間不足時自動斷行的效果,而且還能做到每一行元素都能對齊:
.grid {display: grid;grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
}
Demo 地址: https://codepen.io/airen/full/KKeoJoY
你也可能發現了,雖然 Flexbox 和 Grid 布局都能讓元素因容器空間不足自動換行,但兩者還是有差異的,Flexbox 布局總是可以讓 Flex 項目在同一行主軸方向均分容器空間,Grid 布局并不總是如此 。換句話說,當容器收縮時,網格項目保持完美對齊,而 Flex 項目則根據可用空間來伸縮它們的尺寸和對齊方式 :
Demo 地址: https://codepen.io/airen/full/KKeoJoY
既然 CSS 網格可以用于構建一維布局,那么 CSS Flexbox 也可以用來偽造一個看起來像網格的布局,只不過 Flexbox 算法卻不知道第二個維度。比如下面這個常見的頁面結構布局:
上圖所示是一個典型的二維布局,使用 CSS Grid 實現該布局效果有著天然的優勢:
<div class="grid"><header class="header">header</header><main class="main">main</main><div class="hero">hero</div><nav class="sidebar">sidebar</nav><div class="extra">extra</div><aside class="aside">aside</aside><div class="ads">ads</div><footer class="footer">footer</footer>
</div>
.grid {display: grid;grid-template-columns: 220px minmax(0, 1fr) 220px;grid-template-rows: repeat(5, minmax(10vh, auto));grid-template-areas: "header header header""hero main main""sidebar main main""sidebar extra extra""aside aside ads""footer footer footer";gap: 1rem;
}.header {grid-area: header;
}.hero {grid-area: hero;
}.main {grid-area: main;
}.sidebar {grid-area: sidebar;
}.extra {grid-area: extra;
}.aside {grid-area: aside;
}.ads {grid-area: ads;
}.footer {grid-area: footer;
}
如果使用 CSS Flexbox 布局來實現的話,你在 HTML 結構上就需要做相關的處理,添加額外的容器:
<div class="flex"><header class="header">header</header><div class="flex-container"><!-- 包裹另外兩個 flex container --><div class="flex-container"><!-- 用來包裹 main 和 extra --><main class="main">main</main><div class="extra">extra</div></div><div class="flex-container"><!-- 用來包裹 hero 和 sidebar --><div class="hero">hero</div><nav class="sidebar">sidebar</nav></div></div><div class="flex-container"><!-- 用來包裹 aside 和 ads --><aside class="aside">aside</aside><div class="ads">ads</div></div><footer class="footer">footer</footer>
</div>
使用 CSS Flexbox 布局,需要創建多個 Flex 容器,還需要使用 flex-direction: column
控制 Flex 項目的排列方向,并且通過 flex
相關屬性控制 Flex 項目的伸縮性等。因此,CSS 代碼就會冗余很多:
.flex {display: flex;flex-direction: column;gap: 1rem;
}.flex > .flex-container {display: flex;gap: 1rem;
}.flex-container > .flex-container {display: flex;gap: 1rem;flex-direction: column;
}.flex-container > .flex-container:nth-of-type(1) {flex: 1 1 0%;min-width: 0;order: 2;
}.flex-container > .flex-container:nth-of-type(2) {flex-basis: 220px;flex-shrink: 0;
}.flex .aside {flex: 1 1 0%;min-width: 0;
}.flex .ads {flex-basis: 220px;flex-shrink: 0;
}.flex .sidebar,
.flex .main {min-height: 30vh;
}
最終實現的布局效果是相似的:
Demo 地址: https://codepen.io/airen/full/dyKmLxG
從上面這兩個示例,我們可以得出一個簡單的結論。當你面對選擇 CSS Flexbox 還是 CSS Grid 來構建 Web 布局時,你可以嘗試著問自己:
- 只需要按行或列控制布局?那就選擇 CSS Flexbox ;
- 同時需要按行或列控制布局?那就選擇 CSS Grid 。
布局優先(外在)vs. 內容優先(內在)
CSS Grid 和 CSS Flexbox 除了一維和二維的區別之外,還有另一個區別:布局優先(外在)vs. 內容優先(內在) 。
從前面的課程中,我們可以得知,Flexbox 會監聽它的內容,所以它是內容優先(內在) 。 W3C 規范在介紹 Flexbox 時這樣描述過:
In return it gains simple and powerful tools for distributing space and aligning content in ways that web apps and complex web pages often need. —— W3C Flexbox 規范
大致意思是說,“作為回報,它獲得了簡單而強大的工具,以 Web 應用程序和復雜的 Web 頁面經常需要的方式分配空間和對齊內容”。換句話說,Flexbox 會對瀏覽器說:
嘿,這是我的內容,只要找出在給定空間的基礎上,以最好的方式水平(或垂直)分布它的最佳方式。
使用 CSS Flexbox 布局的理想情形是你有一組元素,希望它們:
- 能平均地分布在 Flex 容器中;
- 內容的大小決定每個元素占據多少空間;
- 如果元素換到了新的一行,它們會根據新行的可用空間決定它們自己的大小。
CSS Grid 則是從布局入手。它提供了一種機制,你可以根據你預想的大小和結構來創建一個網格,然后再把元素放入網格中(放到一個網格單元格,或一個網格區域中),或者元素自動放置到網格中(根據網格項目自動放置算法放置)。
不過,CSS Grid 無論如何,都將堅持它的行和列,所以它是布局優先(一種外在的行為)。當然,如果你愿意,你也可以讓布局保持完全相同,盡管你可能不希望這樣。
也就是說,當你在抉擇使用 CSS Flexbox 還是 CSS Grid 來布局時,你還可以問自己:
- 腦海中有布局結構了?那請選擇 CSS Grid;
- 不用擔心布局,只想讓所有東西都適合?那請選擇 CSS Flexbox。
頁面布局(宏觀布局) vs. 組件布局(微觀布局)
CSS Grid 和 CSS Flexbox 還有一個明顯的差異就是:CSS Grid 用于頁面布局,它是一種宏觀布局;CSS Flexbox 用于組件布局,它是一種微觀布局 。這可能是最直觀、最易于描述 CSS Grid 和 CSS Flexbox 差異的了。
事實上,在構建整個頁面布局時,我不會對使用哪種技術有任何疑問,也不會過于強調哪種技術最好,我更強調的應該根據場景和需求選擇最適合的技術。比如:
就上圖展示的 Web 頁面布局效果而言,在 CSS Grid 出現之前,我們大多數人都是使用 CSS Flexbox 來完成的:
<body><header class="header">header</header><section class="hero">hero</section><main class="main">content</main><div class="form">sign up</div><article class="feature feature--books">feature books</article><article class="feature feature--users">feature users</article><article class="feature feature--story">feature story</article><footer class="footer">footer</footer>
</body>
body {display: flex;flex-direction: column;flex-wrap: wrap;
}.main {flex: 1 1 50vh;
}/* Tablet */
@media only screen and (min-width: 401px) and (max-width: 960px) {body {flex-direction: row;}body > *:not(.form, .feature) {width: 100%;}.form,.feature {width: 50%;}.main {flex: none;}
}/* Desktop */
@media only screen and (min-width: 961px) {body {flex-direction: row;}body > *:not(.form, .feature) {width: 100%;}.feature {width: calc(100% / 3);}.form {width: 100%;order: 1;}.main {flex: none;order: 2;}.footer {order: 3;}
}
Demo 地址:https://codepen.io/airen/full/vYrjJEw
正如示例代碼所示,使用 CSS Flexbox 布局技術雖然能完成所需要 Web 頁面布局效果,但是使用 CSS Flexbox 創建布局還是需要做一些計算,尤其是構建類似網格類的布局,可以說是從來就沒有真正“彈性”的感覺,而且總是會遇到一些令人頭疼的問題。
另外,即使是使用 CSS Flexbox 可以實現所需要的頁面布局,也往往需要需要犧牲 HTML 結構的簡潔性,添加額外的容器,使用多個 Flexbox。
另一方面, CSS Grid 就是為此而生的,比如上面的頁面布局,使用 CSS Grid 要更容易地多:
body {display: grid;grid-template-rows: repeat(2, auto) minmax(0, 1fr) repeat(5, auto);grid-template-areas:"header""hero""content""signup""books""users""story""footer";
}.header {grid-area: header;
}.hero {grid-area: hero;
}.main {grid-area: content;
}.form {grid-area: signup;
}.feature--books {grid-area: books;
}.feature--users {grid-area: users;
}.feature--story {grid-area: story;
}.footer {grid-area: footer;
}/* Tablet */
@media only screen and (min-width: 401px) and (max-width: 960px) {body {grid-template-columns: repeat(2, minmax(0, 1fr));grid-template-rows: repeat(2, auto) minmax(0, 1fr) repeat(3, auto);grid-template-areas:"header header""hero hero""content content""signup books""users story""footer footer";}
}/* Desktop */
@media only screen and (min-width: 961px) {body {grid-template-columns: repeat(3, minmax(0, 1fr));grid-template-rows: repeat(4, auto) minmax(0, 1fr) auto;grid-template-areas:"header header header""hero hero hero""books users story""signup signup signup""content content content""footer footer footer";}
}
Demo 地址: https://codepen.io/airen/full/WNyyXQL
對于大多數 Web 開發者,一說到 Web 布局就通常會想到是頁面級的設計。但是頁面中的小組件可以有自己獨立的布局。
理想情況下,這些小組件無論在頁面上的位置如何,小組件布局都將自動調整。在某些情況下,你可能不知道組件將被放置在主內容列或側邊欄中,或兩者都放置:
在不確定一個組件會在哪里放置的情況下,你需要確保組件可以隨著環境調整布局。依舊拿卡片組件為例,你可能需要為同一個卡片小組件提供不同的布局:
簡單地說,在不確定一個組件會在哪里結束的情況下,你需要確保組件可以根據其容器進行自我調整。在 CSS 的眾多布局技術中,CSS Flexbox 很適用于這些小組件的布局。
<div class="card"> <img src="https://picsum.photos/2568/600?random=1" width="2568" height="600" alt="" class="card__thumbnail" /> <div class="card__badge">Must Try</div> <div class="card__content"><h3 class="card__title">Best Brownies in Town</h3> <p class="card__describe">High quality ingredients and best in-class chef. Light, tender, and easy to make~</p> <button class="card__button">Order now</button> </div>
</div>
.card {display: flex;flex-direction: column;position: relative;gap: 1rem;
}.card__badge {position: absolute;top: 0;left: 0;
}.card__button {margin-left: auto;
}.card__content {padding: 0 20px 20px;display: flex;flex-direction: column;gap: 1rem;
}@media only screen and (min-width: 768px) {.card {flex-direction: row;}.card__thumbnail {max-width: 300px;border-radius: 24px 0 0 24px;}.card__content {flex: 1;padding: 20px 20px 20px 0;}.card__button {margin-top: auto;}
}
Demo 地址: https://codepen.io/airen/full/JjZZOBy
正如你所看到的,使用 CSS Flexbox 和 CSS 媒體查詢,可以讓卡片在瀏覽器不同的視窗寬度下有不同的布局。也不難發現,使用 CSS Flexbox 布局,不得不犧牲 HTML 的結構,使用 div.card__content
來包裹 .card__title
、.card__describe
和 .card__button
。
事實證明,在創建小組件時,CSS Grid 也很棒,而且還可以避免 HTML 結構的冗余。比如上面示例的卡片組件,如果使用 CSS Grid 布局,就可以將 .card__content
這個容器刪除,讓結構變得更扁平:
<div class="card"> <img src="https://picsum.photos/2568/600?random=1" width="2568" height="600" alt="" class="card__thumbnail" /> <div class="card__badge">Must Try</div> <h3 class="card__title">Best Brownies in Town</h3> <p class="card__describe">High quality ingredients and best in-class chef. Light, tender, and easy to make~</p> <button class="card__button">Order now</button>
</div>
.card {display: grid;grid-template-areas:"thumbnail""title""describe""button";gap: 1rem;
}.card__title,
.card__describe {padding: 0 20px;
}.card__button {margin: 0 20px 20px auto;grid-area: button;
}.card__thumbnail {grid-area: thumbnail;
}.card__title {grid-area: title;
}.card__badge {grid-area: thumbnail;z-index: 2;
}.card__describe {grid-area: describe;
}@media only screen and (min-width: 768px) {.card {grid-template-columns: fit-content(300px) minmax(0, 1fr);grid-template-rows: auto minmax(0, 1fr) auto;grid-template-areas:"thumbnail title""thumbnail describe""thumbnail button";}.card__thumbnail {border-radius: 24px 0 0 24px;}.card__title {padding: 20px 20px 0 0;}.card__describe {padding: 0 20px 0 0;}
}
Demo 地址:https://codepen.io/airen/full/bGKKaGG
這兩個示例還是基于媒體查詢來調整小組件布局。雖然看上去沒有問題,但這小組件同時在頁面中的不同位置時,媒體查詢還是無法達到預期效果。不過,隨著 CSS 容器查詢的實現,可以讓小組件布局適應其容器變得更容易。
.card__container {container-type: inline-size;
}.card {display: grid;grid-template-areas:"thumbnail""title""describe""button";gap: 1rem;
}.card__title,
.card__describe {padding: 0 20px;
}.card__button {margin: 0 20px 20px auto;grid-area: button;
}.card__thumbnail {grid-area: thumbnail;
}.card__title {grid-area: title;
}.card__badge {grid-area: thumbnail;z-index: 2;
}.card__describe {grid-area: describe;
}@container (width > 400px) {.card {grid-template-columns: fit-content(300px) minmax(0, 1fr);grid-template-rows: auto minmax(0, 1fr) auto;grid-template-areas:"thumbnail title""thumbnail describe""thumbnail button";}.card__thumbnail {border-radius: 24px 0 0 24px;}.card__title {padding: 20px 20px 0 0;}.card__describe {padding: 0 20px 0 0;}
}
Demo 地址:https://codepen.io/airen/full/oNyypLP
對齊方式
對于很多人來說,CSS Flexbox 除了可以讓 Flex 項目能根據容器主尺寸進行伸縮擴展之外,還有一個,就是強大的對齊能力。這讓 Web 開發者構建水平垂直居中 、等高等布局變得很容易。當然,CSS Grid 也擁有強大的對齊能力,只不過,CSS Grid 和 CSS Flexbox 的對齊方式略有差異:
在 CSS Flexbox 中,其對齊方式主要分為:
justify-content
可以控制所有 Flex 項目在 Flex 容器主軸方向的對齊;align-items
可以控制所有 Flex 項目在 Flex 容器側軸方向的對齊;align-content
可以控制 Flex 行在 Flex 容器側軸方向的對齊,前提是flex-wrap
屬性的值是wrap
或wrap-reverse
;algin-self
可以控制單個 Flex 項目在 Flex 容器側軸方向的對齊;- 由于 Flexbox 是一維布局,它不支持
justify-items
和justify-self
兩個屬性。
CSS Grid 中,對齊方式有些與 Flexbox 對齊方式是相似的,但其要分為三種使用情景:
place-content
(它的子屬性align-content
和justify-content
)控制網格軌道在網格容器的塊軸和內聯軸方向的對齊;place-items
(它的子屬性align-items
和justify-items
)控制所有網格項目在網格區域的塊軸和內聯軸方向的對齊;place-self
(它的子屬性align-self
和justify-self
)控制單個網格項目在網格區域的塊軸和內聯軸方向的對齊。
不管是 Flexbox 布局中的對齊還是網格布局中的對齊,它們都受 CSS 的書寫模式或閱讀模式的影響!
另外,CSS Grid 和 CSS Flexbox 中的項目(網格項目和 Flex 項目)都可以顯式設置 margin
的值來達到單個網格項目對齊。你可以在這兩種布局技術中,設置網格項目或 Flex 項目的 margin-inline
或 margin-block
值為 auto
,實現水平居中或垂直居中:
magin-inline: auto
可以實現水平居中;margin-block: auto
可以實現垂直居中。
z
軸上的層疊
了解 CSS 的 Web 開發者,都知道我們可以在 position
屬性值為非 static
的元素上設置 z-index
屬性值來控制元素在 z
軸上的層級。那么,CSS Flexbox 和 CSS Grid 布局時,Flex 項目和網格項目都可以通過 z-index
的值來控制它們在 z
軸上的層級。
不同的是,CSS Grid 布局實現層疊布局要比 CSS Flexbox 更適合。因為:
- 在 CSS Flexbox 布局中,要實現多個 Flex 項目相互交叉且在
z
軸上層疊的布局,依舊要通過 CSS 的定位(position
)布局來實現; - 在 CSS Grid 布局中,除了可以使用定位(
position
)布局實現網格項目相互交叉且在z
軸上層疊布局之外,網格項目還可以使用grid-column
、grid-row
或grid-area
來實現,并且要比定位布局更靈活。
就拿下面這個示例來說:
Demo 地址: https://codepen.io/airen/full/zYaaQLV
.grid { grid-template-columns: 1fr repeat(10, minmax(3rem, 1fr)) 1fr; grid-template-rows: minmax(3rem, auto) 3rem auto 6rem auto;
} .grid::after { grid-column: 1 / -3; grid-row: 3;
} h2 { grid-column: 1 / span 8; grid-row: 1 / span 2;
} .grid__img { grid-column: 6 / -1; grid-row: 2 / span 3; } blockquote { grid-column: 3 / span 4; grid-row: 4 / span 2;
} p { grid-column: 7 / span 5; grid-row: 5 / span 1; }
你可能已經發現了,雖然有多個網格項目重疊,但這里并沒有定位(position
)相關屬性的身影。在 Grid 布局,我們可以直接使用 grid-column
和 grid-row
屬性放置網格項目(指定網格線名稱,將網格項目放在指定的位置)上。
這種布局看上去似乎很復雜,靈活性不夠。其實不然,只要我們使用靈活的網格軌道(grid-template-columns
和 grid-template-rows
定義網格軌道),那么我們的布局仍然會適應內容(比如有更長的標題、段落等)。
除了能靈活適配更多的內容之外,還可以使用 grid-column
和 grid-row
移動網格項目,使其在不同的位置呈現。簡單地說,可以在該組件的基礎上,得到更多的組件變體。
Demo 地址: https://codepen.io/airen/full/vYrrwvg
.grid {display: grid;gap: var(--pad);
}.grid__img {aspect-ratio: 16 / 9;align-self: center;
}@media (min-width: 45em) {.grid {grid-template-columns: 1fr repeat(10, minmax(0, 6rem)) 1fr;grid-template-rows: 1fr minmax(3rem, auto) 1fr;}h2 {grid-column: 2 / span 6;grid-row: 2;}.grid__img {grid-column: 7 / -1;grid-row: 1 / span 3;}p {grid-column: 2 / span 4;grid-row: 3;}.grid:nth-child(even) h2 {grid-column: span 6 / -2;}.grid:nth-child(even) p {grid-column: span 4 / -2;}.grid:nth-child(2n) .grid__img {grid-column: 1 / span 6;}.grid:nth-child(3n) .grid__img {grid-column: span 6 / -2;}.grid:nth-child(4n) .grid__img {grid-column: 2 / span 6;}
}
當然,這里并沒有說 CSS Flexbox 不能實現類似上面這種層疊布局,只是說 CSS Grid 更適合。在不使用 CSS Grid 布局技術,要實現該示例的效果,你將付出的代價會大得多,即使實現了,也很難達到 CSS Grid 實現的效果:
項目的伸縮擴展
CSS Flexbox 和 CSS Grid 布局中,Flex 容器和網格容器的直接子元素都被稱為項目 (Flex 項目和網格項目),它們都具備伸縮擴展的特性。
- CSS Flexbox 布局中,在 Flex 項目上設置
flex
屬性值為1 1 0%
(通常大部分開發者直接設置為1
),Flex 項目將根據 Flex 容器的剩余空間或不足空間進行收縮擴展; - CSS Grid 布局中,是在網格容器的
grid-template-rows
或grid-template-columns
屬性上設置fr
單位的值(設置網格軌道尺寸),網格項目將根據網格容器的可用空間來進行收縮擴展。
如果你要實現一個均分列(等寬)布局。要是使用 CSS Flexbox 布局,可以在 Flex 項目上設置 flex
和 min-width
:
.flex-container {display: flex;
}.flex-item {flex: 1 1 0%;min-width: 0;}
要是使用 CSS Grid 布局,需要將網格容器的 grid-template-columns
設置為:
.grid-container {display: grid;grid-template-columns: repeat(4, minmax(0, 1fr));
}
注意,示例代碼中
repeat()
函數中的4
是指你想要均分的列數。這里是將網格容器平均分為四列!
如果你不將 fr
與 minmax()
結合使用,則需要網格項目上顯式設置 min-width
的值為 0
:
.grid-container {display: grid;grid-template-columns: repeat(4, 1fr);
}.grid-item {min-width: 0;
}
也就是說,CSS 網格布局中,fr
與 minmax()
函數結合(一般是 minmax(0, 1fr)
)使用時,fr
單位值可以讓網格項目與 CSS Flexbox 中的 Flex 項目上設置 flex
屬性達到非常相似的結果。只不過,CSS 網格仍然創建的是一個二維布局。
Demo 地址: https://codepen.io/airen/full/bGKKPrO
當然,就等寬布局來說,上面這兩種方案都是一樣的。但要是在 Flex 容器中顯示式設置 flex-wrap
屬性的值為 wrap
時,它們之間還是有差異的。
注意,在使用 CSS Flexbox 布局時,我個人建議在 Flex 容器上顯式設置
flex-wrap
的值為wrap
,除非你明確地知道不需要斷行。這樣做的好處是,你編寫的 CSS 更健壯,構建的 Web 布局不易于被打破!
當你拖拽改變瀏覽器視窗大小時,你會發現:
- CSS Flexbox 布局可以根據可用空間適當地調整行中元素(Flex 項目)個數,當有足夠的空間時,全部的六個 Flex 項目在同一行中展示,當容器變的過窄時,每行則可能只展示一個 Flex 項目;
- CSS Grid 則不同,始終保持五列網格列軌道。雖然網格列軌道會自動拉伸,但始終會保持我們定義網格時的列軌道數量,在這個示例是六列。
Demo 地址:https://codepen.io/airen/full/yLEEdxN
排列方向與換行
通過前面課程的學習,我們知道了,在任何一個元素上顯式設置 display
的值為 grid
(或 inline-grid
)、flex
(或 inline-flex
),就定義了一個網格容器和 Flex 容器,它們的直接子元素就都成了網格項目或 Flex 項目。但 CSS Grid 和 CSS Flexbox 在聲明網格格式化上下文和 Flexbox 格式化上下文時,其表現形式還是略有差異的:
- CSS Flexbox 布局中,不管是
flex
還是inline-flex
,默認情況下,都會讓所有 Flex 項目排在一行或一列 ; - CSS Grid 布局中,不管是
grid
還是inline-grid
,默認情況下,都不會改變 Grid 項目的排列方式,將按照 HTML 結構中的源順序排列,除非你在聲明網格容器的時候,顯式使用grid-template-*
(比如,grid-template-columns
、grid-template-rows
)改變其排列方式;
不過,不管是 CSS Grid 布局還是 CSS Flexbox 布局,如果你需要,可以顯式改變排列方向。
- 在 CSS Flexbox 中,可以通過
flex-direction
屬性來改變 Flex 項目在 Flex 容器中排列方式; - 在 CSS Grid 中,可以通過
grid-auto-flow
屬性來改變網格項目在網格容器中默認的排列方式。
不同的是,CSS Grid 布局還可以使用 grid-template-rows
或 grid-template-columns
來改變網格排列。比如:
.grid-container {display: grid;grid-template-columns: 220px minmax(0, 1fr) 220px;grid-template-rows: repeat(3, 100px);
}
除此之外,還可以在網格項目上使用 grid-row
、grid-column
或 grid-area
來改變網格項目的默認排列位置。這是在 CSS Flexbox 布局中做不到的。
剛才有提到過,使用 CSS Flexbox 構建布局時,建議在 Flex 容器上顯式設置 flex-wrap
的值為 wrap
。這樣做的好處是,當 Flex 容器沒有足夠多的空間時,Flex 項目會自動換行(或列),不至于讓 Flex 項目溢出 Flex 容器,打破 Web 布局:
而在 CSS Grid 布局中要實現自動換行(列),就需要采用網格布局中的 RAM 布局技術,即 repeat()
、minmax()
函數結合起來,并且指定列(行)網格軌道數量時,不能使用具體的數值,要使用 auto-fit
或 auto-fill
關鍵詞:
雖然都能實現自動換行(列),但它們還是有差異的。
- 在 CSS Flexbox 布局中,如果 Flex 項目具有伸縮擴展性(即
flex: 1
),最后一行 Flex 項目有可能會填充整個 Flex 容器,比如最后一行只有一個 Flex 項目; - 在 CSS Grid 布局中,所有網格項目都會的大小都會由
minmax(MIN,MAX)
函數來決定,最小值是MIN
,最大值是MAX
(一般是1fr
)。
/* CSS Flexobox */
.flex {--columns: 3;--gap: 1rem;display: flex;flex-wrap: wrap;gap: var(--gap);
}.flex .item {flex: 1 1 calc((100% - (var(--columns) - 1) * var(--gap)) / var(--columns));
}/* CSS Grid */
.grid {display: grid;gap: var(--gap);grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
}
CSS Flexbox 效果 :
CSS Grid 效果 :
Demo 地址:https://codepen.io/airen/full/yLEqBzq
改變順序和間距
前面所列的幾條是 CSS Flexbox 和 CSS Grid 有相似與差異之處,但它們有幾個特性是完全一樣的。比如改變項目的順序。
你可以在項目(網格項目或 Flex 項目)上顯式設置 order
的值改變默認的排列位置:
.item {order: 1;
}
注意,order
的默認值是 0
,默認排列順序是以出現在 HTML 文檔的順序排序。
Demo 地址: https://codepen.io/airen/full/BaVPBXr
另一個相同的特性是 gap
,在 Flex 容器和網格容器上設置 gap
(或其子屬性 column-gap
和 row-gap
)屬性,視覺表現結果是一致的,不同的是:
- CSS Flexbox 是用來控制 Flex 項目之間的間距;
- CSS Grid 是用來控制網格軌道之間的間距。
.container {gap: 1rem;/* 等同于 */row-gap: 1rem;column-gap: 1rem;
}
Demo 地址:https://codepen.io/airen/full/dyKjyMv
有關于 CSS Flexbox 和 CSS Grid 主要的差異就介紹到這了,這里附上它們之間特性的對比圖:
猛擊圖片,查看大圖 !
什么時候使用 CSS Grid 和 CSS Flexbox?
我猜到這里,你對 CSS Grid 和 CSS Flexbox 兩者之間的異同有了更進一步的了解。但對于很多 Web 開發者,尤其是剛接觸 CSS 的開發者,在很多時候,面對選擇還是有一定的困惑。我覺得,在面對選擇的時候,你可以嘗試著按下面這樣來做選擇。
當出現以下情況時,你應該考慮選擇使用 CSS Flexbox:
- 當你需要實現一個小的布局時,可以考慮 Flexbox;
- 當你需要對齊元素時,Flexbox 非常適合;
- 當你需要一個內容優先的設計,尤其是你不知道你的內容看起來是什么樣子的,那么 Flexbox 是完美的選擇;
- 當你需要實現的布局就是一個一維布局,可以考慮 Flexbox。
當出現以下情況時,你應該考慮選擇使用 CSS Grid:
- 當你需要實現一個復雜的設計,可以考慮 Grid;
- 當你需要一個布局優先時,尤其是在你的腦海中已經有了布局設計結構,使用 CSS Grid 更容易構建;
- 當你的布局上元素相互交叉和層疊較多時,CSS Grid 更完美,更簡單;
- 當你需要實現的布局是一個二維布局,那最好是選擇 CSS Grid。
或者你也可以這樣相互比較之后再做抉擇:
- 如果布局比內容更重要,請選擇 CSS Grid;如果內容比布局更重要,請選擇 CSS Flexbox;
- 如果要創建一個嚴格對齊的,基于網格的布局,無論元素包含什么內容都保持不變;那么 CSS Grid 更適合;
- 如果你想創建一個更靈活的布局,尤其是在你不知道輸出的內容是什么,有多少種情況之下,那么 CSS Flexbox 更適合。
不管怎么選擇,在決定使用哪一個之前,請不要忘記:
CSS Grid 是二維布局,布局先行(外在);CSS Flexbox 是一維布局,內容先行(內在)
另外,不管怎么選擇,二者都不是相互替代的關系,它們是一種共存的關系。CSS Grid 可以完成 CSS Flexbox 可以做的大部分事情,而且多得多。所以我更傾向于:
除了那些使用 CSS Flexbox 更好處理的少數情況之外,大多都選擇 CSS Grid !
如果基于上面這些列表項,你還是無法做出選擇的話,那么我們一起來看幾個 Web 布局相關的示例,希望你能從這幾個示例中找到你想要的答案,在未來構建 Web 布局時,你能做出正確的選擇。
先來看一個 Web 中最常見的小組件——按鈕組件:
這是一個再普通不過的按鈕組件了。大家肯定會說,這有啥好思考的呢?不管是 Grid 還是 Flexbox ,這不都是分分鐘的事情嗎?如果僅考慮視覺上的展示,的確是如此。使用 Grid 和 Flexbox 都可以:
<button> Sign up <svg></svg> </button>
關鍵CSS代碼:
.button--flex { display: inline-flex; flex-wrap: wrap;
} .button--grid { display: inline-grid; grid-template-columns: 1fr auto;
}
Demo 地址:https://codepen.io/airen/full/GRGByzK
正如你所看到的,使用 Flexbox 和 Grid 實現的按鈕視覺效果都是一樣的。但是,該按鈕組件要是放置在某個容器中,且這個容器空間較小時,希望按鈕能自動換行,而不是溢出容器:
上圖左側是我們期望的效果,右側是我們不想要的效果! 在這樣的交互或場景下,使用 Flexbox 來構建組件布局要比 Grid 靈活的多。
我們只需要在 Flexbox 容器上顯式設置 flex-wrap
的值為 wrap
即可,使用 Grid 來構建組件布局的話,要麻煩得多,你不得不去做一些額外的工作,比如根據媒體查詢或容器查詢來改變 grid-template-columns
的值或者顯式調整網格項目(svg
)的放置位置。
事實上,Flexbox 本質上做的事就是沿著不同的線條包裝元素,每個元素只關心在給定其不同的寬度時在可用空間中分配它的元素,而與上下線條沒有任何關系。所以,如果我不追求完美對齊的行和列的網格外觀,我會選擇 Flexbox。
此外,Flexbox 可以讓你毫不費力地調整子元素的大小,這也是使用 Grid 更不適合之處,比如上面所演示的按鈕組件。而且,像這樣的場景到處可見:
另外一個我更愿意使用 Flexbox 而不是 Grid 的原因是,如果我想創建一個設計,兩個或多個元素水平對齊,其中一個沿著可用空間伸展,而其他元素保持其自然寬度。比如下圖這樣的場景:
<div class="bar"><span class="icon"><svg></svg></span><!-- 其他 icon --><span class="icon"><svg></svg></span><div class="form"><input type="input"><span class="icon"><svg></svg></span></div><span class="icon"><svg></svg></span>
</div>
.bar {display: flex;align-items: center;gap: 1rem;
}.form {flex: 1 1 auto;min-width: 0;display: flex;gap: 1rem;align-items: center;
}.form input {flex: 1 1 auto;min-width: 0;
}
分別在 .bar
和 .form
兩個元素上創建了 Flex 容器,同時讓 .form
和 input
兩個元素上設置 flex: 1 1 auto
,讓它們能在主軸上根據 Flex 容器空間進行伸縮擴展:
當你改變容器大小時,你將看到的效果如下:
Demo 地址:https://codepen.io/airen/full/wvXxmZO
我認為使用 Flexbox 可以更好地滿足這個組件所需的靈活性,盡管使用 Grid 可以很容易地實現相同的設計。
.bar {display: grid;grid-template-columns: repeat(4, min-content) minmax(0, 1fr) min-content;align-items: center;gap: 1rem;
}.form {display: grid;grid-template-columns: minmax(0, 1fr) min-content;gap: 1rem;align-items: center;
}
Demo 地址: https://codepen.io/airen/full/yLEqjVJ
雖然最終結果是一樣的,但我認為 Flexbox 是更好的選擇,原因是,如果你以后決定在搜索欄兩側添加更多的圖標時,你不需要修改任何的 CSS 樣式:
換作是 Grid 布局,雖然向搜索欄的右側添加額外的圖標,布局看起來還行,但向搜索欄的左側添加額外的圖標時,就會打亂原先的布局:
這意味著你還必須根據新增的圖標數量來調整CSS,比如在搜索欄左側新增兩個,右側新增一個,你要像下面這樣調整網格軌道數量:
.bar {display: grid;grid-template-columns: repeat(6, min-content) minmax(0, 1fr) repeat(2, min-content);
}
Demo 地址:https://codepen.io/airen/full/jOKpxaq
這個示例也再次驗證了,如果你無法預判輸出內容時,最好是使用 CSS Flexbox 構建布局 。
再來看一個頁頭相關的布局。
如下圖所示,這個頁面有三個部分組成:
- 左邊有網站的 Logo(居左);
- 中間是一個導航菜單 (水平居中);
- 右側是用戶頭像,昵稱和一個購物車按鈕 (居右)。
估計不少開發者會采用 Flexbox 布局,并且設置它們兩端對齊:
header {display: flex;justify-content: space-between;
}
Flex 容器的剩余空間是按 space-between
分配給了相鄰 Flex 項目之間,并且第一個 Flex 項目(網站 Logo)緊挨著 Flex 容器左側邊緣(主軸起始邊緣),最后一個 Flex 項目(用戶頭加購物車)緊挨著 Flex 容器右側邊緣(主軸的結束邊緣)。或者說,兩端是對齊了,但這個示例中本該水平居中的導航菜單卻有一點點偏左:
造成這種現象的主要原因是,導航菜單兩邊的 Flex 項目寬度不相等 。所以最終渲染出來的結果并不符合設計要求的視覺效果。也就是說,構建這個頁頭的布局,使用 Flexbox 其實是不太適合的,如果你一定要使用 Flexbox 不是不可以,你需要添加額外的代碼。如果使用 Grid 來布局的話,就會簡單地多:
header { display: grid; grid-template-columns: 1fr auto 1fr; gap: 1rem;
}
設置了一個三列網格,并且第二列的列寬是根據導航菜單的寬度來決定的(auto
),并且把 Grid 容器可用空間(除導航欄寬度與列軌道間距之外的空間)均分成兩等份(第一列和第三列列寬是 1fr
),一份給了第一列(Logo 所占列),另一份給了第三列(用戶頭像、昵稱和購物車按鈕所在列):
另外,就頁面中導欄菜單和右側的用戶信息欄,我們可以使用 Flexbox 來布局:
這也是一個典型的 CSS Grid 和 CSS Flexbox 混合在一起使用的案例:
Demo 地址:https://codepen.io/airen/full/NWYmQmB
卡片組件在 Web 中是另一個常見的組件。像下圖這樣排列卡片的布局也是到處可見:
<article class="cta"><img src='https://picsum.photos/2568/600?random=1'><div class="cta__text-column"><h2>We Don’t Have the Right: A Decolonized Approach to Innovation</h2><p>This image has an aspect ratio of 3/2.</p><a href="">Read all about it</a></div>
</article>
你可能已經想到了,CSS Flexbox 的 flex-direction
就可以輕易實現:
.cta {display: flex;flex-wrap: wrap;
}.cta:nth-child(2n) {flex-direction: row-reverse;
}
只不過,它在卡片標題(h2
)、描述內容(p
)和按鈕(a
)外面需要額外添加一個容器(div
)。
相對而言,要是使用 CSS Grid 來實現這個布局效果的話,它的 HTML 結構能更簡潔和扁平化:
<article class="cta"><img src='https://picsum.photos/2568/600?random=1'><h2>We Don’t Have the Right: A Decolonized Approach to Innovation</h2><p>This image has an aspect ratio of 3/2.</p><a href="">Read all about it</a></article>
不過,你需要寫的 CSS 代碼要比 Flebox 多得多:
.grid {display: grid;grid-template-columns: minmax(0, 1fr) 340px;grid-template-rows: repeat(3, auto);row-gap: min(1.5rem, 2.5vw);grid-template-areas: "cta-title cta-img""cta-describe cta-img""cta-button cta-img";
}.grid img {grid-area: cta-img;
}.grid h2 {grid-area: cta-title;
}.grid p {grid-area: cta-describe;
}.grid a {grid-area: cta-button;
}.grid:nth-child(even) {grid-template-columns: 340px minmax(0, 1fr);grid-template-areas: "cta-img cta-title""cta-img cta-describe""cta-img cta-button";
}
Demo 地址:https://codepen.io/airen/full/gOKjKQm
繼續以卡片組件為例:
上圖涉及到單張卡片和多張卡的布局。在 CSS Grid 沒使用之前,不管是單張卡片還是多張卡片的布局,Web 開發者首先的布局方案是使用 CSS Flexbox。對于單張卡片的布局,使用 CSS Flexbox 一點問題都不存在。
<ul class="cards"><li class="card"><figure><img src="thumbnail.jpg" alt="卡片縮略圖" /></figure><h3>卡片標題</h3><p>卡片描述</p><button><svg></svg>卡片按鈕</button></li><!-- 省略其他卡片 -->
</ul>
對于單張卡片來說,使用 Flexbox 只是用來控制對齊方式、間距之類的。就這兩方面來說,不使用 Flexbox 也可以。如果希望底部的按鈕都能對齊,那就需要使用 Flexbox 來布局,這樣可以很容易實現:
/* 單個 card 布局 */
.card {display: flex;flex-direction: column;gap: var(--gap);align-items: center;
}button {margin-top: auto;
}
對于多張卡片的排列,如果你不希望卡片換行排列,又希望卡片能根據容器空間進行伸縮擴展,往往會使用 CSS 自定義屬性來和 CSS 的 calc()
函數計算每張卡片(Flex 項目)的基礎主尺寸 flex-basis
的值:
:root {--columns: 4; /* 每行要展示的列數 */--gap: 1rem; /* 列間距 */--flex-basis: calc((100% - (var(--columns) - 1) * var(--gap)) / var(--columns)); /* Flex 項目的基礎主尺寸,即每張卡片的 flex-basis 值 */
}/* cards layout */
.cards {display: flex;flex-wrap: wrap;gap: var(--gap);
}/* card layout */
.card {flex: 1 1 var(--flex-basis);display: flex;flex-direction: column;gap: var(--gap);align-items: center;
}button {margin-top: auto;
}
這個時候效果看上去還是不錯的:
Demo 地址: https://codepen.io/airen/full/dyKjgGE
但突然有一天,設計師說,每行只排列三張卡片(--columns: 3
)或者說卡片最小寬度不能小于某個尺寸,比如說 300px
。或者說,服務端輸出的卡片數量不只四個,那么上面的布局就會因為設計需求調整或內容輸出的變化,造成卡片換行展示:
而且,你還會發現,新換行的卡片寬度會根據 Flex 容器的空間伸縮擴展:
雖然換行的卡片能自動進行伸縮擴展,但可能不符合預期的布局效果。如果要讓布局符合預期,就需要你做一些其他的設置,比如說:
- 給卡片設置相同的
min-width
和max-width
值; - 不允許卡片(Flex 項目)能根據 Flex 容器空間自動伸縮擴展,即
flex: 0 0 var(--flex-basis)
。
如果這樣做的話,又將失去了 CSS Flexbox 的優勢,Flex 項目不具彈性了。不過,我們可以考慮將 CSS Flexbox 和 CSS Grid 兩種技術結合起來構建所需的 Web 布局。即,單張卡片 .card
使用 CSS Flexbox 布局,多張卡片排列 .cards
則使用 CSS Grid 布局 。你將會發現,這樣構建出來的布局完全能符合你預期想要的效果。
.cards {display: grid;grid-template-columns: repeat(auto-fit, minmax(min(100% - 2rem, 300px), 1fr));gap: 1rem;
}.card {display: flex;flex-direction: column;gap: 1rem;align-items: center;
}button {margin-top: auto;
}
Demo 地址:https://codepen.io/airen/full/WNygGVx
另外,CSS Flexbox 有一個效果實現起來難度也是非常的大。比如說,每張卡片每個元素區域在垂直方向都要對齊:
就上圖所示效果,對于 CSS Grid 布局來說,一點難度都沒有。我們可以使用 CSS Grid 中的 subgrid
(子網格)來實現:
.cards {display: grid;grid-template-columns: repeat(auto-fit, minmax(min(100% - 2rem, 300px), 1fr));gap: 1rem;
}.card {grid-row: span 4;display: grid;grid-template-rows: subgrid;gap: 1rem;
}button {place-self: center;
}
最終你在支持 subgrid
瀏覽器中看到的效果是很完美的:
Demo 地址:https://codepen.io/airen/full/oNyPYXm
雖然 CSS Flexbox 能實現大部分 Web 布局,但也有不少布局是不太適用的,尤其是層疊類的布局:
我們來看一個層疊方面的示例:
.cards {width: min(100% - 2rem, 768px);margin: auto;display: grid;grid-template-columns: repeat(2, minmax(0, 1fr));gap: 2rem;
}li:nth-of-type(1) {grid-column: 1 / -1;
}.card {display: grid;grid-template-rows: 3rem repeat(4, auto) 3rem;grid-template-columns: 1.5rem minmax(0, 1fr) 1.5rem;grid-template-areas:"... ... ...""... subtitle ...""... title ...""... content ...""... button ...""... ... ...";row-gap: 1.25rem;
}.card > *:not(figure) {z-index: 2;justify-self: center;
}.card figure,
.card::before {grid-area: 1 / 1 / -1 / -1;
}.card::before {z-index: 1;
}.card .subtitle {grid-area: subtitle;
}.card h3 {grid-area: title;
}.card p {grid-area: content;
}.card button {grid-area: button;align-self: center;
}
這里使用了嵌套網格來實現的布局效果。父網格 .cards
構建一個九宮格的布局,每張卡片 .card
是一個內網格,并且使用 grid-template-areas
命名了網格區域,方便使用 grid-area
來放置網格項目(卡片上的每個元素)。其中最為關鍵的部分是 figure
和 .card::before
都跨越了整個網格列軌道和網格行軌道,填充整個網格,并且顯式設置 z-index
值,指定其在 z
軸的層級。
Demo 地址: https://codepen.io/airen/full/YzvONXW
小結
在這節課中,我們主要一起探討了 CSS Grid 和 CSS Flex 之間差異,并且通過具體的示例討論了幾種選擇使用 CSS Grid 還是 CSS Flexbox 布局的方法。
課程中所介紹的經驗法則并沒有什么指導性的,我認為沒有哪一種布局是絕對性優勝于其他布局的,也沒有哪一種布局是絕對性地替代其他布局的。它們應該是共存的 、互補的 、協作的等。