本文共 4907 字,大约阅读时间需要 16 分钟。
瀑布流, 又称瀑布流式布局。是比较流行的一种网站页面布局,视觉表现为宽度相等高度不定的元素组成的参差不齐的多栏布局,随着页面向下滚动,新的元素附加到最短的一列而不断向下加载。
寻找各列之中高度最小者,并将新的元素添加到该列上,然后继续寻找所有列的高度最小者,继续添加到高度最小列上,一直到所有元素均按要求排列完成为止。
瀑布流滑动的时候会不停的出现新的东西,吸引你不断向下探索,巧妙的利用视觉层级,视线的任意流动来缓解视觉的疲劳,采用这种方案可以延长用户停留视觉,提高用户粘度,适合那些随意浏览,不带目的性的使用场景,就像逛街一样,边走边看,所以比较适合图片、商品、资讯类的场景,很多电商相关的网站都使用了瀑布流进行承载。
如何寻找所有列的高度最小者? 如何渲染瀑布流?
采用 Vue 框架来实现瀑布流,其一些自带属性使我们的瀑布流实现更加简单。 通过 ref 可以很方便的获取每列高度。通过比较算法算出高度最小列。
拿到高度最小列之后,将下个要插入的元素数据放到最小列的数据列表中,通过操作数据完成元素渲染。 通过watch监测元素渲染,判断是否继续进行渲染和请求更多元素数据。
每一列都定义一个ref,通过ref获取当前列的高度,然后比较高度取到最小高度,再通过最小高度算出其对应的列数。
瀑布流常用在无限下拉加载或者加载数据量很大,且包含很多图片元素的情景,所以通常不会一次性拿到所有数据,也不会一次性将拿到的数据全部渲染到页面上, 否则容易造成页面卡顿影响用户体验, 所以何时进行渲染、何时继续请求数据就很关键。
选择渲染的区域为滚动高度+可视区域高度的1.5倍,即可以防止用户滚动到底部的时候白屏,也可以防止渲染过多影响用户体验。如果:最小列的高度 - 滚 动高度 < 可视区域高*1.5 则继续渲染元素,否则不再继续渲染。
当已渲染的元素+可视区域可以展示的预估元素个数大于已请求到的个数的时候才去继续请求更多数据,防止请求浪费。如果:已加载的元素个数 + 一屏可以展示的元素预估个数 > 所有请求拿到的元素个数 则触发下一次请求去获取更多数据。
理论存在,实践开始
创建商品列表的基本 html 和 css,让 item 相对于 class 为 goods 的进行排序(相对布局)
![]()
{
{item.presentation}}¥{
{item.salePrice}}销量:{
{item.productNum}}
/** * 返回随机的图片高度 */imgHeight() { // 随机数 * (高度区间)+ 最低的图片高度 const result = Math.floor( Math.random() * (this.MAX_IMG_HEIGHT - this.MIN_IMG_HEIGHT) + this.MIN_IMG_HEIGHT, ); return result;},/** * 根据随机高度,生成图片样式数据 */initImgStyles() { this.goodsData.forEach((item) => { this.imgStyles.push({ height: `${ this.imgHeight()}px`, }); });},
将随机生成的样式添加到 img
标签上
瀑布流布局
获取到所有的 item 元素
遍历 item 元素,得到每一个 item 的高度,加上一个 margin 的高度
创建两个变量:
leftHeightTotal
,rightHeightTotal
,分别表示左右两侧目前距离顶部的高度,通过对比左右两侧距离顶部的高度,来确定 item 的放置位置。 3.1 如果左侧小于等于右侧高度的话,(
leftHeightTotal
<=rightHeightTotal
),那么 item 应该放置在左侧。此时 item 距离左侧为 0,距离顶部为当前的leftHeightTotal
3.2 否则,item 放置到右侧,此时 item 距离右侧为 0,距离顶部为当前的
rightHeightTotal
。
保存计算出的 item 的所有样式,配置到 item 上。
item 配置完成之后,对比左右两侧最大的高度,最大的高度为goods 组件的高度
initWaterfall() { const $goodsItems = this.$refs.goodsItem; if (!$goodsItems) return; let leftHeightTotal = 0; let rightHeightTotal = 0; // 遍历 item 元素,得到每一个 item 的高度,加上一个 margin 的高度 $goodsItems.forEach(($el, index) => { let goodsItemStyle = { }; const elHeight = $el.clientHeight + this.itemMarginBottomSize; // 如果左侧小于等于右侧高度的话 if (leftHeightTotal <= rightHeightTotal) { goodsItemStyle = { left: '0px', top: `${ leftHeightTotal}px`, }; // 更新距离顶部的高度 leftHeightTotal += elHeight; } else { goodsItemStyle = { right: '0px', top: `${ rightHeightTotal}px`, }; rightHeightTotal += elHeight; } // 保存计算出的 item 样式,动态添加到每一个 item 上面。 this.goodsItemStyles.push(goodsItemStyle); }); // 在不需要 goods 自滑动的时候,再去计算 goodsView 的高度 if (!this.isScroll) { this.goodsViewHeight = (leftHeightTotal > rightHeightTotal ? leftHeightTotal : rightHeightTotal) + 'px' }},
将生成的动态样式 goodsItemStyles
添加到外层的 div
上面。
可以将这个封装为一个组件,或者一个插件,数据由 props 传入。实现数据与布局解耦。
完整代码如下:
![]()
{
{ item.presentation}}¥{ { item.salePrice}}
销量:{ { item.productNum}}
你以为这就结束了?少年,你太天真了。。。
来来来,我们接着学
如何在同一组件中去展示不同的样式:
html 表示整个布局的结构,具体的展示样式,将由不同的 css 决定
每种展示样式对应不同的 css,也就是对应不同的类名(css)
1. 垂直列表 -> goods-list 2. 网格布局 -> goods-grid 3. 瀑布流布局 -> goods-waterfall实现不同的展示形式,本质就是实现不同的 css 样式。
props: { /** 通过传入的参数,来决定展示什么样的形式 * 1:列表布局 * 2:网格布局 * 3:瀑布流布局 */ layoutType: { type: String, default: '1', },},
通过判断不同的 layoutType 决定使用的 class 类
initLayout() { this.goodsViewHeight = '100%'; this.goodsItemStyles = []; this.imgStyles = []; switch (this.layoutType) { // 垂直列表 case '1': (this.layoutClass = 'goods-list'), (this.layoutItemClass = 'goods-list-item'); break; // 网格布局 case '2': (this.layoutClass = 'goods-grid'), (this.layoutItemClass = 'goods-grid-item'); break; // 瀑布流布局 case '3': (this.layoutClass = 'goods-waterfall'), (this.layoutItemClass = 'goods-waterfall-item'); this.initImgStyles(); this.$nextTick(() => { this.initWaterfall(); }); break; }},
为最外层组件添加动态类名
:class="[layoutClass, {'goods-scroll' : isScroll}]"
这样呢,我们的组件就大概完成了。把请求数据的方法放在父组件,实现解耦
![]()
{ { item.presentation}}
¥{ { item.salePrice}}
销量:{ { item.productNum}}
转载地址:http://gofmz.baihongyu.com/