ahooks useVirtualList 封装虚拟滚动列表

目录
  • 简介
  • 实现原理
  • 具体实现
  • 思考总结

简介

提供虚拟化列表能力的 Hook,用于解决展示海量数据渲染时首屏渲染缓慢和滚动卡顿问题。

详情可见官网,文章源代码可以点击这里

实现原理

其实现原理监听外部容器的 scroll 事件以及其 size 发生变化的时候,触发计算逻辑算出内部容器的高度和 marginTop 值。

具体实现

其监听滚动逻辑如下:

// 当外部容器的 size 发生变化的时候,触发计算逻辑
useEffect(() => {
  if (!size?.width || !size?.height) {
    return;
  }
  // 重新计算逻辑
  calculateRange();
}, [size?.width, size?.height, list]);
// 监听外部容器的 scroll 事件
useEventListener(
  'scroll',
  e => {
    // 如果是直接跳转,则不需要重新计算
    if (scrollTriggerByScrollToFunc.current) {
      scrollTriggerByScrollToFunc.current = false;
      return;
    }
    e.preventDefault();
    // 计算
    calculateRange();
  },
  {
    // 外部容器
    target: containerTarget,
  },
);

其中 calculateRange 非常重要,它基本实现了虚拟滚动的主流程逻辑,其主要做了以下的事情:

  • 获取到整个内部容器的高度 totalHeight。
  • 根据外部容器的 scrollTop 算出已经“滚过”多少项,值为 offset。
  • 根据外部容器高度以及当前的开始索引,获取到外部容器能承载的个数 visibleCount。
  • 并根据 overscan(视区上、下额外展示的 DOM 节点数量)计算出开始索引(start)和(end)。
  • 根据开始索引获取到其距离最开始的距离(offsetTop)。
  • 最后根据 offsetTop 和 totalHeight 设置内部容器的高度和 marginTop 值。

变量很多,可以结合下图,会比较清晰理解:

代码如下:

// 计算范围,由哪个开始,哪个结束
const calculateRange = () => {
  // 获取外部和内部容器
  // 外部容器
  const container = getTargetElement(containerTarget);
  // 内部容器
  const wrapper = getTargetElement(wrapperTarget);
  if (container && wrapper) {
    const {
      // 滚动距离顶部的距离。设置或获取位于对象最顶端和窗口中可见内容的最顶端之间的距离
      scrollTop,
      // 内容可视区域的高度
      clientHeight,
    } = container;
    // 根据外部容器的 scrollTop 算出已经“滚过”多少项
    const offset = getOffset(scrollTop);
    // 可视区域的 DOM 个数
    const visibleCount = getVisibleCount(clientHeight, offset);
    // 开始的下标
    const start = Math.max(0, offset - overscan);
    // 结束的下标
    const end = Math.min(list.length, offset + visibleCount + overscan);
    // 获取上方高度
    const offsetTop = getDistanceTop(start);
    // 设置内部容器的高度,总的高度 - 上方高度
    // @ts-ignore
    wrapper.style.height = totalHeight - offsetTop + 'px';
    // margin top 为上方高度
    // @ts-ignore
    wrapper.style.marginTop = offsetTop + 'px';
    // 设置最后显示的 List
    setTargetList(
      list.slice(start, end).map((ele, index) => ({
        data: ele,
        index: index + start,
      })),
    );
  }
};

其它就是这个函数的辅助函数了,包括:

  • 根据外部容器以及内部每一项的高度,计算出可视区域内的数量:
// 根据外部容器以及内部每一项的高度,计算出可视区域内的数量
const getVisibleCount = (containerHeight: number, fromIndex: number) => {
  // 知道每一行的高度 - number 类型,则根据容器计算
  if (isNumber(itemHeightRef.current)) {
    return Math.ceil(containerHeight / itemHeightRef.current);
  }
  // 动态指定每个元素的高度情况
  let sum = 0;
  let endIndex = 0;
  for (let i = fromIndex; i < list.length; i++) {
    // 计算每一个 Item 的高度
    const height = itemHeightRef.current(i, list[i]);
    sum += height;
    endIndex = i;
    // 大于容器宽度的时候,停止
    if (sum >= containerHeight) {
      break;
    }
  }
  // 最后一个的下标减去开始一个的下标
  return endIndex - fromIndex;
};
  • 根据 scrollTop 计算上面有多少个 DOM 节点:
// 根据 scrollTop 计算上面有多少个 DOM 节点
const getOffset = (scrollTop: number) => {
  // 每一项固定高度
  if (isNumber(itemHeightRef.current)) {
    return Math.floor(scrollTop / itemHeightRef.current) + 1;
  }
  // 动态指定每个元素的高度情况
  let sum = 0;
  let offset = 0;
  // 从 0 开始
  for (let i = 0; i < list.length; i++) {
    const height = itemHeightRef.current(i, list[i]);
    sum += height;
    if (sum >= scrollTop) {
      offset = i;
      break;
    }
  }
  // 满足要求的最后一个 + 1
  return offset + 1;
};
  • 获取上部高度:
// 获取上部高度
const getDistanceTop = (index: number) => {
  // 每一项高度相同
  if (isNumber(itemHeightRef.current)) {
    const height = index * itemHeightRef.current;
    return height;
  }
  // 动态指定每个元素的高度情况,则 itemHeightRef.current 为函数
  const height = list
    .slice(0, index)
    // reduce 计算总和
    // @ts-ignore
    .reduce((sum, _, i) => sum + itemHeightRef.current(i, list[index]), 0);
  return height;
};
  • 计算总的高度:
// 计算总的高度
const totalHeight = useMemo(() => {
  // 每一项高度相同
  if (isNumber(itemHeightRef.current)) {
    return list.length * itemHeightRef.current;
  }
  // 动态指定每个元素的高度情况
  // @ts-ignore
  return list.reduce(
    (sum, _, index) => sum + itemHeightRef.current(index, list[index]),
    0,
  );
}, [list]);

最后暴露一个滚动到指定的 index 的函数,其主要是计算出该 index 距离顶部的高度 scrollTop,设置给外部容器。并触发 calculateRange 函数。

// 滚动到指定的 index
const scrollTo = (index: number) => {
  const container = getTargetElement(containerTarget);
  if (container) {
    scrollTriggerByScrollToFunc.current = true;
    // 滚动
    container.scrollTop = getDistanceTop(index);
    calculateRange();
  }
};

思考总结

对于高度相对比较确定的情况,我们做虚拟滚动还是相对简单的,但假如高度不确定呢?

或者换另外一个角度,当我们的滚动不是纵向的时候,而是横向,该如何处理呢?

以上就是ahooks useVirtualList 封装虚拟滚动列表的详细内容,更多关于ahooks useVirtualList封装的资料请关注我们其它相关文章!

(0)

相关推荐

  • ahooks解决React闭包问题方法示例

    引言 本文是深入浅出 ahooks 源码系列文章的第三篇,这个系列的目标主要有以下几点: 加深对 React hooks 的理解. 学习如何抽象自定义 hooks.构建属于自己的 React hooks 工具库. 培养阅读学习源码的习惯,工具库是一个对源码阅读不错的选择. 注:本系列对 ahooks 的源码解析是基于 v3.3.13.自己 folk 了一份源码,主要是对源码做了一些解读,可见 详情. 系列文章: 大家都能看得懂的源码 ahooks 整体架构篇 如何使用插件化机制优雅的封装你的请求

  • ahooks封装cookie localStorage sessionStorage方法

    目录 引言 cookie localStorage/sessionStorage 总结与归纳 引言 本文是深入浅出 ahooks 源码系列文章的第九篇,这个系列的目标主要有以下几点: 加深对 React hooks 的理解. 学习如何抽象自定义 hooks.构建属于自己的 React hooks 工具库. 培养阅读学习源码的习惯,工具库是一个对源码阅读不错的选择. 今天来看看 ahooks 是怎么封装 cookie/localStorage/sessionStorage 的. cookie ah

  • ahooks useRequest源码精读解析

    目录 前言 架构图 源码解析 Fetch onBefore onRequest onSuccess onFinally onError 其它 API 小结 plugins usePollingPlugin useRetryPlugin 小结 useRequest 对自定义 hook 的思考 总结 前言 自从 React v16.8 推出了 Hooks API,前端框架圈并开启了新的逻辑复用的时代,不再需要在意 HOC 的无限套娃导致性能差的问题,也解决了 mixin 的可阅读性差的问题.当然对于

  • ahooks useVirtualList 封装虚拟滚动列表

    目录 简介 实现原理 具体实现 思考总结 简介 提供虚拟化列表能力的 Hook,用于解决展示海量数据渲染时首屏渲染缓慢和滚动卡顿问题. 详情可见官网,文章源代码可以点击这里. 实现原理 其实现原理监听外部容器的 scroll 事件以及其 size 发生变化的时候,触发计算逻辑算出内部容器的高度和 marginTop 值. 具体实现 其监听滚动逻辑如下: // 当外部容器的 size 发生变化的时候,触发计算逻辑 useEffect(() => { if (!size?.width || !siz

  • vue轻松实现虚拟滚动的示例代码

    目录 前言 滚动原理 实现 源代码 参考 前言 移动端网页的日常开发中,偶尔会包含一些渲染长列表的场景.比如某旅游网站需要完全展示出全国的城市列表,再有将所有通讯录的姓名按照A,B,C...首字母依次排序展示. 长列表的数量一般在几百条范围内不会出现意外的效果,浏览器本身足以支撑.可一旦数量级达到上千,页面渲染过程会出现明显的卡顿.数量突破上万甚至十几万时,网页可能直接崩溃了. 为了解决长列表造成的渲染压力,业界出现了相应的应对技术,即长列表的虚拟滚动. 虚拟滚动的本质,不管页面如何滑动,HTM

  • 基于Vue3实现列表虚拟滚动效果

    目录 前言 完成效果 思路和需要解决的问题 vue3+setup 写的组件 使用组件 前言 近期在做一个网页播放器项目中,用到很多需要展示歌单的列表 一个歌单动辄千百首歌曲,页面中的元素太多导致热重载的时候 chrome 直接崩了 于是无限滚动列表提上日程 写的有点乱,也是第一次用 typescript 写项目,先记录一下 完成效果 思路和需要解决的问题 与懒加载不同,虚拟滚动需要一次性获取所有数据,但是只显示屏幕可见范围内的数据 要做到这些我需要知道: 一行的高度 屏幕范围内能显示的行数 列表

  • JS触摸屏网页版仿app弹窗型滚动列表选择器/日期选择器

    手机端网页版app在使用下拉列表时,传统的下拉列表使用起来体验非常不好,一般做的稍好一点的交互功能界面都不会直接使用下拉列表,所以app的原生下拉列表都是弹窗列表选择,网页型app从使用体验上来当然也应该做成那样,前段时间在开发网页版app时就遇到这种需求,不仅是日期选择器,数据列表.变量列表选择等等下拉列表型需求都需要,网上找来找去只找到一款比较好的mobiscroll,不过下载比较麻烦,感觉比较奇怪的是jquery.mobile.jeasyui.mobile都没有提供这种控件,不知道为什么?

  • vue虚拟滚动性能优化方式详解

    目录 引言 虚拟滚动(Virtual Scrolling) 理解虚拟滚动 虚拟 滚动 实现虚拟滚动 核心步骤 效果预览 最后 引言 一个简单的情景模拟(千万别被带入): A: 假设现在有 10 万条数据,你作为前端该怎么优化这种大数据的列表? B: 针对大数据列表一般不会依次性加载,会采用上拉加载.分页加载等方式实现优化. A: 那假如加载到最后一条数据的时候,页面上只是列表部分的数据就至少对应 10 万个 dom 节点,你觉得一个页面渲染至少 10 万个 dom 节点的性能如何? A: 如果这

  • vue.js el-table虚拟滚动完整实例代码

    目录 前言 实例代码 总结 前言 基于Element-UI的Table 组件开发的虚拟滚动组件,支持动态高度,解决数据量大时滚动卡顿的问题 实例代码 <template> <div ref="listWrap" style="height: 400px; overflow-y: scroll; margin-top: 20px; padding: 10px" @scroll="scrollListener" > <d

  • android使用flutter的ListView实现滚动列表的示例代码

    现如今打开一个 App,比如头条.微博,都会有长列表,随着我们不断地滑动,视窗内的内容也会不断地更新.今天就用 Flutter 实现一下这种效果. 这里的表现其实就相当于有一个固定长度的容器,然后超出的内容是不可见的,只有当你向上或向下滑动屏幕时,视窗外看不见的内容才会出现在视窗中.如果在 web 开发时,是需要容器加上样式 overflow: auto; 要想用 Flutter 实现,其实也是很简单的,因为 Flutter 为我们提供了 ListView 组件. ListView 主要有以下几

  • 详解Android 视频滚动列表(偷懒型)

    公司的项目需要一个视频的滚动列表. 搜了些文章比较常见的是根据列表项的可视百分比来判断的.实现起来略复杂. 这里想了一个在要求不高的情况下,实现相对简便的方法:根据列表滚动时可见的第一个列表项的位置来播放和暂停对应列表项内的视频. 它的效果大致是这样的: 以下是它的实现. 首先当然是建立列表. 这部分就直接用ListView吧,列表的具体的实现就不贴了.大致就是长这样的一个列表: 接下来就是添加播放器. 这里需要注意的是,在ListView里不能使用我们常用的那种VideoView.基于Surf

  • 微信小程序实现无限滚动列表

    本文实例为大家分享了微信小程序实现无限滚动列表的具体代码,供大家参考,具体内容如下 效果图1.0 实现方式是利用小程序原声组件swiper,方向设置为纵向 :vertical='true'设置同时显示的滑块数量:display-multiple-items='4'设置自动轮播:autoplay:'true'. 话不所说,直接上代码: <!-- 底部排名 --> <view class='contentBottom'> <view class='BottomFirst'>

  • Vue.js 无限滚动列表性能优化方案

    问题 大家都知道,Web 页面修改 DOM 是开销较大的操作,相比其他操作要慢很多.这是为什么呢?因为每次 DOM 修改,浏览器往往需要重新计算元素布局,再重新渲染.也就是所谓的重排(reflow)和重绘(repaint).尤其是在页面包含大量元素和复杂布局的情况下,性能会受到影响.那对用户有什么实际的影响呢? 一个常见的场景是大数据量的列表渲染.通常表现为可无限滚动的无序列表或者表格,当数据很多时,页面会出现明显的滚动卡顿,严重影响了用户体验.怎么解决呢? 解决方案 既然问题的根源是 DOM

随机推荐