瀑布流布局

313 阅读5分钟

image.png

重要性
  • 是最基础知识点,但又不是很难,考察综合能力;且能够作为一个知识链路把其它知识点串起来。
什么是瀑布流布局
  • 从视图角度 观感上参差不齐的布局,区别于栅格化那种规规矩矩、等高等宽的布局。
  • 从开发角度 视图顺序与数据顺序不相关
  • 更通俗的解释 假设有一组照片,这些照片的宽度是固定的,但高度不等。我们要在墙上摆放这些图片,除第一行外,在摆放其它每 行图片时,总是摆放在上一行中高度最矮的那一列图片下面。
瀑布流布局的好处
  • 人的视觉都是等高的,所以会给用户观感上的预览体验。比如,在栅格化布局下,一眼看过去第二行有几张图片就会看到几张图片, 但瀑布流布局下,第二行中位于最矮图片下面那张图片也会被看到。
典型案例
  • 小红书的首页
实现核心:绝对布局+相对布局+动态计算
  • 绝对布局:每一张图片的位置都需要用绝对定位来做。
  • 相对布局:用到绝对布局,那么就一定要先找到绝对布局的爸爸。此处的核心是一定要设置父级相对布局盒子的宽度,如果不设置 那么宽度会自动填充成100vw,那么无法实现整个照片墙在水平方向居中了,所以要设置宽度。
  • 动态计算:动态计算每张图片绝对定位的位置。核心是高度数组。因为left的值好说,可以根据图片的index和宽度计算出来。 top的值需要通过维护一个高度数组来计算。每次找到这个数组中最小的那一项。根据它的位置计算当前图片的位置。计算完后要更新高度数组。
主要思路
  • 计算父级盒子的宽度 这个div的宽度是根据每一行能够摆放的图片数量*图片宽度来计算的。
  • 拆分第一行和其它行的逻辑
    • 第一行的逻辑:top都为0,只需要计算left的值即可。(老师的方法中第一行并没有设置绝对定位,而是用float:left实现 的,但是我不太理解这种,所以第一行也用了绝对定位来处理)
    • 其它行逻辑:每一张图片都要计算top和left的值。 如何区分第一行和其它行:根据视口宽度和图片宽度计算每一行能够放置的图片数量,就可以知道第一行摆放几个元素,然后算出这几个元素的位置。
根据以上得出主要步骤
  • 获取基础信息:父级.cont元素、.box元素、视窗宽度clientWidth
  • 写第一行处理方法
  • 写第二行处理方法
瀑布流布局的封装
  • 瀑布流布局因为会影响到所有,所以一般不是作为utils,而是作为core。
  • 把瀑布流布局封装成一个类,在类的constructor里面拿到一些基础信息(.cont的宽度、.box的宽度、clientWidth),在初始 化,在初始化方法里计算每一行能够放置的图片数量、设置父级盒子的宽度、调用第一行和第二行的处理方法。
使用瀑布流布局
1、在最图片列表外面放一个.cont,给每个图片包裹一层.box
2、实例化瀑布流布局类,调用类的初始化方法即可
其它get
  • 获取数组元素最小值时:先给数组从小到大排序
  • 虚拟列表:当列表很长很长远远超出视窗宽度时,可以对数据做一些截断,防止生成太多dom,以达到性能上的优化。(直接做分页处理不可以吗?)核心在于设置一个安全区,只要保证渲染安全区内的dom即可。
引发问题
  • 问题1:调用瀑布流类的初始化方法时,其实一次性把全部图片都加载进来了。这种是有性能损耗的。
    • 解决办法:通过图片懒加载进行优化。
  • 问题1引发问题2:快速滚动时频繁触发懒加载 解决办法:做防抖处理(涉及滚动、滑动、触摸操作时一般都要考虑防抖处理);(表单提交操作涉及节流处理)
    • 防抖处理debounce:快速滚动时不会引起重复的触发,核心在于设置等待时间。
    • 节流处理throttle:快速提交时不会造成重复的影响,核心在于设置时延。
  • 问题3:每次用到瀑布流时都新建一个实例,但实际参与计算的只有一个实例,所以新建多个实例是没必要的,会引起多实例性能消耗。
    • 解决办法:使用单例设计模式/每次使用完类都把它变成undefined(后者是在业务侧做兜底,但可能会有别的问题。不建议这样做)
    static getInstance() {
        if (!WaterFall._instance) {
            WaterFall._instance = new WaterFall();
        } 
        return WaterFall._instance;
    }
    调用类时:
    WaterFall.getInstance().init();
    
    ps:注意这里是静态方法:静态方法和静态属性直接挂载在类上面,不需要实例化就可以获取。
  • 问题3引发的问题4:如果实例中有数据需要更新 解决办法:实例复用,数据更新
    static update(args) {
        mount(...args);
    }
    static getInstance() {
        if (!WaterFall._instance) {
            WaterFall._instance = new WaterFall();
        }else {
            WaterFall.update(args)
        }
        return WaterFall._instance;
    }
    
性能优化
懒加载
实现思路
1、给原有的imgsrc设置一张通用图片,data-src中设置真正的图片。
2、当图片在视窗内时,用真正的图片替换到通用图片。
滚动的防抖处理
使用单例设计模式,避免多个实例的性能损耗
单例复用、数据更新