Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(virtualList): 等高模式下的抖动处理, 不定高模式快速滑动白屏 #1825

Merged
merged 7 commits into from
Dec 29, 2023
20 changes: 20 additions & 0 deletions src/packages/virtuallist/__test__/virtuallist.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { render, waitFor } from '@testing-library/react'
import '@testing-library/jest-dom'

import { VirtualList } from '../virtuallist'
import { trigger } from '@/utils/test/event'

const props = {
list: new Array(100).fill(0),
Expand Down Expand Up @@ -56,3 +57,22 @@ test('renders only visible items', async () => {
expect(listElement.length).toBe(visibleCount)
})
})
test('scroll', async () => {
const boxHeight = 500
const { container } = render(
<VirtualList
{...props}
containerHeight={boxHeight}
itemEqual={false}
data-testid="scrollList3"
/>
)
const track = container.querySelector('.nut-virtualList-box')
if (track) {
trigger(track, 'scroll', 0, 100)
await waitFor(() => {
const element18 = container.querySelector('[data-index="18"]')
expect(element18).toBeTruthy()
})
}
})
10 changes: 4 additions & 6 deletions src/packages/virtuallist/demo.taro.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,10 +56,10 @@ const ListDemo = () => {
}
const getData = useCallback(() => {
const datas: any = []
const pageSize = 10
const pageSize = 20
for (let i = (pageNo - 1) * pageSize; i < pageNo * pageSize; i++) {
const num = i > 9 ? i : `0${i}`
datas.push({ v: ` list${num}`, k: '3333' })
datas.push({ v: ` list${num}` })
}
if (pageNo === 1) {
setsourceData(() => {
Expand All @@ -77,7 +77,7 @@ const ListDemo = () => {
}, [getData])

const itemRender = (data: any) => {
return <div style={itemStyle}>{data.v + data.k}</div>
return <div style={itemStyle}>{data.v}</div>
}

const itemVariable = (data: any, dataIndex: number, index: number) => {
Expand All @@ -88,7 +88,7 @@ const ListDemo = () => {
...itemStyel2,
}}
>
{data.v}
{data.v}-{dataIndex}
</div>
)
}
Expand All @@ -115,12 +115,10 @@ const ListDemo = () => {
case '2':
return (
<VirtualList
itemHeight={80}
list={list}
itemRender={itemVariable}
onScroll={onScroll}
itemEqual={false}
containerHeight={500}
/>
)
default:
Expand Down
6 changes: 4 additions & 2 deletions src/packages/virtuallist/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,14 +112,16 @@ const getEndIndex = ({
const updateItemSize = (
positions: PositionType[],
items: HTMLCollection,
sizeKey: 'width' | 'height'
sizeKey: 'width' | 'height',
margin?: number
): void => {
const newPos = positions.concat()
Array.from(items).forEach((item) => {
const index = Number(item.getAttribute('data-index'))
const styleVal = item.getAttribute('style')
if (styleVal && styleVal.includes('none')) return
const nowSize = item.getBoundingClientRect()[sizeKey]
let nowSize = item.getBoundingClientRect()[sizeKey]
if (margin) nowSize += margin

const oldSize = positions[index][sizeKey] as number
// 存在差值, 更新该节点以后所有的节点
Expand Down
72 changes: 36 additions & 36 deletions src/packages/virtuallist/virtuallist.taro.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import React, {
useRef,
useState,
} from 'react'
import { ScrollView } from '@tarojs/components'
import { ScrollView, View } from '@tarojs/components'
import { getSystemInfoSync } from '@tarojs/taro'
import classNames from 'classnames'
import { Data, PositionType, VirtualListState } from './types'
import { binarySearch, initPositinoCache, updateItemSize } from './utils'
import { Data, PositionType } from './types'
import { initPositinoCache, updateItemSize } from './utils'
import { BasicComponent, ComponentDefaults } from '@/utils/typings'

const clientHeight = getSystemInfoSync().windowHeight - 5 || 667
Expand All @@ -20,6 +20,7 @@ export interface VirtualListProps extends BasicComponent {
containerHeight: number
itemRender: (data: any, dataIndex: number, index: number) => ReactNode
itemHeight: number
margin: number
itemEqual: boolean
overscan: number
onScroll: () => void
Expand All @@ -31,6 +32,7 @@ const defaultProps = {
list: [] as Array<Data>,
containerHeight: clientHeight,
itemHeight: 66,
margin: 10,
itemEqual: true,
overscan: 2,
} as VirtualListProps
Expand All @@ -42,6 +44,7 @@ export const VirtualList: FunctionComponent<Partial<VirtualListProps>> = (
list,
itemRender,
itemHeight,
margin,
itemEqual,
overscan,
key,
Expand All @@ -55,7 +58,7 @@ export const VirtualList: FunctionComponent<Partial<VirtualListProps>> = (
}

const [startOffset, setStartOffset] = useState(0)
const [start, setStart] = useState(0)
const start = useRef(0)

// 虚拟列表容器ref
const scrollRef = useRef<HTMLDivElement>(null)
Expand All @@ -75,12 +78,6 @@ export const VirtualList: FunctionComponent<Partial<VirtualListProps>> = (
])

const [offSetSize, setOffSetSize] = useState<number>(containerHeight || 0)
const [options, setOptions] = useState<VirtualListState>({
startOffset: 0, // 可视区域距离顶部的偏移量
startIndex: 0, // 可视区域开始索引
overStart: 0,
endIndex: 10, // 可视区域结束索引
})

// 初始计算可视区域展示数量
useEffect(() => {
Expand All @@ -91,69 +88,72 @@ export const VirtualList: FunctionComponent<Partial<VirtualListProps>> = (

useEffect(() => {
if (containerHeight) return

setOffSetSize(getContainerHeight())
}, [containerHeight])

useEffect(() => {
const pos = initPositinoCache(itemHeight, list.length)
const pos = initPositinoCache(itemHeight + margin, list.length)
setPositions(pos)
}, [itemHeight, list])

const prevListLength = useRef(list.length)

// 可视区域总高度
const getContainerHeight = () => {
// 初始首页列表高度
const initH = itemHeight * list.length
const initH = (itemHeight + margin) * list.length
// 未设置containerHeight高度,判断首页高度小于设备高度时,滚动容器高度为首页数据高度,减5为分页触发的偏移量
return initH < clientHeight
? initH + overscan * itemHeight - 5
? initH + overscan * (itemHeight + margin) - 5
: Math.min(containerHeight, clientHeight) // Math.min(containerHeight, clientHeight)
}
// 可视区域条数
const visibleCount = () => {
return Math.ceil(getContainerHeight() / itemHeight) + overscan
return Math.ceil(getContainerHeight() / (itemHeight + margin)) + overscan
}

const end = () => {
return start + visibleCount()
return start.current + visibleCount()
}

const listHeight = () => {
return list.length * itemHeight
return list.length * (itemHeight + margin)
}

const visibleData = () => {
return list.slice(start, Math.min(end(), list.length))
return list.slice(start.current, Math.min(end(), list.length))
}

const updateTotalSize = useCallback(() => {
if (!itemsRef.current) return
const items: HTMLCollection = itemsRef.current.children
if (!items.length) return
// 更新缓存
updateItemSize(positions, items, 'height')
updateItemSize(positions, items, 'height', margin)
}, [positions])

// 滚动监听
const listScroll = (e: any) => {
const scrollTop = e.target.scrollTop
const scrollSize = Math.floor(scrollTop)
const startIndex = binarySearch(positions, false, scrollSize)
const overStart = startIndex - overscan > -1 ? startIndex - overscan : 0
const endIndex = end()
if (scrollTop <= 0) {
e.target.scrollTop = 0
return setStartOffset(0)
}
if (!itemEqual) {
updateTotalSize()
}
setStart(Math.floor(scrollTop / itemHeight))
setOptions({ startOffset, startIndex, overStart, endIndex })
if (end() > list.length - 1) {
start.current = Math.floor(scrollTop / (itemHeight + margin))
setStartOffset(scrollTop - (scrollTop % (itemHeight + margin)))
const endIndex = end()
// list 变动说明触底
if (endIndex > list.length - 1 && prevListLength.current < list.length) {
onScroll && onScroll()
prevListLength.current = list.length
}
setStartOffset(scrollTop - (scrollTop % itemHeight))
}

return (
<div
<View
className={classNames('nut-virtualList-box', className)}
{...rest}
style={{
Expand All @@ -162,6 +162,7 @@ export const VirtualList: FunctionComponent<Partial<VirtualListProps>> = (
>
<ScrollView
scrollY
bounces={false}
type="list"
ref={scrollRef}
className="nut-virtuallist"
Expand All @@ -170,21 +171,20 @@ export const VirtualList: FunctionComponent<Partial<VirtualListProps>> = (
}}
onScroll={listScroll}
>
<div
<View
className="nut-virtuallist-phantom"
style={{ height: `${listHeight()}px` }}
/>
<div
<View
className="nut-virtuallist-container"
ref={itemsRef}
style={{ transform: `translate3d(0, ${startOffset}px, 0)` }}
>
{visibleData().map((data: any, index: number) => {
const { overStart } = options
const dataIndex = overStart + index
const dataIndex = start.current + index
const keyVal = key && data[key] ? data[key] : dataIndex
return (
<div
<View
data-index={`${dataIndex}`}
className="nut-virtuallist-item"
key={`${keyVal}`}
Expand All @@ -193,12 +193,12 @@ export const VirtualList: FunctionComponent<Partial<VirtualListProps>> = (
}}
>
{itemRender ? itemRender(data, dataIndex, index) : data}
</div>
</View>
)
})}
</div>
</View>
</ScrollView>
</div>
</View>
)
}

Expand Down
Loading