Skip to content

Commit 893c237

Browse files
committed
revert: simplify VirtualGrid item size measurement
1 parent 48e7f1b commit 893c237

File tree

2 files changed

+21
-126
lines changed

2 files changed

+21
-126
lines changed

src/components/common/VirtualGrid.test.ts

Lines changed: 0 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -105,64 +105,6 @@ describe('VirtualGrid', () => {
105105
wrapper.unmount()
106106
})
107107

108-
it('uses measured row step (including gap) to compute visible range', async () => {
109-
const items = createItems(100)
110-
mockedWidth.value = 100
111-
mockedHeight.value = 60
112-
mockedScrollY.value = 110
113-
114-
const wrapper = mount(VirtualGrid<TestItem>, {
115-
props: {
116-
items,
117-
gridStyle: defaultGridStyle,
118-
defaultItemHeight: 50,
119-
defaultItemWidth: 100,
120-
maxColumns: 1,
121-
bufferRows: 0
122-
},
123-
slots: {
124-
item: `<template #item="{ index }">
125-
<div class="test-index">{{ index }}</div>
126-
</template>`
127-
},
128-
attachTo: document.body
129-
})
130-
131-
await nextTick()
132-
133-
const initialIndices = wrapper
134-
.findAll('.test-index')
135-
.map((node) => node.text())
136-
expect(initialIndices[0]).toBe('2')
137-
138-
const renderedItemEls = wrapper
139-
.findAll<HTMLElement>('[data-virtual-grid-item]')
140-
.map((node) => node.element)
141-
142-
expect(renderedItemEls.length).toBeGreaterThanOrEqual(2)
143-
144-
Object.defineProperty(renderedItemEls[0], 'clientHeight', { value: 50 })
145-
Object.defineProperty(renderedItemEls[0], 'clientWidth', { value: 100 })
146-
Object.defineProperty(renderedItemEls[0], 'offsetTop', { value: 0 })
147-
Object.defineProperty(renderedItemEls[0], 'offsetLeft', { value: 0 })
148-
149-
Object.defineProperty(renderedItemEls[1], 'clientHeight', { value: 50 })
150-
Object.defineProperty(renderedItemEls[1], 'clientWidth', { value: 100 })
151-
Object.defineProperty(renderedItemEls[1], 'offsetTop', { value: 60 })
152-
Object.defineProperty(renderedItemEls[1], 'offsetLeft', { value: 0 })
153-
154-
await wrapper.setProps({ items: [...items] })
155-
await nextTick()
156-
await nextTick()
157-
158-
const updatedIndices = wrapper
159-
.findAll('.test-index')
160-
.map((node) => node.text())
161-
expect(updatedIndices[0]).toBe('1')
162-
163-
wrapper.unmount()
164-
})
165-
166108
it('respects maxColumns prop', async () => {
167109
const items = createItems(10)
168110
mockedWidth.value = 400

src/components/common/VirtualGrid.vue

Lines changed: 21 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -57,8 +57,8 @@ const emit = defineEmits<{
5757
'approach-end': []
5858
}>()
5959
60-
const rowHeight = ref(defaultItemHeight)
61-
const colWidth = ref(defaultItemWidth)
60+
const itemHeight = ref(defaultItemHeight)
61+
const itemWidth = ref(defaultItemWidth)
6262
const container = ref<HTMLElement | null>(null)
6363
const { width, height } = useElementSize(container)
6464
const { y: scrollY } = useScroll(container, {
@@ -67,7 +67,7 @@ const { y: scrollY } = useScroll(container, {
6767
})
6868
6969
const cols = computed(() =>
70-
Math.min(Math.floor(width.value / colWidth.value) || 1, maxColumns)
70+
Math.min(Math.floor(width.value / itemWidth.value) || 1, maxColumns)
7171
)
7272
7373
const mergedGridStyle = computed<CSSProperties>(() => {
@@ -78,8 +78,8 @@ const mergedGridStyle = computed<CSSProperties>(() => {
7878
}
7979
})
8080
81-
const viewRows = computed(() => Math.ceil(height.value / rowHeight.value))
82-
const offsetRows = computed(() => Math.floor(scrollY.value / rowHeight.value))
81+
const viewRows = computed(() => Math.ceil(height.value / itemHeight.value))
82+
const offsetRows = computed(() => Math.floor(scrollY.value / itemHeight.value))
8383
const isValidGrid = computed(() => height.value && width.value && items?.length)
8484
8585
const state = computed<GridState>(() => {
@@ -101,28 +101,15 @@ const renderedItems = computed(() =>
101101
isValidGrid.value ? items.slice(state.value.start, state.value.end) : []
102102
)
103103
104-
function spacerRowsToHeight(rows: number): string {
105-
return `${rows * rowHeight.value}px`
104+
function rowsToHeight(rows: number): string {
105+
return `${(rows / cols.value) * itemHeight.value}px`
106106
}
107107
108-
const topSpacerRows = computed(() => {
109-
if (!isValidGrid.value) return 0
110-
return Math.floor(state.value.start / cols.value)
111-
})
112-
113-
const bottomSpacerRows = computed(() => {
114-
if (!isValidGrid.value) return 0
115-
116-
const totalRows = Math.ceil(items.length / cols.value)
117-
const renderedEndRow = Math.ceil(state.value.end / cols.value)
118-
return Math.max(0, totalRows - renderedEndRow)
119-
})
120-
121108
const topSpacerStyle = computed<CSSProperties>(() => ({
122-
height: spacerRowsToHeight(topSpacerRows.value)
109+
height: rowsToHeight(state.value.start)
123110
}))
124111
const bottomSpacerStyle = computed<CSSProperties>(() => ({
125-
height: spacerRowsToHeight(bottomSpacerRows.value)
112+
height: rowsToHeight(items.length - state.value.end)
126113
}))
127114
128115
whenever(
@@ -132,53 +119,19 @@ whenever(
132119
}
133120
)
134121
135-
const ITEM_SIZE_EPSILON_PX = 1
136-
137-
/**
138-
* Measures the effective grid row/column step (including `gap`) from rendered
139-
* items to keep spacer math stable and prevent scroll jitter near the end.
140-
*/
141122
function updateItemSize(): void {
142-
if (!container.value) return
143-
144-
const itemElements = Array.from(
145-
container.value.querySelectorAll('[data-virtual-grid-item]')
146-
).filter((node): node is HTMLElement => node instanceof HTMLElement)
147-
148-
const firstItem = itemElements[0]
149-
150-
if (!firstItem?.clientHeight || !firstItem?.clientWidth) return
151-
152-
const nextRowItem = itemElements.find(
153-
(item) => item.offsetTop > firstItem.offsetTop
154-
)
155-
156-
const measuredRowHeight = nextRowItem
157-
? nextRowItem.offsetTop - firstItem.offsetTop
158-
: firstItem.clientHeight
159-
160-
const nextColItem = itemElements.find(
161-
(item) =>
162-
item.offsetTop === firstItem.offsetTop &&
163-
item.offsetLeft > firstItem.offsetLeft
164-
)
165-
166-
const measuredColWidth = nextColItem
167-
? nextColItem.offsetLeft - firstItem.offsetLeft
168-
: firstItem.clientWidth
169-
170-
if (
171-
measuredRowHeight > 0 &&
172-
Math.abs(rowHeight.value - measuredRowHeight) >= ITEM_SIZE_EPSILON_PX
173-
) {
174-
rowHeight.value = measuredRowHeight
175-
}
176-
177-
if (
178-
measuredColWidth > 0 &&
179-
Math.abs(colWidth.value - measuredColWidth) >= ITEM_SIZE_EPSILON_PX
180-
) {
181-
colWidth.value = measuredColWidth
123+
if (container.value) {
124+
const firstItem = container.value.querySelector('[data-virtual-grid-item]')
125+
126+
// Don't update item size if the first item is not rendered yet
127+
if (!firstItem?.clientHeight || !firstItem?.clientWidth) return
128+
129+
if (itemHeight.value !== firstItem.clientHeight) {
130+
itemHeight.value = firstItem.clientHeight
131+
}
132+
if (itemWidth.value !== firstItem.clientWidth) {
133+
itemWidth.value = firstItem.clientWidth
134+
}
182135
}
183136
}
184137
const onResize = debounce(updateItemSize, resizeDebounce)

0 commit comments

Comments
 (0)