refactor(composables): 改进页面滚动锁定逻辑并修复潜在问题
确保在客户端插件加载完成后再获取 Lenis 实例 修复组件卸载时可能的状态不一致问题 将 useHead 配置整合到 usePageScrollLock 中
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, onBeforeUnmount } from 'vue'
|
import { ref, onMounted, onBeforeUnmount, computed } from 'vue'
|
||||||
import gsap from 'gsap'
|
import gsap from 'gsap'
|
||||||
import { useAppStore } from '~/stores/app'
|
import { useAppStore } from '~/stores/app'
|
||||||
import { usePageScrollLock } from '~/composables/usePageScrollLock'
|
import { usePageScrollLock } from '~/composables/usePageScrollLock'
|
||||||
@@ -8,8 +8,8 @@ const loaderRef = ref<HTMLElement | null>(null)
|
|||||||
const loaderBarRef = ref<HTMLElement | null>(null)
|
const loaderBarRef = ref<HTMLElement | null>(null)
|
||||||
const appStore = useAppStore()
|
const appStore = useAppStore()
|
||||||
|
|
||||||
// 加载期间锁定页面滚动
|
// 加载期间锁定页面滚动(注意:Pinia 的 state 是自动解包的,必须用 computed 包装才能保持响应式)
|
||||||
usePageScrollLock(appStore.isAppLoading)
|
usePageScrollLock(computed(() => appStore.isAppLoading))
|
||||||
|
|
||||||
let loaderTween: gsap.core.Tween | null = null
|
let loaderTween: gsap.core.Tween | null = null
|
||||||
let removeLoadListener: (() => void) | null = null
|
let removeLoadListener: (() => void) | null = null
|
||||||
|
|||||||
@@ -6,7 +6,9 @@ import type Lenis from 'lenis'
|
|||||||
let lenisLockCount = 0
|
let lenisLockCount = 0
|
||||||
|
|
||||||
export const usePageScrollLock = (isLocked: Ref<boolean> | boolean) => {
|
export const usePageScrollLock = (isLocked: Ref<boolean> | boolean) => {
|
||||||
const { $lenis } = useNuxtApp() as ReturnType<typeof useNuxtApp> & { $lenis?: Lenis }
|
const nuxtApp = useNuxtApp()
|
||||||
|
// 由于 $lenis 是从 client 插件中注入的,SSR 期间可能为 undefined
|
||||||
|
// 而且在 setup 期间解构可能拿不到最新的值,我们改为在函数内部动态获取
|
||||||
|
|
||||||
// SSR 和 CSR 都支持:自动将 class 注入到 HTML 标签上
|
// SSR 和 CSR 都支持:自动将 class 注入到 HTML 标签上
|
||||||
useHead({
|
useHead({
|
||||||
@@ -22,16 +24,18 @@ export const usePageScrollLock = (isLocked: Ref<boolean> | boolean) => {
|
|||||||
if (locked === currentlyLocked) return
|
if (locked === currentlyLocked) return
|
||||||
currentlyLocked = locked
|
currentlyLocked = locked
|
||||||
|
|
||||||
// Lenis 控制仅在客户端有效
|
// Lenis 控制仅在客户端有效,不需要依赖 $lenis 立即存在
|
||||||
if (import.meta.client) {
|
if (import.meta.client) {
|
||||||
if (locked) {
|
if (locked) {
|
||||||
lenisLockCount++
|
lenisLockCount++
|
||||||
// 只有第一个锁请求时,才真正停止 Lenis 并滚动到顶部
|
// 只有第一个锁请求时,才真正停止 Lenis 并滚动到顶部
|
||||||
if (lenisLockCount === 1) {
|
if (lenisLockCount === 1) {
|
||||||
document.documentElement.classList.add('is-home-intro-active')
|
document.documentElement.classList.add('is-home-intro-active')
|
||||||
if ($lenis) {
|
// 注意:需要在这里动态获取 $lenis,因为此时 client 插件才可能挂载完成
|
||||||
$lenis.scrollTo(0, { immediate: true })
|
const lenisInstance = (nuxtApp as any).$lenis
|
||||||
$lenis.stop()
|
if (lenisInstance) {
|
||||||
|
lenisInstance.scrollTo(0, { immediate: true })
|
||||||
|
lenisInstance.stop()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -39,8 +43,10 @@ export const usePageScrollLock = (isLocked: Ref<boolean> | boolean) => {
|
|||||||
// 只有当所有锁请求都释放时,才重新启动 Lenis
|
// 只有当所有锁请求都释放时,才重新启动 Lenis
|
||||||
if (lenisLockCount === 0) {
|
if (lenisLockCount === 0) {
|
||||||
document.documentElement.classList.remove('is-home-intro-active')
|
document.documentElement.classList.remove('is-home-intro-active')
|
||||||
if ($lenis) {
|
// 同理,动态获取
|
||||||
$lenis.start()
|
const lenisInstance = (nuxtApp as any).$lenis
|
||||||
|
if (lenisInstance) {
|
||||||
|
lenisInstance.start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -56,6 +62,8 @@ export const usePageScrollLock = (isLocked: Ref<boolean> | boolean) => {
|
|||||||
|
|
||||||
// 组件卸载时安全释放当前持有的锁
|
// 组件卸载时安全释放当前持有的锁
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
|
if (currentlyLocked) {
|
||||||
updateLock(false)
|
updateLock(false)
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-10
@@ -1,7 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, ref, watch } from 'vue'
|
import { ref, watch } from 'vue'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { useHead, useSeoMeta } from '#imports'
|
import { useSeoMeta } from '#imports'
|
||||||
import { useAppStore } from '~/stores/app'
|
import { useAppStore } from '~/stores/app'
|
||||||
import { usePageScrollLock } from '~/composables/usePageScrollLock'
|
import { usePageScrollLock } from '~/composables/usePageScrollLock'
|
||||||
import HomeIntro from '~/components/home/HomeIntro.vue'
|
import HomeIntro from '~/components/home/HomeIntro.vue'
|
||||||
@@ -12,14 +12,7 @@ const appStore = useAppStore()
|
|||||||
|
|
||||||
const isIntroActive = ref(true)
|
const isIntroActive = ref(true)
|
||||||
|
|
||||||
// 保证即使 Lenis 还没加载完成,也能通过原生 CSS 锁定页面
|
// 使用组合式函数管理 Lenis 滚动锁定(已包含 useHead 配置)
|
||||||
useHead({
|
|
||||||
htmlAttrs: {
|
|
||||||
class: computed(() => (isIntroActive.value ? 'is-home-intro-active' : '')),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// 使用组合式函数管理 Lenis 滚动锁定
|
|
||||||
usePageScrollLock(isIntroActive)
|
usePageScrollLock(isIntroActive)
|
||||||
|
|
||||||
// 首页的 SEO 和 Meta 优化,支持国际化响应式
|
// 首页的 SEO 和 Meta 优化,支持国际化响应式
|
||||||
|
|||||||
Reference in New Issue
Block a user