virtual scroll list for vue

import { tableViewData } from './xxx.js'

const OVER_SCAN = 2;
const ITEM_HEIGHT = 32;

export const containerRef = ref(null); // 外部容器引用

export const renderViewData = ref([]); // 给外部实际渲染的数据,会根据数据的显示状态获取到正确的数据

const size = useElementSize(containerRef); // 容器高度
export const ranges = ref({ from: 0, to: 0 }); // 数据取值范围

export const placeholderHeight = computed(() => ({
  head: ranges.value.from * ITEM_HEIGHT + "px",
}));

// 将不展示的数据过滤, 防止隐藏的数据参与计算导致页面渲染的元素不正确的问题
const source = computed(() =>
  tableViewData.value.filter((row) => getConfigOfRowKey(row).display)
);

const getViewCapacity = (containerHeight) => {
  return Math.ceil(containerHeight / ITEM_HEIGHT);
};

const getOffset = (scrollTop) => {
  return Math.floor(scrollTop / ITEM_HEIGHT) + 1;
};

export const calculateRange = () => {
  const element = containerRef.value;
  if (!element) {
    return;
  }

  const offset = getOffset(element.scrollTop);
  const viewCapacity = getViewCapacity(element.clientHeight - 38 - 7); // 表头高度 + 横向滚动条高度

  const start = offset - OVER_SCAN;
  const end = offset + viewCapacity + OVER_SCAN;

  ranges.value = {
    from: start < 0 ? 0 : start,
    to: end > source.value.length ? source.value.length : end,
  };

  nextTick(() => {
    // 计算需要渲染到页面中的数据
    renderViewData.value = source.value.slice(
      ranges.value.from,
      ranges.value.to
    );
  });
};

watch(
  // 当容器的高度或者数据源变化则重新计算数据区间
  [size.height, tableViewData],
  debounce(() => calculateRange(), 200)
);