Skip to content

使用vue3 + TS 实现的demo,之前的所有虚拟列表和瀑布流demo都是仿小红书的虚拟瀑布流列表实现的铺垫

Notifications You must be signed in to change notification settings

ztygod/xiaohongshu-virtual-waterfall

Repository files navigation

virtual-waterfall-demo

不定高

这里指的不定高说的是类似小红书的图片加文字形式
屏幕截图 2024-06-02 220724.png
在之前的关于不定高实现中,我们是先假设一个虚拟高度,先把卡片挂载上,再调用nextTick,在渲染之前得到真实高度,更新卡片宽高和x,y坐标,最后渲染上页面得到不定高卡片。
但是在这个情景中,关于定高的逻辑处理我们相对完善,如果我们要插入一个真实DOM高度,我们就需要大改之前的代码,这是我们不愿看到的。所以这里使用了一个不太寻常的办法。用一个盒子临时存放添加的卡片,等它挂载获取到真实高度后再把它卸载。

类型接口

老一套,我们先来看看我们的类型接口。

import type { CSSProperties } from "vue";

export interface IVirtualWaterFallProps{
    gap:number,//卡片间隔
    column:number,//列数
    pageSize:number,//单次请求的数据量
    request : (page:number,pageSize:number) => Promise<ICardItem[]>
}

export interface ICardItem {
  id: number | string;
  width: number;
  height: number;
  [key: string]: any;
}
export interface IBookColumnQueue {
  list: IBookRenderItem[];
  height: number;
}

export interface IBookRenderItem {
  item: ICardItem;
  y: number;
  h: number;
  imageHeight: number;
  style: CSSProperties;
}

export interface IBookItemRect {
  width: number;
  height: number;
  imageHeight: number;
}

这些基本与前面一致,要注意的是IBookRenderItem,IBookItemRect相较之前多了一个imageHeight属性,这是我们用来专门记录卡片中图片高度的(因为有文本行的存在,卡片高度不等于图片高度 )

计算不定高度

在定高中,我们是利用addInQueue还有其中的generatorItem计算出每个图片的宽高和坐标。在不定高中我们不能这么做。
我们首先要得到itemSizeInfo 的信息,包括卡片的宽高和图片的高度。

const itemSizeInfo = ref(new Map<ICardItem["id"], IBookItemRect>());

const setItemSize = () => {
  itemSizeInfo.value = dataState.list.reduce<Map<ICardItem["id"], IBookItemRect>>((pre, current) => {
    const itemWidth = Math.floor((scrollState.viewWidth - (props.column - 1) * props.gap) / props.column);
    pre.set(current.id, {
      width: itemWidth,
      height: 0,
      imageHeight: Math.floor((itemWidth * current.height) / current.width),
    });
    return pre;
  }, new Map());
};

之后就来实现 mountTemporaryList,在这里挂载临时的卡片,并获取其真实高度:

const mountTemporaryList = (list: ICardItem[]) => {
    isShow.value = false
    list.forEach((item) => {
        const rect = itemSizeInfo.value.get(item.id)!
        temporaryList.value.push({
            item,
            y:0,
            h:0,
            imageHeight:rect.imageHeight,
            style:{
                width:`${rect.width}px`
            }
        })
    })
    nextTick(() => {
        const list = document.querySelector("#temporary-list")!;
        [...list.children].forEach((item,index) => {
            const rect = item.getBoundingClientRect()
            temporaryList.value[index].h = rect.height
        })
        isShow.value = true
        updateItemSize()
        addInQueue(temporaryList.value.length)
        temporaryList.value = []
    })
}
const updateItemSize = () => {
  temporaryList.value.forEach(({ item, h }) => {
    const rect = itemSizeInfo.value.get(item.id)!;
    itemSizeInfo.value.set(item.id, { ...rect, height: h });
  });
};

这里的逻辑很简单,就是先得到itemSizeInfo的信息,再将其添加进temporaryList中, temporaryList是临时渲染的数据,在它渲染之前我们得到它的真实高度,并重新更新。最后利用addInQueue计算出样式,截取可视区域的数据项完成虚拟列表。

About

使用vue3 + TS 实现的demo,之前的所有虚拟列表和瀑布流demo都是仿小红书的虚拟瀑布流列表实现的铺垫

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published