瀑布流+虚拟列表

发布于:2024-05-07 ⋅ 阅读:(22) ⋅ 点赞:(0)
waterfall.png

之前写过一篇文章 好像大家对这种超级简单的实现方案不太认可;那我就只能来手复杂度和性能拉满的操作,没错!就是熟知的虚拟列表+传统式定位排列节点。

需要js版的直接右键查看源代码即可

实现思路

1. 数据来源必要字段

图片的宽高是必要的吗?答案是,因为是对整个页面的节点尺寸进行精确计算,所以宽高的信息必不可少;其次就是宽度在load(next)方法传入中时,确保这个时候所有的宽度都是想同的,因为内部不做比例计算,而是只做了对高度的比例计算;在假数据中我把宽度固定为308了,所以传入前就不需要处理。

2. 瀑布流数据组装方案

动态列的计算方式和我之前那篇文章一致,给定一个宽度与列数的配置数组,然后监听元素的尺寸的变化进行计算即可;和之前写过的实现方案不同的是:这里数据渲染的列数只有一个,那么怎么去标记每个数据项是属于第几列呢?这里还是参考原来实现的思路,只不过是将列数据columnData和渲染节点的数据domDataList分开,通过每次添加时,循环去拿到高度最低的那个列的索引columnData[0].height,最后设置进渲染列表即可。

3. 滚动监听

核心功能!!!在滚动时,需要遍历指定范围(视口中)的domDataList,如果超出的节点就将它们移除,这样就达到了页面元素永远只有视口范围的可视数量;至于为什么不全部遍历,是因为在上千条数据时,遍历的性能会明显下降,所以在计算视口范围的条数时,尤其重要。

4. 程序设计

  • 这里我将封装成一个函数,可在任意场景下调用;
  • 不同的布局场景下,修改的地方有setElement()updateDomDataList()这两处函数,因为节点布局的不同,所以没办法写成通用的操作;
  • 文本的计算同理,具体看textInfo的实现,计算动态文本的操作不写进waterfallVirtual()方法里面的原因是有些文字节点高度是固定的,所以这里分开处理;

函数的传参在类型声明文件中已经写得很明细了,这里就不做补充,具体看代码调用部分

/** 瀑布流类型 */
namespace WaterfallVirtual {
  /** 函数传参 */
  export interface Params {
    /** 瀑布流列表节点 */
    el: HTMLElement | string
    /** 指定监听滚动的节点,不传则默认监听`window`的滚动操作 */
    scrollEl?: HTMLElement
    /** 容器之间的间距,默认`10` */
    gap?: number
    /** 是否关闭实时监听元素变动并更新布局,关闭时,由开发者自行决定调用`mutation`来进行更新 */
    notMutation?: boolean
    /** 容器宽度与列数配置 */
    columns: Array<Column>
    /** 加载数据回调,返回一个调用函数`next` */
    load: (
      /** 
       * 只有调用该函数才会执行输出节点操作
       * @param list 由外部添加的列表数据
       */
      next: (list: Array<Row>) => void
    ) => void
    /** 元素挂载钩子函数 */
    mounted?: (el: HTMLElement) => void
  }
  interface Column {
    /** 最小的匹配宽度 */
    minWidth: number
    /** `column`的最小值为`2`,小于`2`的将被过滤掉 */
    column: number
  }
  /** 外部传入的基础数据类型 */
  export interface Row {
    id: number
    /** 图片地址 */
    url: string
    /** 文本信息 */
    content: string
    /** 图片的宽度 */
    width: number
    /** 图片的高度 */
    height: number
  }
  /** 节点布局相关数据 */
  export interface DomData {
    index: number
    columnIndex: number
    width: number
    /**
     * 节点的高度
     * - 注意需要在`updateDomDataList`方法中,根据布局动态计算 
     */
    height: number
    imgHeight: number
    left: number
    top: number
    content: string
    url: string
  }
}

function waterfallVirtual(params: WaterfallVirtual.Params) {}

调用片段

const waterfall = waterfallVirtual({
  el: ".layout .list",
  gap: 12,
  columns: [
    { minWidth: 1600, column: 5 },
    { minWidth: 1200, column: 4 },
    { minWidth: 780, column: 3 },
    { minWidth: 500, column: 2 },
  ],
  async load(next) {
    if (!state.hasMore) {
      const isConfirm = confirm("当前数据已全部加载完成,是否重新开始?");
      if (isConfirm) {
        waterfall.reset();
        onReset();
      }
    }
    if (state.loading) return;
    state.loading = true;
    const res = await getPicList(state.pageInfo);
    state.loading = false;
    state.pageInfo.page++;
    state.hasMore = state.pageInfo.page * state.pageInfo.size < res.data.total;
    next(res.data.list);
  },
  mounted(el) {
    // console.log("元素挂载 >>", el, data);
    el.addEventListener("click", function() {
      const info = {
        index: el.dataset["index"],
        column: el.dataset["column"]
      }
      console.log(el.id, totalData[info.index!]);
    });
  }
});

完整代码