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: 修复 ScrollTrigger 在特定情况下无法触发加载的问题 #4430

Merged
merged 1 commit into from
Nov 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<VideoCard v-for="c of cards" :key="c.id" :data="c" />
</div>
<VEmpty v-if="!loading && cards.length === 0" />
<ScrollTrigger v-if="!error" @trigger="loadCards" />
<ScrollTrigger v-if="!error" ref="scrollTrigger" detect-viewport @trigger="loadCards" />
<MinimalHomeOperations v-if="cards.length > 0" @refresh="refresh" />
</div>
</template>
Expand Down Expand Up @@ -43,19 +43,25 @@ export default Vue.extend({
try {
this.error = false
this.loading = true
this.$refs.scrollTrigger.setLoadState('loading')
this.cards = lodash.uniqBy(
[...this.cards, ...(await getVideoFeeds('video', this.lastID))],
it => it.id,
)
} catch (error) {
logError(error)
this.error = true
this.$refs.scrollTrigger.setLoadState('error')
} finally {
this.loading = false
if (this.loaded) {
this.$refs.scrollTrigger.setLoadState('loaded')
}
}
},
async refresh() {
this.cards = []
this.$refs.scrollTrigger.resetIsFirstLoad()
},
},
})
Expand Down
68 changes: 67 additions & 1 deletion src/ui/ScrollTrigger.vue
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,89 @@
</template>
<script lang="ts">
import { useScopedConsole } from '@/core/utils/log'
import { delay } from '@/core/utils/'

export default Vue.extend({
components: {
VLoading: () => import('./VLoading.vue').then(m => m.default),
},
props: {
detectViewport: {
type: Boolean,
required: false,
default: false,
},
},
data() {
return {
isFirstLoad: true,
isViewportTriggerRunning: false,
parentLoadState: 'none',
}
},
async mounted() {
const console = useScopedConsole('ScrollTrigger')
const element = this.$el as HTMLElement
const { visible } = await import('@/core/observer')
visible(element, records => {
visible(element, async records => {
if (records.some(r => r.intersectionRatio > 0)) {
console.log('Intersection Observer trigger')
this.trigger()

if (!this.detectViewport && !this.isFirstLoad && this.isViewportTriggerRunning) {
return
}

this.isViewportTriggerRunning = true
while (this.getVisibleStateByViewport(element)) {
await delay(500)

// 需要父组件配合 setLoadState,不然会一直 continue
// 确认父组件加载状态,等待加载完成后再继续执行,否则会出现多次加载的情况
if (this.parentLoadState !== 'loaded') {
continue
}

// 再次确认元素可视情况,避免当元素已不可视时仍然触发加载
if (!this.getVisibleStateByViewport(element)) {
break
}

console.log('is first load & viewport trigger')
this.trigger()
}
this.isFirstLoad = false
}
})
},
methods: {
/**
* 当元素顶部位于视口内部时返回为正数,位于视口外部时返回为负数,正好位于视口底部时返回为零。
* @param element HTML 元素
* @returns 元素顶部到视口底部的距离
*/
getElementToViewportBottomDistance(element: HTMLElement): number {
return document.documentElement.clientHeight - element.getBoundingClientRect().top
the1812 marked this conversation as resolved.
Show resolved Hide resolved
},

/**
* 当元素顶部在视口内部时返回 true。
* 为避免极端情况,当元素顶部到视口底部的距离大于 -20 时同样返回 true。
* @param element HTML 元素
* @returns 元素可视情况
*/
getVisibleStateByViewport(element: HTMLElement): boolean {
return Boolean(this.getElementToViewportBottomDistance(element) > -20)
},

setLoadState(loadState: 'loading' | 'error' | 'loaded') {
this.parentLoadState = loadState
},

resetIsFirstLoad() {
this.isFirstLoad = true
},

trigger() {
this.$emit('trigger')
},
Expand Down
Loading