虚拟列表即可视区域渲染
虚拟列表分为动态高度和固定高度
不考虑任何可变因素,实现一个最简单的例子,假定列表的li标签高度为30px,可视区域大小为300px,所以,我们在上下滚动时,可视元素为10个左右,只需要考虑如何渲染这10个元素
例子采用react实现,原生和vue同理
思路
- 虚拟列表的表现形式和普通列表一样,但是只渲染10个元素,我们就需要一个空的元素去撑起滚动条,给这个元素虚拟列表本应该有的高度,就可以模拟滚动条
- 虚拟列表可视区域渲染的个数时固定的,我们只需要知道当前滚动的scrollTop再除每一项的高度,就可以知道已经滚过去了多少项,拿到当前可视区域的起始序号,再总
list.slice(start,end)
即可
jsximport { useEffect, useRef, useState, Profiler } from "react"; import "./styles.css"; const total = 10000; const arr = Array.from({ length: total }).map((_, idx) => idx); export default function App() { const appRef = useRef(); const [start, setStart] = useState(0); const [scrollTop, setScrollTop] = useState(0); const [offset, setOffset] = useState(0); const [list, setList] = useState([]); const onScroll = (e) => { requestAnimationFrame(() => { setScrollTop(e.target.scrollTop); let start = Math.floor(e.target.scrollTop / 30); setStart(start); let end = start + 10; setList(arr.slice(start, end)); }); }; useEffect(() => { const container = appRef.current; container.addEventListener("scroll", onScroll); }, []); useEffect(() => { setOffset(start * 30); }, [start]); function callBack(...arg) { console.log(arg[2]); } return ( <div> {scrollTop} <Profiler id="vl" onRender={callBack}> <div ref={appRef} className="App"> <div className="mask" style={{ height: "300000px" }}></div> <ul style={{ transform: `translate3d(0, ${offset}px, 0)` }}> {list.map((item) => ( <li key={item}>{item}</li> ))} </ul> </div> </Profiler> </div> ); }
css.App { font-family: sans-serif; text-align: center; background-color: aqua; height: 300px; overflow-y: auto; position: relative; } .mask { position: absolute; top: 0; bottom: 0; left: 0; right: 0; } ul { transform: translate3d(0, 0, 0); } li { height: 30px; } li:nth-of-type(odd) { background-color: #f00; } li:nth-of-type(even) { background-color: #0f0; }