CSS 如何改變網格布局偶數行的排序?
最近在項目中看到這樣一個布局,如下
圖片
布局本身沒什么奇怪的,就是 「4 * 2」 的網格,比較特殊的是第二行布局是從右往左的,整體是一個這樣的順序
圖片
而這個列表是通過一個數組動態渲染的,可能有很多同學會將這個數組分成兩份,然后將第二份進行反向,類似于這樣
let arr1 = list.slice(0, 4)
let arr2 = list.slice(4, 8).reverse()
然后,由于第二行的第一個其實是原數組的第八個,還需要針對第二行做額外的處理,比如序列
// 第一行
第 {{ i }} 個
// 第二行
第 {{ 8 - i }} 個
而且,如果有點擊事件,傳值也需要額外處理,雖然也能實現,但顯然是太麻煩,而且還容易出 bug。
那么,有沒有其他更簡單、更穩定的方式來解決呢?也就是如何讓第二行子項反向呢?
一、flex 布局實現
由于這里是動態渲染,所以最理想的結構應該是這樣的,直接一層循環搞定
<div class="list">
<div class="item">1</div>
<div class="item">2</div>
<div class="item">3</div>
<div class="item">4</div>
<div class="item">5</div>
<div class="item">6</div>
<div class="item">7</div>
<div class="item">8</div>
</div>
通過 flex 或者 grid都很容易實現4 * 2的布局,先用 flex實現
.list{
display: flex;
width: 600px;
gap: 20px;
flex-wrap: wrap;
}
.item{
width: calc( (100% - 60px) / 4 );
aspect-ratio: 1/1;
background: royalblue;
color: #fff;
font-size: 30px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
}
可以得到這樣的布局
圖片
有什么辦法在不改變 html 的情況下改變第二行的位置呢???
在 flex中,可以直接通過order進行排序,order越大,元素越靠后。
https://developer.mozilla.org/zh-CN/docs/Web/CSS/order
比如,我們將第 5 個元素的order設置成1
.item:nth-child(5){
order: 1
}
由于默認是0,現在第 5 個元素的order最大,所以它跑到了最后面
圖片
按照這樣的規則,我們可以將第 5、6、7、8 個子項的order分別一次減少就行了(大于0)
.item:nth-child(5){
order: 4;
}
.item:nth-child(6){
order: 3;
}
.item:nth-child(7){
order: 2;
}
.item:nth-child(8){
order: 1;
}
這樣第二行就反向了,如下
圖片
當然還可以在循環的時候,加上 CSS 變量
<div class="list">
<div class="item" style="--i: 1">1</div>
<div class="item" style="--i: 2">2</div>
<div class="item" style="--i: 3">3</div>
<div class="item" style="--i: 4">4</div>
<div class="item" style="--i: 5">5</div>
<div class="item" style="--i: 6">6</div>
<div class="item" style="--i: 7">7</div>
<div class="item" style="--i: 8">8</div>
</div>
然后可以用 「calc」動態去改變order,如下
.item:nth-child(n + 5){
order: calc( 8 - var(--i))
}
同樣能達到相同的效果,完整代碼可以查看
- CSS flex order (juejin.cn)[1]
- CSS flex order (codepen.io)[2]
二、grid 布局實現
還是同樣的布局,現在換 grid 實現,正常情況下,可能會直接用repeat(4, 1fr)來實現一個 4 * 2的布局
.list{
display: grid;
width: 600px;
gap: 20px;
grid-template-columns: repeat(4, 1fr);
}
.item{
aspect-ratio: 1/1;
background: royalblue;
color: #fff;
font-size: 30px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
}
效果如下
圖片
那么,如何讓下面一行反過來呢?
除了使用上面的方式,還可以用帶有名稱的grid-template-areas來實現,比如我們這樣命名
.list{
/**/
grid-template-areas:
'a1 a2 a3 a4'
'a5 a6 a7 a8';
}
這樣就劃分成了a1~a8一共八塊區域,為了方便映射,我們可以在生成 html時,通過 CSS 變量帶上這些名稱
<div class="list">
<div class="item" style="--i: a1">1</div>
<div class="item" style="--i: a2">2</div>
<div class="item" style="--i: a3">3</div>
<div class="item" style="--i: a4">4</div>
<div class="item" style="--i: a5">5</div>
<div class="item" style="--i: a6">6</div>
<div class="item" style="--i: a7">7</div>
<div class="item" style="--i: a8">8</div>
</div>
這樣就可以很方便的把每一個子項“填入”對應的區域了
.item{
/**/
grid-area: var(--i);
}
現在仍然是默認順序,如果要改變第二行的順序,直接grid-template-areas就行了
.list{
/**/
grid-template-areas:
'a1 a2 a3 a4'
'a8 a7 a6 a5'; /* 把第二行反向 */
}
這樣就很方便直觀的改變了子項的位置了,效果如下
圖片
完整代碼可以查看
- CSS grid area (juejin.cn)[3]
- CSS grid area (codepen.io)[4]
三、更加自由的“蛇形布局”
上面的例子只有兩行,如果有多行呢,并且行數不定,如何處理呢?就像這樣的
圖片
這種時候用 grid 可能少許不方便了(可能我還沒找到精髓??),下面用 flex 實現
目前 CSS 中并沒有能夠檢測第幾行的選擇器,所以只能用其他方式。假設每一行的個數是確定的,這里是 4,那么,第二行就是5、6、7、8,隔一行,第四行就是13、14、15、16,依次類推。
有什么方式可以匹配第幾個呢?沒錯,就是:nth-child,由于是隔一行,所以是每 8 個一個循環,可以這樣來選擇偶數行,如下
.item:nth-child(8n + 5){
/*選擇第5、13、21...*/
}
.item:nth-child(8n + 6){
/*選擇第6、14、22...*/
}
.item:nth-child(8n + 7){
/*選擇第7、15、23...*/
}
.item:nth-child(8n + 8){
/*選擇第8、16、24...*/
}
由于默認的order是0,如果改變其他的order肯定會跑到后面去,為了避免影響,可以先手動設置order
.item{
/**/
order: var(--i);
}
下面要對偶數行的順序進行調整,比如第二行
圖片
第 1 個的位置調到第4個位置,所以 order需要加 3
第 2 個的位置調到第3個位置,所以 order需要加 1
第 3 個的位置調到第2個位置,所以 order需要減 1
第 4 個的位置調到第1個位置,所以 order需要減 3
最后實現就是
.item:nth-child(8n + 5){
order: calc(var(--i) + 3)
}
.item:nth-child(8n + 6){
order: calc(var(--i) + 1)
}
.item:nth-child(8n + 7){
order: calc(var(--i) - 1)
}
.item:nth-child(8n + 8){
order: calc(var(--i) - 3)
}
這樣就實現了行數不固定的“蛇形布局”,完整代碼如下
- CSS flex snake (juejin.cn)[5]
- CSS flex snake (codepen.io)[6]
四、優勢和總結
這樣實現有什么優勢呢?很明顯 JavaScript 無需關注布局,只用處理業務邏輯就行,也無需單獨對第二行元素做特殊處理,特別是序列,之前很容易混亂,最重要的是實現更加清晰明了,也更加穩定。下面總結一下要點
- 用 js 來修改布局比較麻煩,而且邏輯容易混亂
- flex 中可以用 order 來改變子項位置
- 借助 CSS 變量可以更加方便地映射到每個子項
- grid 中可以用 grid-template-areas 手動指定每個子項的位置
- 蛇形布局可以用 nth-child 選中偶數項,從而改變位置
按照我的經驗,布局最好 CSS 單獨完成,不要讓 JS 參與其中,這樣邏輯也會更加清晰。