菇街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小时内与您取得联系。