-
Notifications
You must be signed in to change notification settings - Fork 2.7k
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
useVirtualList 配合 IntersectionObserver 出现抖动 #2324
Comments
我怀疑是掉帧,在你代码 IntersectionObserver 的 callback 里随便打印个 log,鼠标快速滚动一下可以看到很多输出,重排太多次了。我没记错的话 首先,Ellipsis 里的逻辑肯定要做性能优化的。其次至于 useVirtualList 有多少性能问题在里面,目前我不确定。后面我找其他成熟的虚拟滚动库和 useVirtualList 对比下,写一些稍微复杂的场景,不只是纯文本展示。 综上,目前我能看到的抖动貌似是掉帧,不是 bug 类的抽搐。 |
@liuyib 有啥优化的建议吗? |
目前看,你的 demo 中每一行都是定高,高度就没必要重新算了( 另外,这个 demo 里的逻辑有几处比较奇怪的地方:
综上,目前 demo 里代码实现上还有些问题,就不做优化建议了。 |
基于目前反馈的信息,没有发现 bug 类的异常哈,issue 就关了~ |
@liuyib 似乎不是,还是有点问题,我把订阅去掉了依然会有抖动,我猜测是因为使用的原生 scroll 导致的?因为我使用了另一个虚拟滚动的 hook,它用的是 react 的 |
我比较好奇的是为什么不使用react 的 |
发一下这个的 GitHub 地址呢,我去瞅下~ 也许可以参考优化下 ahooks 的 useVirtualList |
公司内部的一个库,不过可以给你贴源码 import { useSize } from 'ahooks'
import { useEffect, useState, useMemo, UIEvent, useRef } from 'react'
export interface OptionType<T> {
itemHeight: number | ((data: T, index: number) => number)
overscan?: number
onScroll?: (e: UIEvent<HTMLElement>) => void
}
export const useVirtualList = <T>(
originalList: T[],
options: OptionType<T>,
deps: any[] = [],
) => {
const { itemHeight, overscan = 5, onScroll } = options
const scrollerRef = useRef<HTMLDivElement>(null)
const size = useSize(scrollerRef)
const [range, setRange] = useState({ start: 0, end: 10 })
useEffect(() => {
getListRange()
}, [size?.width, size?.height, originalList.length])
const totalHeight = useMemo(() => {
if (typeof itemHeight === 'number') {
return originalList.length * itemHeight
}
return originalList.reduce(
(sum, data, index) => sum + itemHeight(data, index),
0,
)
}, [originalList.length, ...deps])
const list = useMemo(
() =>
originalList.slice(range.start, range.end).map((ele, index) => ({
data: ele,
index: index + range.start,
})),
[originalList, range],
)
const getListRange = () => {
const element = scrollerRef.current
if (element) {
const offset = getRangeOffset(element.scrollTop)
const viewCapacity = getViewCapacity(element.clientHeight)
const from = offset - overscan
const to = offset + viewCapacity + overscan
setRange({
start: from < 0 ? 0 : from,
end: to > originalList.length ? originalList.length : to,
})
}
}
const getViewCapacity = (scrollerHeight: number) => {
if (typeof itemHeight === 'number') {
return Math.ceil(scrollerHeight / itemHeight)
}
const { start = 0 } = range
let sum = 0
let capacity = 0
for (let i = start; i < originalList.length; i++) {
const height = (itemHeight as (data: T, index: number) => number)(
originalList[i],
i,
)
sum += height
capacity = i
if (sum >= scrollerHeight) {
break
}
}
return capacity - start
}
const getRangeOffset = (scrollTop: number) => {
if (typeof itemHeight === 'number') {
return Math.floor(scrollTop / itemHeight) + 1
}
let sum = 0
let offset = 0
for (let i = 0; i < originalList.length; i++) {
const height = (itemHeight as (data: T, index: number) => number)(
originalList[i],
i,
)
sum += height
if (sum >= scrollTop) {
offset = i
break
}
}
return offset + 1
}
const getDistanceTop = (index: number) => {
if (typeof itemHeight === 'number') {
const height = index * itemHeight
return height
}
const height = originalList
.slice(0, index)
.reduce((sum, data, index) => sum + itemHeight(data, index), 0)
return height
}
const scrollTo = (index: number) => {
if (scrollerRef.current) {
scrollerRef.current.scrollTop = getDistanceTop(index)
getListRange()
}
}
return {
list,
scrollTo,
scrollerRef,
scrollerProps: {
ref: scrollerRef,
onScroll: (e: UIEvent<HTMLElement>) => {
e.preventDefault()
getListRange()
if (onScroll) {
onScroll(e)
}
},
style: { overflowY: 'auto' } as { overflowY: 'auto' },
},
wrapperProps: {
style: {
width: '100%',
height: totalHeight,
paddingTop: getDistanceTop(range.start),
},
},
}
} |
它们也是 fork 你们实现😄 |
最小复现 case:https://stackblitz.com/edit/stackblitz-starters-2ayxep?file=src%2FEllipsis.tsx
在超出默认视口范围后继续滚动会出现抖动的情况,因为内部用到了
IntersectionObserver
重新绘制视图,初步判断是出现重复计算引发的抖动The text was updated successfully, but these errors were encountered: