alibaba / hooks

A high-quality & reliable React Hooks library. https://ahooks.pages.dev/
https://ahooks.js.org/
MIT License
14.02k stars 2.71k forks source link

useVirtualList 最后一个元素的高度不能超过 container 的高度 #2496

Open weekbin opened 7 months ago

weekbin commented 7 months ago

当最后一个元素的高度比 Container 还高时,getOffset 的返回值会等于 list.length

  const getOffset = (scrollTop: number) => {
    if (isNumber(itemHeightRef.current)) {
      return Math.floor(scrollTop / itemHeightRef.current) + 1;
    }
    let sum = 0;
    let offset = 0;
    for (let i = 0; i < list.length; i++) {
      const height = itemHeightRef.current(i, list[i]);
      sum += height;
      if (sum >= scrollTop) {
        offset = i;
        break;
      }
    }
    return offset + 1;
  };

在计算 visibleCount 时,会导致 getVisibleCount 的返回值为 -list.length

  const getVisibleCount = (containerHeight: number, fromIndex: number) => {
    if (isNumber(itemHeightRef.current)) {
      return Math.ceil(containerHeight / itemHeightRef.current);
    }

    let sum = 0;
    let endIndex = 0;
    for (let i = fromIndex; i < list.length; i++) {
      const height = itemHeightRef.current(i, list[i]);
      sum += height;
      endIndex = i;
      if (sum >= containerHeight) {
        break;
      }
    }
    return endIndex - fromIndex;
  };

最终会导致 start = list.length,end = overscan,会取不到元素进行渲染。

const start = Math.max(0, offset - overscan); // 起始位置
const end = Math.min(list.length, offset + visibleCount + overscan); // 结束位置

建议在 getVisibleCount 返回时增加判断 endIndex - fromIndex < 0 ? 0 : endIndex - fromIndex, 这样在最后一个元素高度比 Contianer 还大时,仍然可以保证正常的渲染。

weekbin commented 7 months ago

用官网的例子改了改就可以复现。

/**
 * title: Dynamic item height
 * desc: Specify item height dynamically.
 *
 * title.zh-CN: 动态元素高度
 * desc.zh-CN: 动态指定每个元素的高度
 */

import React, { useMemo, useRef } from 'react';
import { useVirtualList } from 'ahooks';

export default () => {
  const containerRef = useRef(null);
  const wrapperRef = useRef(null);

  const originalList = useMemo(() => Array.from(Array(200).keys()), []);

  const [value, onChange] = React.useState<number>(0);

  const [list, scrollTo] = useVirtualList(originalList, {
    containerTarget: containerRef,
    wrapperTarget: wrapperRef,
    itemHeight: (i) => (i % 2 === 0 ? 428 : 310),
    overscan: 10,
  });

  return (
    <div>
      <div style={{ textAlign: 'right', marginBottom: 16 }}>
        <input
          style={{ width: 120 }}
          placeholder="line number"
          type="number"
          value={value}
          onChange={(e) => onChange(Number(e.target.value))}
        />
        <button
          style={{ marginLeft: 8 }}
          type="button"
          onClick={() => {
            scrollTo(Number(value));
          }}
        >
          scroll to
        </button>
      </div>
      <div ref={containerRef} style={{ height: '300px', overflow: 'auto', background: 'black' }}>
        <div ref={wrapperRef}>
          {list.map((ele) => (
            <div
              style={{
                height: ele.index % 2 === 0 ? 428 : 310,
                display: 'flex',
                justifyContent: 'center',
                alignItems: 'center',
                border: '1px solid #e8e8e8',
                background: ele.index % 2 === 0 ? 'green' : 'red'
              }}
              key={ele.index}
            >
              Row: {ele.data+1} size: {ele.index % 2 === 0 ? 'small' : 'large'}
            </div>
          ))}
        </div>
      </div>
    </div>
  );
};
askwuxue commented 3 months ago

let me try