实现一个简单的虚拟列表瀑布流

February 22, 2025 / xChenNing / 12阅读 / 0评论 / 分类: 编程解决方案前端

XCN Virtual Waterfall

一个简单的定宽不定高虚拟列表瀑布流组件,使用react + ts编写,适用于react项目

该项目已在WA - AI Platform - Frontend项目中实装

example

Github

https://github.com/CheNing233/XCN-VirtualWaterfall

Feature

  • 支持虚拟列表
  • 自定义列数
  • 响应式
  • 支持自定义底部元素
  • 支持指定挂载滚动监听的容器
  • 支持使用钩子动态更改单个项目渲染
  • 支持使用钩子触发重新布局
  • 待开发根据宽度动态调整开关,适合停止滚动,宽度归0的场景

Usage

1. Install

npm install xcn-waterfall

2. Import

import {useXCNWaterfallItem, XCNWaterfall} from "xcn-waterfall";

3. Use

父组件使用示例

// src/example/responsiveCols.tsx

function ResponsiveCols() {
  // 这里随机生成一些盒子
  const [data, setData] = useState(
    generateRandomObjects().map(item => {
      const id = generateRandomId()

      return {
        id: id,
        height: item.height,
        width: item.width,
        content: () => (
          <Comp
            name={id}
            style={{
              position: 'absolute', left: 0, top: 0, right: 0, bottom: 0,
              background: item.color
            }}
          >
          </Comp>
        )
      } as WaterfallItems // 这个类型定义了每个item的必要参数,包括id,height,width,content
    })
  )
  
  // 这里是处理 onRequestBottomMore 事件,当请求3次后,返回空[]表示没有更多数据了
  const count = useRef(0)
  const handleRequestMore = async () => {
    count.current++
    let newData: WaterfallItems[] = []

    if (count.current < 3) {
      newData = generateRandomObjects().map(item => {
        const id = generateRandomId()
        return {
          id: id,
          height: item.height,
          width: item.width,
          content: () => (
            <Comp
              name={id}
              style={{
                position: 'absolute', left: 0, top: 0, right: 0, bottom: 0,
                background: item.color
              }}
            />
          )
        }
      })
    } else {
      newData = [];
    }

    // 延时 1000 ms
    await new Promise(resolve => setTimeout(resolve, 1000))

    return newData as WaterfallItems[]
  }

  return (
    <>
      <XCNWaterfall
        data={data}
        // 响应式列数,指定在不同屏幕下的列数
        columnsGroup={{
          xs: 1,
          sm: 2,
          md: 3,
          lg: 4,
        }}
        // 容器滚动监听的ref,可选,默认自动生成100%高度的容器包裹自身滚动
        // scrollContainerRef={scrollRef}
        onRequestBottomMore={handleRequestMore}
        // 底部元素渲染函数,可选
        bottomCompRenderFn={(reqCount: number, isLoading: boolean, isFinished: boolean) => (
          <h4 style={{
            color: 'yellow',
            textAlign: 'center'
          }}>waterfall bottom | reqCount {reqCount} | isLoading {`${isLoading}`} | isFinished {`${isFinished}`}</h4>
        )}
        // 容器样式,可选
        style={{
          width: '80vw',
          height: '80vh'
        }}
      />
    </>
  )
}

子组件使用示例

// src/example/comp.tsx
// 自定义的卡片
export function Comp(props: any) {

  // 通过 useXCNWaterfallItem 获取到当前卡片的 item 数据,并修改并重新渲染该卡片
  const {
    item, updateItem,
    initState, computedPosition, computedItemsInView, setItemsToRender
  } = useXCNWaterfallItem(props.name)

  /**
   * update new width and height for box
   * */
  const updateNewBox = () => {
    updateItem({
      height: Math.floor(Math.random() * (1024 - 512 + 1)) + 512,
      width: Math.floor(Math.random() * (1024 - 512 + 1)) + 512,
    })
    initState()
    computedPosition()
    setItemsToRender(computedItemsInView())
  }

  return (
    <div {...props}>
      {props.name}
      <p>
        use item {item?.count || "none"}
      </p>
      <button onClick={() => {
        if (!item?.count) {
          updateItem({
            count: 1
          })
        } else {
          updateItem({
            count: item.count + 1
          })
        }
      }}>
        +
      </button>
      <button onClick={updateNewBox}>
        set new box
      </button>
    </div>
  )
}

Tips

  • 使用useXCNWaterfallItem钩子动态更改单个项目渲染,比如单个卡片内的计数器
  • 优先使用columnsGroup属性设置列数,如果没有该属性,则使用columns属性,响应式是基于容器宽度而不是window宽度

Full Example

可查看src/example目录

Principle

1. initState()

在全部重排之前调用

用于初始化瀑布流状态,计算列宽

2. computedPosition()

根据缓存的宽高计算所有item的位置,排序生成用于二分查找的数据

3. computedItemsInView()

这一步使用二分查找,定位所有命中视口和缓存高度的item

4. setItemsToRender()

第3步计算出来的item进行渲染

#React(4)#TypeScript(5)#虚拟列表(1)#瀑布流布局(1)

文章作者:xChenNing

文章链接:https://blog.glcn.top/archives/shi-xian-yi-ge-jian-dan-de-xu-ni-lie-biao-pu-bu-liu

版权声明:本博客所有文章除特别声明外,均采用CC BY-NC-SA 4.0 许可协议,转载请注明出处!


评论