Skip to content

Commit cdfff3d

Browse files
committed
feat: 先読みが無効化されるときの挙動を改善
1 parent bb4b1c5 commit cdfff3d

2 files changed

Lines changed: 46 additions & 34 deletions

File tree

src/components/Main/MainView/MessagesScroller/MessagesScroller.vue

Lines changed: 37 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,11 @@
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
@@ -37,11 +38,12 @@
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>
@@ -54,22 +56,15 @@
5456
import { emit } from 'process'
5557
5658
import 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'
6560
import { useRoute, useRouter } from 'vue-router'
6661
6762
import {
6863
useEventListener,
6964
useIntersectionObserver,
7065
useResizeObserver
7166
} from '@vueuse/core'
72-
import { throttle } from 'throttle-debounce'
67+
import { debounce, throttle } from 'throttle-debounce'
7368
7469
import { useOpenLink } from '/@/composables/useOpenLink'
7570
import 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 // 端に到達するたびに増加させる閾値
9590
const DECAY_PER_SECOND = 0.6 // 一秒あたりの減衰率
9691
9792
/**
@@ -250,41 +245,52 @@ const bottomSkeletonKey = shallowRef(0)
250245
useResizeObserver(
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+
256253
useResizeObserver(
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
// 可視→非可視の遷移のみで再生成するため、前回の状態を追跡
265266
let wasTopVisible = false
266267
let wasBottomVisible = false
267268
268269
useIntersectionObserver(
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+
280284
useIntersectionObserver(
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
323329
const { getThreshold, update: updateThreshold } = useDynamicLoadThreshold()
324-
const { isMobile } = useResponsive()
325-
326-
const enablePreload = computed(() => !isIOS() && !isMobile.value)
327330
328331
// スケルトンの端に到達した場合にスクロール位置を戻してループさせる
329332
const SKELETON_LOOP_MARGIN = 50 // 端からのマージン
330333
331334
const 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
369372
const 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
396404
const visibilitychangeListener = () => {
397405
if (document.visibilityState === 'visible') {

src/components/Main/MainView/MessagesScroller/MessagesSkeleton.vue

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,27 +44,31 @@
4444
const props = withDefaults(
4545
defineProps<{
4646
count?: number
47+
simple?: boolean
4748
reversed?: boolean
4849
}>(),
4950
{
5051
count: 16,
52+
simple: false,
5153
reversed: false
5254
}
5355
)
5456
5557
const generateRandomMessage = () => {
5658
const lines = []
57-
const lineCount = Math.floor(Math.random() * 3) + 1
59+
const lineCount = props.simple ? 2 : Math.floor(Math.random() * 3) + 1
5860
for (let i = 0; i < lineCount; i++) {
5961
lines.push(Math.floor(Math.random() * 40) + 40)
6062
}
6163
64+
const stampCount = Math.random() < 0.3 ? Math.floor(Math.random() * 5) + 1 : 0
65+
6266
return {
6367
lines,
64-
hasQuote: Math.random() < 0.1,
65-
hasImage: Math.random() < 0.1,
66-
hasFile: Math.random() < 0.05,
67-
stamps: Math.random() < 0.3 ? Math.floor(Math.random() * 5) + 1 : 0
68+
hasQuote: props.simple ? false : Math.random() < 0.1,
69+
hasImage: props.simple ? false : Math.random() < 0.1,
70+
hasFile: props.simple ? false : Math.random() < 0.05,
71+
stamps: props.simple ? 3 : stampCount
6872
}
6973
}
7074

0 commit comments

Comments
 (0)