IntersectionObserver实现小程序长列表优化

IntersectionObserver实现小程序长列表优化

关于 IntersectionObserver

思路

这里以一屏数据为单位【一个分页的10条数据,最好大于视口高度】,

监听每一屏数据和视口的相交比例,即用户能不能看到它

只将可视范围的数据渲染到页面上,其余的使用空白高度占位符代替,

可视范围可扩大到当前可视范围的上下两倍到三倍,减少滚动时留白现象

实现

大多数都是监听 scroll,结合防抖函数,来控制数据的渲染

这里我们使用小程序自带api,IntersectionObserver 来实现

小程序 IntersectionObserver

通过 IntersectionObserver 来监听可视范围内的元素,当元素进入可视范围时,将对应的数据渲染到页面上。

其返回的 intersectionRatio 为相交比例,大于0 说明进入可视范围,渲染真实数据,否则,渲染高度占位符

第一步

首先需要两个数组,一个用来存放真实数据,一个用来存放高度占位符数据

将原来的 storeList,改为二维数组,以一屏为单位进行分割

javascript 复制代码
// Taro

// wholePageHeight和wholeStoreList不用放在state中,页面销毁时记得释放

// 高度占位符数据
let wholePageHeight = [];
// 真实数据
let wholeStoreList = [];

state = {
    // 当前页码
    pageCurrent: 0,
    // 渲染的数据
    storeList: [],
    // 高度占位符数据
    storeListPlaceholder: [],
    // ...
}

第二步

获取界面上的节点信息

保存所有的列表数据以及页面高度数据

每次数据请求完成,将列表数据push到 wholeStoreList 中,

然后将当前这一屏的数据放到对应页面的 pageCurrentstoreList 中,
setState 后, 计算这一屏的高度,然后将其存放到 wholePageHeight

javascript 复制代码
// Taro

// 分页获取列表数据
getList = async () => {
    // 接口请求数据
    const { pageCurrent } = this.state;
    const records = await api.getList({
        pageCurrent
    });
    // 分页累加
    pageCurrent++

    // 保存所有的列表数据
    wholeStoreList.push(records);
    // 设置当前这一屏的数据
    storeList[pageCurrent] = records;

    this.setState({
        storeList,
        pageCurrent,
    }, () => {
        // 数据setState后,获取当前页的高度
        this.getPageHeight();
    });
}

// 计算当前页的高度
getPageHeight = () => {
    const {
        pageCurrent,
    } = this.state;
    // 返回一个 SelectorQuery 对象实例
    const query = Taro.createSelectorQuery();
    // 查询节点信息的对象
    query
        // 在当前页面下选择第一个匹配选择器 selector 的节点。返回一个 NodesRef 对象实例,可以用于获取节点信息。
        // 类似于 CSS 的选择器
        .select(`#storePage${pageCurrent}`)
        // 添加节点的布局位置的查询请求,SelectorQuery.exec 方法后,节点信息会在 callback 中返回
        .boundingClientRect()
        // 执行所有的请求。请求结果按请求次序构成数组,在callback的第一个参数中返回。
        .exec(res => {
        if (res && res[0].height) {
            /**
             *   rect.id      // 节点的ID
             *   rect.dataset // 节点的dataset
             *   rect.left    // 节点的左边界坐标
             *   rect.right   // 节点的右边界坐标
             *   rect.top     // 节点的上边界坐标
             *   rect.bottom  // 节点的下边界坐标
             *   rect.width   // 节点的宽度
             *   rect.height  // 节点的高度
             */
            // 保存当前页的高度
            wholePageHeight.push(res[0].height);
            
            // 监听当前页的节点
            this.observePage(pageCurrent);
        }
    });
}

第三步

针对每一屏都去添加监听,判断是否需要渲染真实数据,还是高度占位符

这里通过 IntersectionObserver 来监听,当元素进入可视范围时,将对应的数据渲染到页面上。

:::tip 小细节

如果只控制一屏的显示,那么当用户快速滑动上下屏时,

会出现一屏的数据还没渲染完,就已经滚动到下一屏了,导致白屏出现

所以:需要设置 relativeToViewport top 和 bottom 参数,顶部和底部的边界,进入上下三个屏幕高度就开始渲染

:::

javascript 复制代码
// Taro

observePage = () => {
    const {
        storeList,
    } = this.state;

    const windowHeight = Taro.systemInfo.windowHeight;

    // WXML节点布局相交状态
    // 创建 IntersectionObserver 对象,用于推断某些节点是否可以被用户看见、有多大比例可以被用户看见。
    const observerObj = Taro.createIntersectionObserver(this)
        // 指定页面显示区域作为参照区域之一
        .relativeToViewport({
            // 设置顶部和底部的边界,进入上下三个屏幕高度就开始渲染
            top: 3 * windowHeight, 
            bottom: 3 * windowHeight
        });
    
    // 返回IntersectionObserver对象实例,用于推断某些节点是否可以被用户看见、有多大比例可以被用户看见
    observerObj.observe(`#storePage${pageIndex}`, (res) => {
        /**
         *   res.id // 目标节点 id
         *   res.dataset // 目标节点 dataset
         *   res.intersectionRatio // 相交区域占目标节点的布局区域的比例
         *   res.intersectionRect // 相交区域
         *   res.intersectionRect.left // 相交区域的左边界坐标
         *   res.intersectionRect.top // 相交区域的上边界坐标
         *   res.intersectionRect.width // 相交区域的宽度
         *   res.intersectionRect.height // 相交区域的高度
         */
        // < 0,未相交,使用高度占位符
        if (res.intersectionRatio <= 0) {
            storeList[pageIndex] = {
                height: wholePageHeight[pageIndex]
            }
            // > 0,相交,使用真实数据
        } else {
            storeList[pageIndex] = wholeStoreList[pageIndex];
        }
        this.setState({
            storeList
        });
    });
}

第四步

在页面上渲染数据,如果当前这一屏有数据,就渲染真实数据,否则渲染高度高度占位符

javascript 复制代码
// Taro

// 列表渲染
renderStoreList = () => {
    const {
        storeList,
    } = this.state;
    return (
        storeList.map((storePage, index) => (
            <View className='store-page' key={index} id={'storePage' + index}>
                {
                    // 是否存在当前页的数据
                    storePage && storePage.length ?
                        <View className='store-list'>
                            {
                                storePage.map((storeItem) => (<StoreInfo item={storeItem} key={storeItem.id} />))
                            }
                        </View>
                        :
                        // 占位组件 ---
                        <PlaceWrap customStyle={{height: storePage.height + 'px'}} />
                }
            </View>
        ))
    )
};

到此,一个长列表的优化就OK了

IntersectionObserver

看了下 caniuse ,发现除了IE,兼容性也还行,那web端也可以尝试下的

相关推荐
崔庆才丨静觅4 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60615 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了5 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅5 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅6 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅6 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment6 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅6 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊6 小时前
jwt介绍
前端
爱敲代码的小鱼6 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax