菇街PC首頁瀑布流實踐
作者:蘑菇街前端團隊
鏈接:https://juejin.im/post/5e05acf0f265da33d158a1b1
這篇文章主要是介紹網站頁面瀑布流布局的實現,主要包括:
瀑布流, 又稱瀑布流式布局,是比較流行的一種網站頁面布局。視覺表現為寬度相等高度不定的元素組成的參差不齊的多欄布局,隨著頁面向下滾動,新的元素附加到最短的一列而不斷向下加載。
瀑布流本質上就是尋找各列之中高度最小的一列,并將新的元素添加到該列后面,只要有新的元素需要排列,就繼續尋找所有列中的高度最小列,把后來的元素添加到高度最小列上。
我們接下來看下為什么要永遠尋找最小列?
先看圖1的排列順序,第一排元素的頂部會處于同一個高度,依次排列在頂端,第一排排滿之后,第二排從左往右排列。然而這種排列方式很容易出現其中一列過長或其中一列過短的情況。
為了解決 圖1 中列可能過長或者過短的問題,我們按照 圖2 的方式將元素放在最短的一列進行排列。
瀑布流滑動的時候會不停的出現新的東西,吸引你不斷向下探索,巧妙的利用視覺層級、視線的任意流動來緩解視覺的疲勞,采用這種方案可以延長用戶停留視覺,提高用戶粘度,適合那些隨意瀏覽,不帶目的性的使用場景,就像逛街一樣,邊走邊看,所以比較適合圖片、商品、資訊類的場景,很多電商相關的網站都使用了瀑布流進行承載。
上圖的蘑菇街 PC 瀑布流效果是在基礎瀑布流的基礎上做了擴展改造, 在瀑布流頂部某一列或某幾列插入其他非瀑布流內容。
本文將介紹這種擴展瀑布流的四列實現場景,適用基礎場景如下:
我們采用 Vue 框架來實現瀑布流,其一些自帶屬性使我們的瀑布流實現更加簡單。
通過 Vue 的具名插槽(slot),將非瀑布流元素作為父組件的內容傳遞給瀑布流子組件。
<!-- 父組件 -->
<div class="parent">
<Waterfall :merge=true :mergeHeight=800 mergeColumns=[2,3]>
<template slot="first-col">
<!-- 第一列內容... -->
</template>
<template slot="second-col">
<!-- 第二列內容... -->
</template>
<template slot="third-col">
<!-- 第三列內容... -->
</template>
<template slot="last-col">
<!-- 第四列內容... -->
</template>
<template slot="merge-col">
<!-- 合并內容... -->
</template>
</Waterfall>
</div>
<!-- 子組件(waterfall) -->
<div class="child">
<!-- 第一列 -->
<div ref="column1" :style="{marginTop: merge && mergeColumns.indexOf(1) > -1 ? mergeHeight + 'px':''}">
<template v-if="$slots['first-col']">
<slot name="first-col"></slot>
</template>
<template v-for="(item, index) in columnList1">
<!-- 第一列瀑布流內容... -->
</template>
</div>
<!-- 第二列 -->
<div ref="column2" :style="{marginTop: merge && mergeColumns.indexOf(2) > -1 ? mergeHeight + 'px':''}">
<template v-if="$slots['second-col']">
<slot name="second-col"></slot>
</template>
<template v-for="(item, index) in columnList2">
<!-- 第二列瀑布流內容... -->
</template>
</div>
<!-- 第三列 -->
<div ref="column3" :style="{marginTop: merge && mergeColumns.indexOf(3) > -1 ? mergeHeight + 'px':''}">
<template v-if="$slots['third-col']">
<slot name="third-col"></slot>
</template>
<template v-for="(item, index) in columnList3">
<!-- 第三列瀑布流內容... -->
</template>
</div>
<!-- 第四列 -->
<div ref="column4" v-if="is4Columns">
<template v-if="$slots['last-col']">
<slot name="last-col"></slot>
</template>
<template v-for="(item, index) in columnList4">
<!-- 第四列瀑布流內容... -->
</template>
</div>
<!-- 合并塊非瀑布流內容 -->
<div class="column-merge" v-if="merge" :style="{left: (mergeColumns[0] - 1)*330 + 'px'}">
<slot name="merge-col"></slot>
</div>
</div>
每一列都定義一個 ref,通過 ref 獲取當前列的高度,如果該列上方有合并塊,則高度要加上合并塊的高度,然后比較 4 列高度取到最小高度,再通過最小高度算出其對應的列數。
// 通過ref獲取每列高度,column1,column2,column3,column4分別代表第一、二、三、四列
let columsHeight=[this.$refs.column1.offsetHeight, this.$refs.column2.offsetHeight, this.$refs.column3.offsetHeight, this.$refs.column4.offsetHeight]
// 如果包含合并塊, 則更新高度,合并塊下的列高要增加合并塊的高度
if(this.merge){
// 如果有合并列,則合并列下的列高度要加合并內容的高度。
columsHeight[0]=this.mergeColumns.indexOf(1) > -1 ? columsHeight[0] + this.mergeHeight : columsHeight[0];
columsHeight[1]=this.mergeColumns.indexOf(2) > -1 ? columsHeight[1] + this.mergeHeight : columsHeight[1];
columsHeight[2]=this.mergeColumns.indexOf(3) > -1 ? columsHeight[2] + this.mergeHeight : columsHeight[2];
columsHeight[3]=this.mergeColumns.indexOf(4) > -1 ? columsHeight[3] + this.mergeHeight : columsHeight[3];
}
// 獲取各列最小高度
let minHeight=Math.min.apply(null, columsHeight);
// 通過最小高度,得到第幾列高度最小
this.getMinhIndex(columsHeight, minHeight).then(minIndex=> {
// 渲染加載邏輯
});
// 獲取高度最小索引函數
getMinhIndex(arr, value){
return new Promise((reslove)=> {
let minIndex=0;
for(let i in arr){
if(arr[i]==value){
minIndex=i;
reslove(minIndex);
}
}
});
}
瀑布流常用在無限下拉加載或者加載數據量很大、且包含很多圖片元素的情景,所以通常不會一次性拿到所有數據,也不會一次性將拿到的數據全部渲染到頁面上,否則容易造成頁面卡頓影響用戶體驗,所以何時進行渲染、何時繼續請求數據就很關鍵。
選擇渲染的區域為滾動高度 + 可視區域高度的 1.5 倍,既可以防止用戶滾動到底部的時候白屏,也可以防止渲染過多影響用戶體驗。如果 最小列的高度 - 滾動高度 < 可視區域高 * 1.5 ,則繼續渲染元素,否則不再繼續渲染。
當已渲染的元素+可視區域可以展示的預估元素個數 > 已請求到的個數 的時候才去繼續請求更多數據,防止請求浪費。 如果 已加載的元素個數 + 一屏可以展示的元素預估個數 > 所有請求拿到的元素個數 ,則觸發下一次請求去獲取更多數據。
data() {
return {
columnList1: [], // 第一列元素列表
columnList2: [],
columnList3: [],
columnList4: [],
renderIndex: -1, // 渲染第幾個item
isRendering: false, // 是否正在渲染
itemList: [], // 所有元素列表
isEnd: false
};
}
watch: {
renderIndex(value) {
// 當前滾動條高度
const scrollTop=document.documentElement.scrollTop || window.pageYOffset || document.body.scrollTop;
// 最小列高度 - 滾動高度 < 可視區域高的的1.5倍
if (renderMinTop - scrollTop < winHeight * 1.5) {
this.renderWaterfall();
}
// 已加載的元素個數 + 一屏可以展示元素預估個數 > 所有請求拿到的元素個數
if (loadedItemNum + canShowItemNum > this.itemList.length && !this._requesting && !this.isEnd) {
// 請求瀑布流數據
this.getData();
}
}
}
scroll() {
// 當前滾動條高度
const scrollTop=document.documentElement.scrollTop || window.pageYOffset || document.body.scrollTop;
// 底部檢測高度
const bottomDetectionTop=this.$refs.bottomDetection.offsetTop;
const tempLastScrollTop=lastScrollTop; // lastScrollTop:上次一滾動高度
lastScrollTop=scrollTop;
if (tempLastScrollTop===-1) {
this.renderWaterfall();
}
// 如果是向下滾動則判斷是否需要繼續渲染
if (scrollTop > tempLastScrollTop) {
if (bottomDetectionTop - tempLastScrollTop < winHeight * 1.5 && !this.isRendering) {
this.renderWaterfall();
}
}
}
renderWaterfall() {
// 如果還沒有數據、所有數據已經渲染完成、正在渲染則不進行渲染計算操作
if (this.itemList.length===0 || this.renderIndex >=this.itemList.length - 1 || this.isRendering) {
if (this.renderIndex===this.feedList.length - 1 && !this._requesting && !this.isEnd) {
this.getData();
}
return;
}
this.isRendering=true;
/***
*** 獲取最小高度代碼
***/
this.getMinhIndex(columnsHeight, minHeight).then(minIndex=> {
const key=`columnList${minIndex + 1}`;
let itemData=this.itemList[this.renderIndex + 1];
this[key]=this[key].concat(itemData);
this.$nextTick(()=> {
this.renderIndex=this.renderIndex + 1;
this.isRendering=false;
});
});
}
為了靈活使用瀑布流,在設計的時候就做好了擴展準備,通過 HTML 模板代碼可以看出來,具名插槽的內容可以放在任意列,并沒有限制死,所以可以擴展使用到以下各個場景。
大家好,這里是 FEHub,每天早上 9 點更新,為你分享優質精選文章,與你一起進步。
如果喜歡這篇文章,希望大家點贊,評論,轉發。你的支持,是我最大的動力,咱們明天見 :)
關注公眾號 「FEHub」,每天進步一點點
感謝大家的支持,周末領紅包,加雞腿。
公眾號后臺回復關鍵字:「雞腿」,即可參與紅包抽獎。
者 | 木易揚
責編 | 伍杏玲
本人是去年 7-8月開始準備面試,過五關斬六將,最終在年末抱得網易歸,深深感受到高級前端面試的套路。以下是自己整理的面試題匯總,不敢藏私,統統貢獻出來。
面試的公司分別是:阿里、網易、滴滴、、有贊、挖財、滬江、餓了么、攜程、喜馬拉雅、兌吧、微醫、寺庫、寶寶樹、海康威視、蘑菇街、酷家樂、百分點和海風教育。
以下是面試題匯總,后續階段會持續深入更新面試題解,共勉!
阿里
網易
滴滴
有贊
挖財
滬江
餓了么
攜程
喜馬拉雅
兌吧
微醫
寺庫
寶寶樹
海康威視
蘑菇街
酷家樂
百分點
海風教育
作者簡介:木易楊,網易高級前端工程師,跟著我每周重點攻克一個前端面試重難點。讓我帶你走進高級前端的世界,在進階的路上,共勉!
聲明:本文系作者投稿,版權歸作者所有。
先,我們來看一下什么是瀑布流布局效果,比如電商網站 蘑菇街
原理圖:
在一個大盒子里,放置多個小盒子,小盒子的大小可以不一致,長短不一樣,呈現一種瀑布流的效果。
使用CSS3S實現只需要如下4步:
1. 準備圖片素材
2. 書寫相應HTML結構
3. 了解CSS 多欄(Multi-column) 屬性
4. 使用CSS 多欄屬性完成瀑布流布局
*請認真填寫需求信息,我們會在24小時內與您取得聯系。