使用 CSS Columns 布局來實(shí)現(xiàn)自動(dòng)分組布局
最近在項(xiàng)目中碰到這樣一個(gè)布局,有一個(gè)列表,先按照 4 * 2 的正常順序排列,當(dāng)超過 8 個(gè)后,會(huì)橫向重新開始 4 * 2 的布局,有點(diǎn)像一個(gè)個(gè)獨(dú)立的分組,然后水平排列,如下
圖中序號是 dom 序列,所以其實(shí)這這樣的一個(gè)順序。
很多同學(xué)可能會(huì)想到給子元素分組(通過 JS
將原數(shù)組拆分組合成一個(gè)二維數(shù)組),每 8 個(gè)套一層容器,然后水平排列就行了
是不是有點(diǎn)麻煩呢?
其實(shí),無需單獨(dú)嵌套容器也能實(shí)現(xiàn)類似分組的效果,這就需要借助本文要介紹的 column 布局了,一起看看吧~
一、簡單介紹一下 columns
平時(shí)接觸較多的都是flex或者grid,但還有一個(gè)columns布局往往被忽視了。
https://developer.mozilla.org/zh-CN/docs/Web/CSS/columns
columns布局,又稱“多列”布局(或者“分欄”布局),這是一個(gè)使用場景比較有限,但是幾乎無法被替代的一種布局。
使用非常簡單,直接看一個(gè)例子,假設(shè)有這樣一段文本
p{
width: 500px;
}
<p>歡迎關(guān)注前端偵探,這里有一些有趣的、你可能不知道的HTML、CSS、JS小技巧技巧,比如這篇文章,如何使用 CSS columns 布局來實(shí)現(xiàn)自動(dòng)分組布局,一起看看吧</p>
默認(rèn)是這樣的。
下面我們將段文本分成3列。
p{
columns: 3;
}
看,自動(dòng)就分成了3列。
并且閱讀順序是從左到右,從下到下,直到整列閱讀完成,非常類似以前的報(bào)刊讀物閱讀習(xí)慣。
除了指定列數(shù),還可以根據(jù)指定寬度自動(dòng)去計(jì)算列數(shù),比如:
p{
columns: 100px;
}
效果如下:
這個(gè)表示按照最小100px去分段,看最多可以分成多少列,并不是說每列就一定是 100,應(yīng)該是大于等于 100,直到剩余空間可以再放下一列。
那為啥設(shè)置的是100,總寬度是500,卻只分成了4列?原因是有「默認(rèn)列間距」,如果去除這個(gè)間距。
p{
column-gap: 0px;
}
這樣就剛好被分成了5列。
看不清楚?加個(gè)分割線試試。
p{
column-rule: 1px solid red;
}
是不是剛好分成了 5 列?(注意,這里的分割線是不占空間的)。
簡單了解columns多列布局后,下面來看另外的用途。
二、columns 實(shí)現(xiàn)橫向分組布局
可能你已經(jīng)發(fā)現(xiàn)了,上面的文本分列布局和我們文章開頭所需要的效果非常類似,都是一列一列的,因此我們可以嘗試用columns布局來實(shí)現(xiàn)這樣的效果。
假設(shè)html是這樣的。
<div class="wrap">
<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 class="item">9</div>
<div class="item">10</div>
<div class="item">11</div>
<div class="item">12</div>
<div class="item">13</div>
<div class="item">14</div>
</div>
</div>
這里多了一層wrap是用來做滾動(dòng)容器的,簡單修飾一下。
.wrap{
display: flex;
width: 400px;
overflow: auto;
outline: 1px dashed #9747FF;
}
.item{
display: inline-flex;
width: 80px;
margin: 10px;
aspect-ratio: 1/1;
background: #FFE8A3;
color: #333;
font-size: 30px;
border-radius: 10px;
align-items: center;
justify-content: center;
}
效果如下,很正常的一個(gè)布局。
現(xiàn)在,我們希望縱向高度是固定的,然后橫向滾動(dòng),先加一個(gè)高度試試。
.list{
height: 200px;
}
這樣就變成了縱向滾動(dòng)的布局了。
那么,如何讓它橫向分欄并且滾動(dòng)呢?其實(shí)非常簡單,只需要添加一行。
.list{
height: 200px;
column-width: 400px;
}
設(shè)置分欄寬度為滾動(dòng)容器寬度之后,就自動(dòng)將整個(gè)列表分成多組了,相當(dāng)于每個(gè)滾動(dòng)屏幕作為一組,從左到右排列,由于空間不足,所以可以橫向滾動(dòng)。
是不是非常神奇?僅需一個(gè)屬性就實(shí)現(xiàn)了縱向滾動(dòng)到橫向滾動(dòng)的切換。
三、借助 scroll-snap 實(shí)現(xiàn)輪播效果
通常碰到這種橫向滾動(dòng)的效果,你可能會(huì)想到一個(gè)swiper組件,也就是那種一屏一屏切換的效果,沒錯(cuò),我們這里也可以借助scroll-snap輕易實(shí)現(xiàn)。
關(guān)于scroll-snap,網(wǎng)上教程非常多,MDN 官網(wǎng)也有非常清晰的 demo,如果不熟悉的可以先去了解一下:https://developer.mozilla.org/zh-CN/docs/Web/CSS/scroll-snap-type。
這里就不詳細(xì)介紹了。
回到這里,由于整個(gè)列表下面只有一層子元素,好像并沒有辦法區(qū)分每一屏的臨界點(diǎn)。其實(shí)不然,可以想一下,這里每一屏有 8 個(gè)元素,也就是第1、9、17...個(gè)分別是每一屏的第一個(gè)元素,是不是可以以這些元素為標(biāo)識(shí)(吸附對象)呢?
首先要在滾動(dòng)容器下定義一下。
.wrap{
scroll-snap-type: x mandatory;
}
然后給第1、9、17...個(gè)元素添加吸附對象,這里可以用nth-child選擇器。
.item:nth-child(8n+1){
scroll-snap-align: start;
}
效果如下(為了區(qū)分,把每一屏的第一個(gè)元素背景做了高亮)。
還可以多添加點(diǎn)元素,多切幾屏看看效果:
完整代碼可以參考:
- CSS columns (juejin.cn)[1]
- CSS column (codepen.io)[2]
四、CSS 實(shí)現(xiàn)的優(yōu)勢和總結(jié)
相對于傳統(tǒng)的 JS實(shí)現(xiàn)來說,有哪些好處呢?
- 少了一層嵌套容器,業(yè)務(wù)邏輯會(huì)根據(jù)干凈。
- 自適應(yīng)強(qiáng),可以根據(jù)需求選擇固定列數(shù)或者固定寬度,JS往往只能根據(jù)數(shù)量去分組。
- 不會(huì)報(bào)錯(cuò),想想看,JS中的數(shù)組經(jīng)常會(huì)出現(xiàn)xxx.slice is not function這樣的錯(cuò)誤,輕則警告,總則整個(gè)頁面白屏。
- 布局足夠靈活,想橫向滾動(dòng)就橫向滾動(dòng),想縱向滾動(dòng)就縱向滾動(dòng),而JS方式往往還需要改變數(shù)組形態(tài)。
有這么多好處還不趕緊用起來?下面再來回顧一下columns布局。
- columns布局,又稱“多列”布局(或者“分欄”布局),可以將默認(rèn)的文本流輕易分成多欄,非常類似以前的報(bào)刊讀物排版。
- column可以通過寬度(column-width)去自動(dòng)分割,或者通過指定數(shù)量(column-count)將布局分成多少欄。
- column-gap可以設(shè)置分欄之間的空隙,默認(rèn)是有間隔的。
- column-rule可以設(shè)置分隔線,這種分割線是不占據(jù)空間的。
- columns布局使用場景比較有限,但是幾乎無法被替代。
多想象一下,其實(shí)可以有更多的使用場景,雖然本來并不是做這個(gè)事情的。
[1]CSS columns (juejin.cn): https://code.juejin.cn/pen/7293927297596260379。
[2]CSS column (codepen.io): https://codepen.io/xboxyan/pen/RwvPLEM。