fix(web): 按真实高度计算虚拟列表窗口
This commit is contained in:
@@ -2,7 +2,14 @@
|
||||
<!-- Implementation reference: draft-2026-07-13-p0-cloud-console. -->
|
||||
|
||||
<script setup lang="ts" generic="T extends object">
|
||||
import { computed, ref } from "vue";
|
||||
import {
|
||||
computed,
|
||||
nextTick,
|
||||
onBeforeUnmount,
|
||||
onMounted,
|
||||
ref,
|
||||
watch,
|
||||
} from "vue";
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
items: T[];
|
||||
@@ -18,8 +25,9 @@ const props = withDefaults(defineProps<{
|
||||
});
|
||||
|
||||
const scrollTop = ref(0);
|
||||
const viewportHeight = ref(typeof props.height === "number" ? props.height : 420);
|
||||
const viewportHeight = ref(typeof props.height === "number" ? props.height : 0);
|
||||
const root = ref<HTMLElement | null>(null);
|
||||
let resizeObserver: ResizeObserver | null = null;
|
||||
const startIndex = computed(() => Math.max(0, Math.floor(scrollTop.value / props.itemHeight) - props.overscan));
|
||||
const endIndex = computed(() => Math.min(props.items.length, Math.ceil((scrollTop.value + viewportHeight.value) / props.itemHeight) + props.overscan));
|
||||
const visibleItems = computed(() => props.items.slice(startIndex.value, endIndex.value));
|
||||
@@ -27,6 +35,45 @@ const totalHeight = computed(() => props.items.length * props.itemHeight);
|
||||
const windowOffset = computed(() => startIndex.value * props.itemHeight);
|
||||
const rootStyle = computed(() => ({ height: typeof props.height === "number" ? `${props.height}px` : props.height }));
|
||||
|
||||
function readViewportHeight(): void {
|
||||
const height = root.value?.clientHeight ?? 0;
|
||||
if (height > 0 && height !== viewportHeight.value) viewportHeight.value = height;
|
||||
}
|
||||
|
||||
function stopObservingViewport(): void {
|
||||
resizeObserver?.disconnect();
|
||||
resizeObserver = null;
|
||||
if (typeof window !== "undefined") {
|
||||
window.removeEventListener("resize", readViewportHeight);
|
||||
}
|
||||
}
|
||||
|
||||
function observeViewport(): void {
|
||||
stopObservingViewport();
|
||||
readViewportHeight();
|
||||
if (!root.value) return;
|
||||
if (typeof ResizeObserver !== "undefined") {
|
||||
resizeObserver = new ResizeObserver(readViewportHeight);
|
||||
resizeObserver.observe(root.value);
|
||||
return;
|
||||
}
|
||||
if (typeof window !== "undefined") {
|
||||
window.addEventListener("resize", readViewportHeight, { passive: true });
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(observeViewport);
|
||||
onBeforeUnmount(stopObservingViewport);
|
||||
|
||||
watch(
|
||||
() => props.height,
|
||||
async (height) => {
|
||||
if (typeof height === "number") viewportHeight.value = height;
|
||||
await nextTick();
|
||||
readViewportHeight();
|
||||
},
|
||||
);
|
||||
|
||||
function onScroll(event: Event): void {
|
||||
const target = event.currentTarget;
|
||||
if (!(target instanceof HTMLElement)) return;
|
||||
|
||||
Reference in New Issue
Block a user