虚拟列表的渐进式实现,vue,react
2、react (下面是几个版本 样式在最后面)
/* * @Author: your name * @Date: 2020-03-13 11:11:23 * @LastEditTime: 2020-05-13 16:39:01 * @LastEditors: Please set LastEditors * @Description: 列表项等宽 * @FilePath: /optimus/src/pages/test/Index/hooks.js */ import React, { useState, useEffect, useRef } from "react"; import styles from "./style.less"; const arr = []; for (let index = 0; index < 200; index++) { arr.push({ index, height: parseInt(Math.random() * 30 + 20) }); } const Index = (props) => { const [height, setHeight] = useState(0); const [itemHeight] = useState(30); const [totalList] = useState(arr); const [list, setList] = useState([]); const couterRef = useRef(); const totalRef = useRef(); useEffect(() => { setHeight(totalList.length * itemHeight); }, [totalList]); useEffect(() => { updateVisibleData(); }, [totalList]); const onScrollCapture = (view) => { const scrollTop = totalRef.current.scrollTop; updateVisibleData(scrollTop); }; const updateVisibleData = (scrollTop) => { scrollTop = scrollTop || 0; const clientHeight = totalRef.current.clientHeight; const visibleCount = Math.ceil(clientHeight / itemHeight); const start = Math.floor(scrollTop/ itemHeight); const end = start + visibleCount; const _list = totalList.slice(start, end); setList(_list); couterRef.current.style.webkitTransform = `translate3d(0, ${start * itemHeight}px, 0)`; }; return ( <div className={styles.wrap}> <div className={styles.listView} ref={totalRef} onScrollCapture={onScrollCapture} > <div className={styles.listViewPhantom} style={{ height: height }} ></div> <div className={styles.listViewContent} ref={couterRef}> {list.map((item, index) => { return ( <div className={styles.listViewItem} style={{ height: itemHeight }} key={index} > {item.index} </div> ); })} </div> </div> </div> ); }; export default Index;
/* * @Author: x * @Date: 2020-03-13 11:11:23 * @LastEditTime: 2020-05-13 16:34:47 * @LastEditors: Please set LastEditors * @Description: react 列表项不等宽 * @FilePath: /optimus/src/pages/test/Index/hooks.js */ import React, { useState, useEffect, useRef } from "react"; import styles from "./style.less"; const arr = []; for (let index = 0; index < 200; index++) { arr.push({ index, height: parseInt(Math.random() * 30 + 20) }); } const Index = (props) => { const [height, setHeight] = useState(0); const [totalList, setTotalList] = useState(arr); const [list, setList] = useState([]); const couterRef = useRef(); const totalRef = useRef(); useEffect(() => { let total = 0; let index = 0; let length = totalList.length; for (index; index < length; index++) { total += totalList[index].height; } setHeight(total); }, [totalList]); useEffect(() => { updateVisibleData(); }, [totalList]); const onScrollCapture = (view) => { const scrollTop = totalRef.current.scrollTop; updateVisibleData(scrollTop); }; const updateVisibleData = (scrollTop) => { scrollTop = scrollTop || 0; const start = findNearestItemIndex(scrollTop); const end = findNearestItemIndex(scrollTop + totalRef.current.clientHeight); const _list = totalList.slice(start, Math.min(end + 1, totalList.length)); setList(_list); couterRef.current.style.webkitTransform = `translate3d(0, ${getItemSizeAndOffset(start).offset}px, 0)`; }; const findNearestItemIndex = scrollTop => { let total = 0; for (let i = 0, j = totalList.length; i < j; i++) { const size = totalList[i].height; total += size; if (total >= scrollTop || i === j -1) { return i; } } return 0; } const getItemSizeAndOffset = start => { let total = 0; for (let i = 0, j = Math.min(start, totalList.length - 1); i <= j; i++) { const size = totalList[i].height; if (i === j) { return { offset: total, size }; } total += size; } return { offset: 0, size: 0 }; } return ( <div className={styles.wrap}> <div className={styles.listView} ref={totalRef} onScrollCapture={onScrollCapture} > <div className={styles.listViewPhantom} style={{ height: height }} ></div> <div className={styles.listViewContent} ref={couterRef}> {list.map((item, index) => { return ( <div className={styles.listViewItem} style={{ height: item.height }} key={index} > {item.index} </div> ); })} </div> </div> </div> ); }; export default Index;
.wrap{ width: 100%; height: 800px; display: flex; justify-content: center; align-items: center; background-color: antiquewhite; } .listView { height: 400px; width: 160px; overflow: auto; position: relative; border: 1px solid #aaa; } .listViewPhantom { position: absolute; left: 0; top: 0; right: 0; z-index: 100; width: 160px; background-color: rgba(red,0.5); } .listViewContent { left: 0; right: 0; top: 0; position: absolute; } .listViewItem { padding: 5px; color: #666; box-sizing: border-box; border-bottom: 1px solid red; display: flex; align-items: center; }
写在最后
题目当然还可以再优化:
对itemHeight的缓存;
对contextHeight的高度计算;
对缓存结果的算法查询;
对未缓存结果的算法查询;
根据渲染结果动态更新列表项的高度;
数据源更新时尽量范围小的删除失效缓存;
。。。
优化之路永无尽头;
- 上一篇 »优雅的渐进式PHP采集框架
- 下一篇 »Vue基础