虚拟列表动态ant

123 阅读4分钟
import { Space, Table, Tag } from 'antd';
import React, { useEffect,useState } from 'react';
import './index.css';
const columns = [
     {
    title: 'id',
    dataIndex: 'id',
    key: 'id',
    // render: (text) => <a>{text}</a>,
  },
  {
    title: 'Name',
    dataIndex: 'name',
    key: 'name',
    render: (text) => <a>{text}</a>,
  },
  {
    title: 'Age',
    dataIndex: 'age',
    key: 'age',
  },
  {
    title: 'Address',
    dataIndex: 'address',
    key: 'address',
  },
//   {
//     title: 'Tags',
//     key: 'tags',
//     dataIndex: 'tags',
//     render: (_, { tags }) => (
//       <>
//         {tags.map((tag) => {
//           let color = tag.length > 5 ? 'geekblue' : 'green';
//           if (tag === 'loser') {
//             color = 'volcano';
//           }
//           return (
//             <Tag color={color} key={tag}>
//               {tag.toUpperCase()}
//             </Tag>
//           );
//         })}
//       </>
//     ),
//   },
//   {
//     title: 'Action',
//     key: 'action',
//     render: (_, record) => (
//       <Space size="middle">
//         <a>Invite {record.name}</a>
//         <a>Delete</a>
//       </Space>
//     ),
//   },
];
let datas = []
for(let i=0;i<10000;i++){
    datas.push({name:''+i,id:i, key:'' + i})
    i%2===0 && (datas[i].name = 'llllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll')
    i%3===0 && (datas[i].name = 'lllllllllllllllllllllllllllllllllllllkkkkkkkkklmmcsllllllllllllllllllllllllllllllllhddddddddddddddddddddddddddddddddddddddlllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll')
    i===9998 && (datas[i].name = 'lllllllllllllllllllllllllllllllllllllkkkkkkkkklmmcsllllllllllllllllllllllllllllllllhddddddddddddddddddddddddddddddddddddddlllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll看到基督教基督教的亟待解决的就打架大家基督教基督教的就打架大家巨大的积极的角度亟待解决的大家假的假的假的假的坚决打击就打架大家觉得就打架大家假的假的假的假的的男男女女你就大胆的当年年底iiiiiiiiiiiiiiiiiiiii哦顶顶顶顶顶顶是看可是可是可是姐姐大家都送哦时间参加就参加')
    i === 9999 && (datas[i].name = 'lllllllllllllllllllllllllllllllllllllkkkkkkkkklmmcsllllllllllllllllllllllllllllllllhddddddddddddddddddddddddddddddddddddddlllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllllll看到基督教基督教的亟待解决的就打架大家基督教基督教的就打架大家巨大的积极的角度亟待解决的大家假的假的假的假的坚决打击就打架大家觉得就打架大家假的假的假的假的的男男女女你就大胆的当年年底iiiiiiiiiiiiiiiiiiiii哦顶顶顶顶顶顶是看可是可是可是姐姐大家都送哦时间参加就参加')
}
let lineHeight = 55
let cacheArr = datas.map((item,index) => {
          return {
            index,
            start:index*lineHeight,
            end:(index+1)*lineHeight,
            height:lineHeight
          }
       })

const TableHeight = () => {
    let allHeight = 500
    let lineHeight = 55
    let pageCount = Math.round(allHeight / lineHeight)
    const [tableData, setTableData] = useState(datas.slice(0, pageCount))
    const [listAllHeight, setListAllHeight] = useState(datas.length - 1 + 'px')
    const [isAllHeight, setIsAllHeight] = useState(false)
    const [allHeightIndex,setAllHeightIndex] = useState(datas.length - 1)
    // const [stopScrollIndex, setStopScrollIndex] = useState(datas.length - 1)
    let  stopScrollIndex = datas.length - 1
    // setTableData(datas.slice(0, pageCount))
    let handleTableScroll = () => {
        let dom = document.querySelector('.ant-table-body')
        let div = document.createElement("div");  //创建段落元素
        div.style.height= listAllHeight
        div.style.position = 'absolute'
        div.style.width = '1px'
        div.className = 'myDiv'
        dom.insertBefore(div, dom.children[0]);
        updatePosition(0)
        dom.addEventListener('scroll', () => {
            // 滚动距离
            let scrollTop = dom.scrollTop
            //获取当前滚动距离对应的第一个元素
            let startIndex = findItemIndex(scrollTop)
            //当前滚动条已经到最底端了,就不需要再执行更新操作了
            if(startIndex >stopScrollIndex){
                return
            }
              // 预估可见数量
            const viewCount = Math.ceil(allHeight / lineHeight);
            let endIndex = startIndex + viewCount
            setTableData(datas.slice(startIndex, endIndex))

            //获取真实高度并缓存
            updatePosition(startIndex,scrollTop)
        })
    }
    let findItemIndex = (scrollTop) => {
        let low = 0; 
        let high = cacheArr.length - 1;
        while(low <= high) {
            const mid = Math.floor((low + high) / 2);
            const { start, end } = cacheArr[mid];
            if (scrollTop >= start && scrollTop <= end) {
                high = mid;
                break;
            } else if (scrollTop > end) {
                low = mid + 1;
            } else if (scrollTop < start) {
                high = mid - 1;
            }
        }
        // console.log('high',high,'stopScrollIndex',this.stopScrollIndex)
        //滚动到最底部时,不需要再计算了
        // high = high>=this.stopScrollIndex?this.stopScrollIndex:high
        return high;
    }
    let getListAllHeight = ()=> {
         return (cacheArr && cacheArr.length > 0
        ? cacheArr[cacheArr.length - 1].end
        : 0) + 'px'
        // return this.cacheArr[this.cacheArr.length-1].end
    }
    const updatePosition = (startIndex,scrollTop)=> {
        let trNodes = [...document.querySelectorAll('tr')].slice(2)
          let itemIndex = startIndex
          let endIndex = itemIndex+2
          //计算滚动时,前一个元素留下来的部分(没完全滚动上去)
          let startRest = cacheArr[startIndex].height - scrollTop%cacheArr[startIndex].height
          for(let i=0;i<trNodes.length;i++){
              let node = trNodes[i]
              let newHeight = node.getBoundingClientRect().height;
              console.log(itemIndex)
              let oldHeight = cacheArr[itemIndex].height
                const dValue = Math.abs(newHeight - oldHeight)
                if (dValue) {
                    cacheArr[itemIndex].height = newHeight
                    // 当前节点与底部的距离 = 上一个节点与底部的距离 + 当前节点的高度
                    cacheArr[itemIndex].end = itemIndex > 0 ? cacheArr[itemIndex - 1].end + newHeight : cacheArr[itemIndex].height;
                    // 当前节点与顶部的距离 = 上一个节点与底部的距离
                    cacheArr[itemIndex].start = itemIndex > 0 ? cacheArr[itemIndex - 1].end : 0;
                    // 更改一个节点就需要更改之后所有的值,不然会造成空白
                    for (let j = itemIndex + 1, len = cacheArr.length; j < len; j++) {
                        cacheArr[j].start = cacheArr[j - 1].end;
                        cacheArr[j].end += dValue;
                    }
                }
                //计算可视区内元素
                endIndex = itemIndex+2
                let curHeight = 0
                curHeight =  startIndex && itemIndex>startIndex? startRest + cacheArr[itemIndex].end-cacheArr[startIndex+1].end : startRest
                if(curHeight>500){
                    // this.tableData = this.datas.slice(startIndex, endIndex);
                    setTableData(datas.slice(startIndex, endIndex))
                    //计算所有元素是否已全部获取完真实高度(首次可视区内最后一个元素为总元素的最后一个)
                    !isAllHeight && itemIndex === cacheArr.length-1 && setAllHeightIndex(startIndex)&& setIsAllHeight(true)
                    break;
              }
              if (itemIndex >= datas.length-1) {
                  break;
              }
                itemIndex++
          }
          itemIndex = itemIndex>cacheArr.length-1?cacheArr.length-1:itemIndex
          //全部为真实节点,this.listAllHeight就不再更新了
          if(startIndex<=allHeightIndex){
              setListAllHeight(getListAllHeight())
              console.log(listAllHeight,getListAllHeight())
             document.querySelector('.myDiv').style.height = getListAllHeight()
          }
          if(scrollTop !== undefined){
            // startIndex = startIndex>this.cacheArr.length-1?this.cacheArr.length-1:startIndex
            // 计算滚动条是否滚到最底部
            let isScrollHeight = startIndex<cacheArr.length-1?(startRest + cacheArr[itemIndex].end-cacheArr[startIndex+1].end):cacheArr[startIndex].height//(可视区元素总高度)
            let isScroll = isScrollHeight>500
            //滚动条滚到最底部时(首次可视区元素总高度小于可视区高度并且可视区内最后一个元素为总元素的最后一个)
            if(!isScroll && itemIndex===cacheArr.length-1){
                // stopScrollIndex = startIndex
                // setStopScrollIndex(startIndex)
                stopScrollIndex = startIndex
            }else{
                //不同滚动方式,滚动条滚到最底部时对应的元素不同,所以没有到达最底部时将 this.stopScrollIndex恢复到最大值,以防止下次向下滚动计算的临界值有问题
                // stopScrollIndex = cacheArr.length-1
                // setStopScrollIndex(cacheArr.length-1)
                stopScrollIndex = cacheArr.length-1
            }
            console.log(startIndex)
            //可视区第一个元素的向上偏移
            let startOffset = scrollTop - scrollTop % cacheArr[startIndex].height
            let tableBody = document.querySelector('tbody') 
            tableBody.style.transform = `translate3d(0,${startOffset}px,0)`
          }
    }
    useEffect(() => {
       handleTableScroll()
    },[])
    return (
        <Table
            columns={columns}
            dataSource={tableData}
            scroll={{ y: 500 }}
           pagination={false}/>
    )
};

export default TableHeight

.ant-table-wrapper{
    overflow: hidden;
}
.ant-table-body{
    position: relative;
    height: 500px;
}