找回密码
 会员注册
查看: 18|回复: 0

关于双列瀑布流布局的优化思考

[复制链接]

3

主题

0

回帖

10

积分

新手上路

积分
10
发表于 2024-9-20 21:45:45 | 显示全部楼层 |阅读模式
导语在前端领域,经常会遇到瀑布流布局的开发,最近整理了下相关的使用场景和解决方案,其中包含了简单算法DP,前端基础知识,业务场景的思考。  什么是瀑布流布局瀑布流又称瀑布流式布局,是一种比较流行的页面布局方式,英文名称为:MasonryLayouts 。与传统的分页显示不同,视觉上表现为参差不齐的多栏布局,最早由Pinterest首先运用。特别是在移动端,双列瀑布流的应用更加常见,在展现呈现每个元素能够以自身的情况合理占据空间,每个元素宽高不一致,左右依次调整排列,最终占据最小的屏幕高度,配合无限加载的设计,无论从用户使用心理的考虑、展示的美观,用户体验等方面考虑,瀑布流都是一种相当优秀的布局方式。以腾讯课堂APP的瀑布流为例:01使用场景根据瀑布流的优缺点,我们不难得出在什么情况下选择瀑布流是合理的选择:内容以为主的时候,瀑布流是更好的选择。占用空间比较大,并且大脑理解的速度相比理解文字要快,短时间内可以扫过的内容很多,所以如果用分页显示的话用户务必会频繁的翻页,影响沉浸式的体验,而瀑布流可以解决这个问题。信息与信息之间相对独立时,瀑布流是更好的选择。如果信息关联性强,用户务必会进行大量的回溯操作去查看之前或者之后的信息,相反,如果信息相对独立的话,可以使用瀑布流,让用户同时接受来自不同地方的信息。信息与信息之间相对独立时,瀑布流是更好的选择。瀑布流给人的直观印象,就是同时显示的信息与用户搜索的匹配度大致一样,而分页显示的直观印象则是越靠上的信息被认为与用户的搜索越匹配。因此,当信息与搜索匹配度没有明显区分度时,可以采用瀑布流。用户目的性不强的时候,瀑布流是更好的选择。如果用户有特定需要查找的信息,分页查找定位更方便,而当目的性较弱的时候,瀑布流可以增加用户停留的时间和意想不到的收获。这里引用了一篇文章的总结,瀑布流能够有效引导用户利用碎片化的时间,尽可能获得最大化的用户留存和使用时间。如何实现瀑布流布局结合前人的总结,目前实现瀑布流方式有 multi-column,grid,Flexbox 三种,实现方案各有不同,这里就不给大家具体说明了,各位不了解的请自行Google。从兼容性及易用性综合考虑,还是推荐使用Flexbox的布局方案。一般来说HTML结构如下:(以微信小程序为例)                           上面的代码中,container 代表瀑布流容器,负责滚动和触发无限加载;column-container 是列容器,item-card 是其中的每一项卡片。相应的CSS设置如下:.container {      display: flex;      flex-direction: row;      justify-content: space-between;      align-items: flex-start;      margin-top: 12px;      > .column-container {        flex: 1 1 0;        margin: 4px;        display: flex;        flex-direction: column;        justify-content: flex-start;        align-items: center;      }    }瀑布流容器的flex设置横向布局,列容器为纵向布局。对应的数据元组也分为下面这些,couponList 是总数据,left 是分配到左边的一列的数据,right 是分配右边一列的数据。具体优化分配方式是后续分析的重点,这里先按照下表进行分析。Page({  data: {    couponList: [],    left: [],    right: [],  },}直到这里,我们才真正进入本文的重点:怎么做一个高性能,高体验的H5双列瀑布流?这里我们先选定一个使用场景,技术实现上选用Flexbox实现布局,数据加载方面要求无限向下滚动加载,能够方便大家更加关注具体的业务背景,也降低作为作者介绍优化的范围,便于讲述。如果有其他场景,可以在留言区里大家一起讨论,在这里就不做大而全的讲述了。准确来说,在双列瀑布流的使用场景中,围绕元素卡片高度是否固定,顺序是否严格固定,可以分为元素高度分化场景、顺序分化场景,具体如下:元素高度分化场景:A1场景:每个元素高度固定;A2场景:每个元素高度不固定,但是可以数据类型估算自身相对于屏幕宽度的百分比高度;A3场景:元素高度不固定,且无法预估高度,只能等渲染之后才可以确定高度;顺序分化场景:(结合无限加载为前提)B1场景:元素的相对顺序严格一致B2场景:元素的相对顺序宽泛性一致下面我们就具体的场景来一一分析,并优化其中的实现细节。A1 场景之下,数据排列方式就可以简单做成左右交替排列,这也是最简单一种方式。let i = 0;while (i  {    // 创建对象    const img = new Image();    // 改变的src    img.src = img_url;    // 判断是否有缓存    if (img.complete) {      resolve({ width: img.width, height: img.height });    } else {      img.onload = () => {        resolve({ width: img.width, height: img.height });      };    }  })} 进阶优化01误差矫正在A2场景中,每个卡片的高度并不能像预想的高度去精确渲染,特别是在移动端H5中使用Rem单位、适配不同的设备类型的场景中,计算的精度差,渲染的像素误差,都会给计算左右高度差时带来误差一定的误差,在无限滚动的基础上,这种误差会持续累积,最终导致布局策略的失败。因此需要对左右高度差在每次加载数据后进行矫正。这里采用的方式比较简单,可以在左右列容器的尾部增加一个高度为0px的隐藏锚点元素,每次渲染结束后获取锚点元素的offsetTop的值,更新左右两侧的高度差。下面是HTML结构:                                     下面是小程序的更新差值的代码: this.setData({       left: [...left, ...leftData],      right: [...right, ...rightData],      diffValue: diffValue + nextDiff,    }, () => {      // 更新左右间距差      const query = wx.createSelectorQuery();      console.log('计算高度差');      query.select('#left-archer').boundingClientRect();      query.select('#right-archer').boundingClientRect();      const { diffValue } = this.data;      query.exec((res) => {        console.log(res[0].top - res[1].top, diffValue);        this.setData({          diffValue: res[0].top - res[1].top,        });    }); });leftData,rightData是新增的排列数据,diffValue是左右两列高度差值,事实证明diffValue和左右锚点的高度差值存在误差(如下图),需要通过这种手段矫正下。02通过DP算法获取最优排列在A2场景下,通过计算高度差向高度低的一列添加元素,实际并不是完美方案,因为在极端场景下,例如最后一个元素过高,会导致底部左右的高度差过大,甚至超过一个常见元素的高度,一方面没有合理使用屏幕高度,另外一方面巨大的高度差也会给用户体验带来负面影响。为了解决这种问题,我们引入简单的DP算法来解决这个问题。假如已知所有待排列元素的高度,就可以计算出这些元素的真实占据的高度-记为总高度H,假如不考虑卡片不可分割的特性,将两个列容器想想成联通的两个水柱,那么其元素总高度H/2就是其最佳占据高度,由于很难出现左右排列高度一致的情况,因此获取最靠近H/2的排列高度即为最佳排列高度,进而转换成背包问题就是在H/2容量的背包里,如何放置尽可能使用其空间体积的题目,下面就按照这个思路来解决如何获取最优的问题。 resetLayoutByDp: function (couponList) {    const { left, right, diffValue } = this.data;    const heights = couponList.map(item => (item.height / item.width * 160 + 77));    const bagVolume = Math.round(heights.reduce((sum, curr) => sum + curr, diffValue) / 2);    let dp = [];   //......省略部分代码   // 具体的DP算法可以自行查阅相关资料    const rightIndex = dp[heights.length - 1][bagVolume].indexes;    const nextDiff = heights.reduce((target, curr, index) => {      if (rightIndex.indexOf(index) === -1) {        target += heights[index];      } else {        target -= heights[index];      }      return target;    }, 0);    const rightData = rightIndex.map(item => couponList[item]);    const leftData = couponList.reduce((target, curr, index) => {      if (rightIndex.indexOf(index) === -1) {        target.push(couponList[index]);      }      return target;    }, []);    this.setData({       left: [...left, ...leftData],      right: [...right, ...rightData],      diffValue: diffValue + nextDiff,    }, () => {      // 更新左右间距差      const query = wx.createSelectorQuery();      console.log('计算高度差');      query.select('#left-archer').boundingClientRect();      query.select('#right-archer').boundingClientRect();      const { diffValue } = this.data;      query.exec((res) => {        console.log(res[0].top - res[1].top, diffValue);        this.setData({          diffValue: res[0].top - res[1].top,        });      });    });  }03优化列容器中的排列在实际业务场景中,常常会对排列顺序有要求,常见于广告和推荐的算法中,这里前端也可以做一些优化。这里的手段主要列容器内部的排序和不同列容器的相同元素的置换,尽可能保证高优先级的元素出现靠前的位置。最终的效果演示如下:已关注关注重播分享赞关闭观看更多更多腾讯IMWeb前端团队已关注分享点赞在看已同步到看一看写下你的评论分享视频,时长00:190/000:00/00:19切换到横屏模式继续播放进度条,百分之0播放00:00/00:1900:19倍速播放中0.5倍0.75倍1.0倍1.5倍2.0倍超清流畅您的浏览器不支持video标签继续观看关于双列瀑布流布局的优化思考观看更多转载,关于双列瀑布流布局的优化思考腾讯IMWeb前端团队已关注分享点赞在看已同步到看一看写下你的评论视频详情紧追技术前沿,深挖专业领域扫码关注我们吧!
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 会员注册

本版积分规则

QQ|手机版|心飞设计-版权所有:微度网络信息技术服务中心 ( 鲁ICP备17032091号-12 )|网站地图

GMT+8, 2024-12-27 15:12 , Processed in 1.047174 second(s), 26 queries .

Powered by Discuz! X3.5

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表