實(shí)現(xiàn)瀑布流布局,就這幾行代碼?
瀑布流布局是一種比較流行的頁(yè)面布局方式,表現(xiàn)為參差不齊的多欄卡片。跟網(wǎng)格布局相比,顯得更靈動(dòng),更具藝術(shù)氣息。
實(shí)現(xiàn)瀑布流布局的方式有多種,比如multi-column布局,grid布局,flex 布局等。但是這些實(shí)現(xiàn)方式都有各自的局限性,代碼也略復(fù)雜。
其實(shí),有個(gè)最原始、最簡(jiǎn)單,也是兼容性最好的實(shí)現(xiàn)方式,那就是使用絕對(duì)定位。瀑布流布局的元素是一些等寬不等高的卡片,只要根據(jù)元素的實(shí)際寬高計(jì)算出自己的坐標(biāo)位置就行了。
要計(jì)算坐標(biāo)自然要用到 JavaScript,這就不是純 CSS 方案,對(duì)某些前端極客來(lái)講顯得不那么純粹。不過(guò)只要理清思路了,也用不了幾行代碼。本文就給出最近實(shí)現(xiàn)的一個(gè)版本。
//?計(jì)算每個(gè)卡片的坐標(biāo)
export?function?calcPositions({?columns?=?2,?gap?=?7,?elements?})?{
??if?(!elements?||?!elements.length)?{
????return?[];
??}
??const?y?=?[];?//上一行卡片的底部縱坐標(biāo)數(shù)組,用于找到新卡片填充位置
??const?positions?=?[];?//?每個(gè)卡片的坐標(biāo)數(shù)組
??elements.forEach((item,?index)?=>?{
????if?(y.length?//?還未填滿(mǎn)一行
??????y.push(item.offsetHeight);
??????positions.push({
????????left:?(index?%?columns)?*?(item.offsetWidth?+?gap),
????????top:?0
??????});
????}?else?{
??????const?min?=?Math.min(...y);?//?最小縱坐標(biāo)
??????const?idx?=?y.indexOf(min);?//?縱坐標(biāo)最小的卡片索引
??????y.splice(idx,?1,?min?+?gap?+?item.offsetHeight);?//?替換成新卡片的縱坐標(biāo)
??????positions.push({
????????left:?idx?*?(item.offsetWidth?+?gap),
????????top:?min?+?gap
??????});
????}
??});
//?由于采用絕對(duì)定位,容器是無(wú)法自動(dòng)撐開(kāi)的。因此需要計(jì)算實(shí)際高度,即最后一個(gè)卡片的top加上自身高度
??return?{?positions,?containerHeight:?positions[positions.length?-?1].top?+?elements[elements.length?-?1].offsetHeight?};
}
上面這段代碼的作用就是計(jì)算每個(gè)卡片的left、top,以及容器的總高度。關(guān)鍵位置都有注釋?zhuān)瑧?yīng)該不難理解。
有了這幾行核心代碼,要想封裝成瀑布流組件就很容易了。以 Vue 為例,可以這樣封裝:MasonryLite.vue
<template>
??<div?class="masonry-lite">
????<slot>slot>
??div>
template>
<script>
import?{?calcPositions?}?from?'./index.js';
export?default?{
??name:?'MasonryLite',
??props:?{
????gap:?{
??????type:?Number,
??????default:?12,
????},
????columns:?{
??????type:?Number,
??????default:?2,
????},
??},
??data()?{
????return?{};
??},
??mounted()?{
????this.doLayout();
??},
??methods:?{
????doLayout()?{
??????const?children?=?[...this.$el.querySelectorAll('.masonry-item')];
??????if?(children.length?===?0)?{
????????return;
??????}
??????const?{?positions,?containerHeight?}?=?calcPositions({
????????elements:?children,
????????columns:?this.columns,
????????gap:?this.gap,
??????});
??????children.forEach((item,?index)?=>?{
????????item.style.cssText?=?`left:${positions[index].left}px;top:${positions[index].top}px;`;
??????});
??????this.$el.style.height?=?`${containerHeight}px`;
????},
??},
};
script>
<style?lang="scss"?scoped>
.masonry-lite{
??position:?relative;
}
.masonry-item?{
??position:?absolute;
}
style>
使用組件:
<MasonryLite>
??<div?class="product-card masonry-item"?v-v-for="(item,?index)?in?items"?:key="index">
????<img?:src="item.imageUrl"?/>
????<header>{{?item.title?}}header>
??div>
MasonryLite>
不過(guò)這樣其實(shí)還會(huì)有點(diǎn)問(wèn)題,就是doLayout的執(zhí)行時(shí)機(jī)。因?yàn)樵摲桨富诮^對(duì)定位,需要元素在渲染完成后才能獲取到實(shí)際寬高。如果卡片內(nèi)有延遲加載的圖片或者其他動(dòng)態(tài)內(nèi)容,高度會(huì)發(fā)生變化。這種情況下就需要在DOM更新后主動(dòng)調(diào)用一次doLayout重新計(jì)算布局。
如果大家有更好的實(shí)現(xiàn)方案,歡迎交流!
如果覺(jué)得對(duì)你有幫助,幫忙點(diǎn)個(gè)不要錢(qián)的star。
往期干貨:
?26個(gè)經(jīng)典微信小程序+35套微信小程序源碼+微信小程序合集源碼下載(免費(fèi))
?干貨~~~2021最新前端學(xué)習(xí)視頻~~速度領(lǐng)取
?前端書(shū)籍-前端290本高清pdf電子書(shū)打包下載
點(diǎn)贊和在看就是最大的支持??
