diff --git a/app/app.vue b/app/app.vue
index 784ee14..976880d 100644
--- a/app/app.vue
+++ b/app/app.vue
@@ -1,7 +1,6 @@
diff --git a/app/composables/usePageScrollLock.ts b/app/composables/usePageScrollLock.ts
index 961e756..8426b1b 100644
--- a/app/composables/usePageScrollLock.ts
+++ b/app/composables/usePageScrollLock.ts
@@ -1,50 +1,55 @@
-import { onMounted, onBeforeUnmount, watch, type Ref } from 'vue'
-import { useNuxtApp } from '#app'
+import { onBeforeUnmount, watch, type Ref, isRef, unref, computed } from 'vue'
+import { useNuxtApp, useHead } from '#imports'
import type Lenis from 'lenis'
-import { useAppStore } from '~/stores/app'
+
+// 全局模块级变量,用于记录锁定请求的次数,解决多组件并发锁定的竞态问题
+let lenisLockCount = 0
export const usePageScrollLock = (isLocked: Ref | boolean) => {
const { $lenis } = useNuxtApp() as ReturnType & { $lenis?: Lenis }
- const lockScroll = () => {
- if (import.meta.client) {
- document.documentElement.classList.add('is-home-intro-active')
- $lenis?.scrollTo(0, { immediate: true })
- $lenis?.stop()
+ // SSR 和 CSR 都支持:自动将 class 注入到 HTML 标签上
+ useHead({
+ htmlAttrs: {
+ class: computed(() => unref(isLocked) ? 'is-home-intro-active' : undefined)
}
- }
+ })
- const unlockScroll = () => {
- if (import.meta.client) {
- document.documentElement.classList.remove('is-home-intro-active')
- $lenis?.start()
- }
- }
+ let currentlyLocked = false
- // If passed a boolean, lock immediately and unlock on unmount
- if (typeof isLocked === 'boolean') {
- if (isLocked) {
- onMounted(() => {
- lockScroll()
- })
- onBeforeUnmount(() => {
- unlockScroll()
- })
- }
- } else {
- // If passed a ref, watch its value
- watch(isLocked, (locked) => {
+ const updateLock = (locked: boolean) => {
+ // 防止重复触发相同状态
+ if (locked === currentlyLocked) return
+ currentlyLocked = locked
+
+ // Lenis 控制仅在客户端有效
+ if (import.meta.client && $lenis) {
if (locked) {
- lockScroll()
+ lenisLockCount++
+ // 只有第一个锁请求时,才真正停止 Lenis 并滚动到顶部
+ if (lenisLockCount === 1) {
+ $lenis.scrollTo(0, { immediate: true })
+ $lenis.stop()
+ }
} else {
- unlockScroll()
+ lenisLockCount = Math.max(0, lenisLockCount - 1)
+ // 只有当所有锁请求都释放时,才重新启动 Lenis
+ if (lenisLockCount === 0) {
+ $lenis.start()
+ }
}
- }, { immediate: true })
-
- onBeforeUnmount(() => {
- unlockScroll()
- })
+ }
}
- return { lockScroll, unlockScroll }
+ // 监听 Ref 变化,或立即处理 boolean 值
+ if (isRef(isLocked)) {
+ watch(isLocked, updateLock, { immediate: true })
+ } else {
+ updateLock(isLocked)
+ }
+
+ // 组件卸载时安全释放当前持有的锁
+ onBeforeUnmount(() => {
+ updateLock(false)
+ })
}
diff --git a/app/pages/index.vue b/app/pages/index.vue
index 0fb0cf9..c857907 100644
--- a/app/pages/index.vue
+++ b/app/pages/index.vue
@@ -20,13 +20,6 @@ if (!userStore.isLoggedIn) {
userStore.loadDemoUser()
}
-// 让首屏入场的锁滚动在 SSR/首帧就生效,避免等 onMounted 才隐藏滚动条。
-useHead({
- htmlAttrs: {
- class: computed(() => (isIntroActive.value ? 'is-home-intro-active' : undefined)),
- },
-})
-
// 使用组合式函数管理 Lenis 滚动锁定
usePageScrollLock(isIntroActive)