1818 data-testid =" channel-viewport"
1919 >
2020 <MessagesSkeleton
21- v-if =" enablePreload && ! isReachedEnd "
21+ v-if =" ! isReachedEnd "
2222 ref="topSkeletonRef"
2323 :key =" topSkeletonKey "
24- :count =" 16 "
24+ :simple =" ! enableProactiveLoading "
25+ :count =" enableProactiveLoading ? 16 : 3 "
2526 :class =" $style .edgeSkeleton "
2627 />
2728 <MessagesScrollerSeparator
3738 />
3839 </template >
3940 <MessagesSkeleton
40- v-if =" enablePreload && ! isReachedLatest "
41+ v-if =" enableProactiveLoading && ! isReachedLatest "
4142 ref="bottomSkeletonRef"
4243 :key =" bottomSkeletonKey "
4344 reversed
44- :count =" 8 "
45+ :simple =" ! enableProactiveLoading "
46+ :count =" enableProactiveLoading ? 16 : 3 "
4547 :class =" $style .edgeSkeleton "
4648 />
4749 </div >
5456import { emit } from ' process'
5557
5658import type { ComponentPublicInstance , Ref } from ' vue'
57- import {
58- computed ,
59- nextTick ,
60- onMounted ,
61- shallowRef ,
62- watch ,
63- watchEffect
64- } from ' vue'
59+ import { computed , nextTick , onMounted , shallowRef , watch } from ' vue'
6560import { useRoute , useRouter } from ' vue-router'
6661
6762import {
6863 useEventListener ,
6964 useIntersectionObserver ,
7065 useResizeObserver
7166} from ' @vueuse/core'
72- import { throttle } from ' throttle-debounce'
67+ import { debounce , throttle } from ' throttle-debounce'
7368
7469import { useOpenLink } from ' /@/composables/useOpenLink'
7570import useResponsive from ' /@/composables/useResponsive'
@@ -90,8 +85,8 @@ export interface MessageScrollerInstance extends ComponentPublicInstance {
9085 rootRef: HTMLDivElement
9186}
9287
93- const MIN_THRESHOLD = 500 // 最小の閾値
94- const THRESHOLD_INCREMENT = 1000 // 端に到達するたびに増加させる閾値
88+ const MIN_THRESHOLD = 1000 // 最小の閾値
89+ const THRESHOLD_INCREMENT = 1500 // 端に到達するたびに増加させる閾値
9590const DECAY_PER_SECOND = 0.6 // 一秒あたりの減衰率
9691
9792/**
@@ -250,41 +245,52 @@ const bottomSkeletonKey = shallowRef(0)
250245useResizeObserver (
251246 () => topSkeletonRef .value ?.$el ,
252247 entries => {
253- topSkeletonHeight .value = entries [0 ]?.contentRect .height ?? 0
248+ topSkeletonHeight .value =
249+ unrefElement (topSkeletonRef ).getBoundingClientRect ().height ?? 0
254250 }
255251)
252+
256253useResizeObserver (
257254 () => bottomSkeletonRef .value ?.$el ,
258255 entries => {
259- bottomSkeletonHeight .value = entries [0 ]?.contentRect .height ?? 0
256+ bottomSkeletonHeight .value =
257+ unrefElement (bottomSkeletonRef ).getBoundingClientRect ().height ?? 0
260258 }
261259)
262260
261+ const { isMobile } = useResponsive ()
262+ const enableProactiveLoading = computed (() => ! isIOS () && ! isMobile )
263+
263264// IntersectionObserverでスケルトンがビューポートから出たことを検出し、再生成する
264265// 可視→非可視の遷移のみで再生成するため、前回の状態を追跡
265266let wasTopVisible = false
266267let wasBottomVisible = false
267268
268269useIntersectionObserver (
269270 () => unrefElement (topSkeletonRef ),
270- ([entry ]) => {
271+ async ([entry ]) => {
271272 if (! entry ) return
273+
272274 // 可視→非可視の遷移時のみキーを更新
273275 if (wasTopVisible && ! entry .isIntersecting ) {
274276 topSkeletonKey .value ++
275277 }
278+
276279 wasTopVisible = entry .isIntersecting
277280 },
278281 { root: rootRef }
279282)
283+
280284useIntersectionObserver (
281285 () => unrefElement (bottomSkeletonRef ),
282286 ([entry ]) => {
283287 if (! entry ) return
288+
284289 // 可視→非可視の遷移時のみキーを更新
285290 if (wasBottomVisible && ! entry .isIntersecting ) {
286291 bottomSkeletonKey .value ++
287292 }
293+
288294 wasBottomVisible = entry .isIntersecting
289295 },
290296 { root: rootRef }
@@ -321,15 +327,12 @@ watch(
321327)
322328
323329const { getThreshold, update : updateThreshold } = useDynamicLoadThreshold ()
324- const { isMobile } = useResponsive ()
325-
326- const enablePreload = computed (() => ! isIOS () && ! isMobile .value )
327330
328331// スケルトンの端に到達した場合にスクロール位置を戻してループさせる
329332const SKELETON_LOOP_MARGIN = 50 // 端からのマージン
330333
331334const loopSkeletonIfNeeded = async () => {
332- if (! rootRef .value || ! enablePreload . value ) return
335+ if (! rootRef .value ) return
333336
334337 const { scrollTop, scrollHeight, clientHeight } = rootRef .value
335338
@@ -345,11 +348,11 @@ const loopSkeletonIfNeeded = async () => {
345348
346349 // 新しいスケルトンの高さとの差分を補正
347350 const heightDiff = topSkeletonHeight .value - oldHeight
348- rootRef .value .scrollBy ({ top: loopAmount + heightDiff })
351+ rootRef .value .scrollTo ({ top: scrollTop + loopAmount + heightDiff })
349352 }
350353
351354 // 下端のスケルトンに到達した場合
352- if (
355+ else if (
353356 scrollHeight - (scrollTop + clientHeight ) < SKELETON_LOOP_MARGIN &&
354357 ! props .isReachedLatest
355358 ) {
@@ -362,24 +365,26 @@ const loopSkeletonIfNeeded = async () => {
362365 await nextTick ()
363366
364367 // 下端の場合は高さの差分は自動的に反映されるので、loopAmountだけ戻す
365- rootRef .value .scrollBy ({ top: - loopAmount })
368+ rootRef .value .scrollTo ({ top: scrollTop - loopAmount })
366369 }
367370}
368371
369372const requestLoadMessages = () => {
370373 if (! rootRef .value ) return
371374 state .scrollTop = rootRef .value .scrollTop
372375
373- loopSkeletonIfNeeded ()
374-
375376 const top = rootRef .value .scrollTop - topSkeletonHeight .value
376377 const bottom =
377378 rootRef .value .scrollHeight -
378379 (rootRef .value .scrollTop +
379380 rootRef .value .clientHeight +
380381 bottomSkeletonHeight .value )
381382
382- if (enablePreload .value ) updateThreshold (top , bottom )
383+ if (enableProactiveLoading .value ) {
384+ loopSkeletonIfNeeded ()
385+ updateThreshold (top , bottom )
386+ }
387+
383388 const threshold = getThreshold ()
384389
385390 if (props .isLoading ) return
@@ -391,7 +396,10 @@ const requestLoadMessages = () => {
391396 }
392397}
393398
394- const handleScroll = throttle (64 , requestLoadMessages )
399+ const handleScroll = (enableProactiveLoading .value ? throttle : debounce )(
400+ 200 ,
401+ requestLoadMessages
402+ )
395403
396404const visibilitychangeListener = () => {
397405 if (document .visibilityState === ' visible' ) {
0 commit comments