前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >虚拟列表与 Scroll Restoration

虚拟列表与 Scroll Restoration

作者头像
Innei
发布2021-12-28 10:52:24
8590
发布2021-12-28 10:52:24
举报
文章被收录于专栏:静之森

虚拟列表是为了提高页面性能而出现的。我们知道,一个页面上的 DOM 树越复杂,节点越多性能越低,每次重排(reflow)的成本越高。于是,虚拟列表出现了。虚拟列表的原理是只渲染可视部分以及部分预渲染的节点,待滚动之后替换可视部分节点。余下的空间则用 padding-top padding-bottom 撑开。

渲染 50 个 Node,实际只渲染了可见部分的 Node
渲染 50 个 Node,实际只渲染了可见部分的 Node

渲染 50 个 Node,实际只渲染了可见部分的 Node

本篇文章不讨论如何实现一个虚拟列表,此类文章网上有很多。但是有关于回退页面无法回到虚拟列表上一次的位置的文章却很少。默认情况下,在后退页面时,浏览器会自动回到上一次浏览的位置。(如果设置 history.scrollRestoration = 'auto',默认为 auto

https://cdn.jsdelivr.net/gh/Innei/img-bed@master/1618734535980.gif
https://cdn.jsdelivr.net/gh/Innei/img-bed@master/1618734535980.gif

但是如果用了虚拟列表,这里的虚拟列表跟随 document 根节点(document.documentElement)滚动,即使开启了 Restoration,回退页面后仍然无法回到上一次的位置。这是因为虚拟列表需要计算得出整个容器的高度,在计算之前容器没有高度,浏览器就不能回到之前的滚动高度了,因为高度不存在。

react-virtuoso
react-virtuoso

react-virtuoso

一种方式是,记录之前虚拟列表容器的高度,在回退回来之后先用之前记录的值去撑开整个容器高度,待虚拟列表加载后去除。这样有个问题是虚拟列表无法知道当前的位置原来是什么内容,因为虚拟列表都是按照单个 Node 高度去计算的,整体高度是一个预估值,不能知道当前位置具体是什么。

对于 react-virtuoso 这个库,没有直接暴露给我们每个 Node 计算后的高度,也没有一个自身的 State 想要缓存状态不太现实。一个不好的解决方案是用提供的接口在每次滚动后记录一个 Range,Range 是一个当前渲染内容的索引,在之后的渲染后可以用自身的 scrollTo 方法跳转。这样有个坏处是会出现跳动,原先在顶部直接跳动到了原先的位置,还是个预估值。既不准确也不符合 UX 逻辑。

之后,我又找到一个比较小众的库,

virtual-scroller

,不仅仅可以在 React 使用,他独立封装了一个 Core,可以单独在各个框架中使用,即使在 VanillaJS 中使用,小众的库功能肯定不会很多,但是基本的功能也都有,也可以 fork 一份出来进行修改和扩展。选择此库的原因是他暴露了自身的 State,可以缓存每个 State 在之后的渲染中使用。该库没有文档,没有 type definition,通过翻看源码我们可以知道,可以在 Router Change 之前获取到该组件的 Ref,记录下该组件的 State,在后面的渲染中注入 initialState。

https://cdn.jsdelivr.net/gh/Innei/img-bed@master/1618738325891.gif
https://cdn.jsdelivr.net/gh/Innei/img-bed@master/1618738325891.gif

jsx

代码语言:javascript
复制
1import Router from 'next/router'
2import { useEffect, useMemo, useRef } from 'react'
3import VirtualScroller from 'virtual-scroller/react'
4const cacheState = {}
5const cachePrevTop = {}
6if ('window' in globalThis) {
7  window.debug = {
8    cacheState,
9    cachePrevTop,
10  }
11}
12export default function Test() {
13  const cacheKey = useMemo(
14    () => ('window' in globalThis ? location.pathname : ''),
15    [],
16  )
17
18  const ref = useRef()
19  useEffect(() => {
20    // history.scrollRestoration = 'manual'
21    const $scroll = document.scrollingElement
22
23    requestAnimationFrame(() => {
24      $scroll.scrollTop = cachePrevTop[cacheKey]
25      // console.log('top')
26    })
27
28    const handler = () => {
29      cachePrevTop[cacheKey] = $scroll.scrollTop
30      if (ref.current) {
31        cacheState[cacheKey] = { ...ref.current.state }
32      }
33    }
34    Router.events.on('routeChangeStart', handler)
35    return () => {
36      Router.events.off('routeChangeStart', handler)
37    }
38  }, [])
39  return (
40    <div className="">
41      <VirtualScroller
42        ref={ref}
43        items={Array.from({ length: 50 }, (_, i) => i)}
44        itemComponent={Item}
45        initialState={cacheState[cacheKey]}
46      />
47    </div>
48  )
49}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-04-18,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档