refactor(滚动锁定): 重构页面滚动锁定逻辑
- 使用全局计数器解决多组件并发锁定竞态问题 - 将 HTML class 注入逻辑移至 composable 中统一管理 - 优化锁定状态更新逻辑,避免重复操作 - 移除不再需要的计算属性和导入
This commit is contained in:
@@ -1,7 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useHead, useSeoMeta } from '#imports'
|
import { useHead, useSeoMeta } from '#imports'
|
||||||
import { useI18n } from 'vue-i18n'
|
import { useI18n } from 'vue-i18n'
|
||||||
import { computed } from 'vue'
|
|
||||||
|
|
||||||
const { t } = useI18n()
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ onBeforeUnmount(() => {
|
|||||||
cleanupHeroScroll?.()
|
cleanupHeroScroll?.()
|
||||||
})
|
})
|
||||||
|
|
||||||
defineExpose({ heroStageRef, heroRef, play })
|
defineExpose({ play })
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
|
|||||||
@@ -1,50 +1,55 @@
|
|||||||
import { onMounted, onBeforeUnmount, watch, type Ref } from 'vue'
|
import { onBeforeUnmount, watch, type Ref, isRef, unref, computed } from 'vue'
|
||||||
import { useNuxtApp } from '#app'
|
import { useNuxtApp, useHead } from '#imports'
|
||||||
import type Lenis from 'lenis'
|
import type Lenis from 'lenis'
|
||||||
import { useAppStore } from '~/stores/app'
|
|
||||||
|
// 全局模块级变量,用于记录锁定请求的次数,解决多组件并发锁定的竞态问题
|
||||||
|
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 { $lenis } = useNuxtApp() as ReturnType<typeof useNuxtApp> & { $lenis?: Lenis }
|
||||||
|
|
||||||
const lockScroll = () => {
|
// SSR 和 CSR 都支持:自动将 class 注入到 HTML 标签上
|
||||||
if (import.meta.client) {
|
useHead({
|
||||||
document.documentElement.classList.add('is-home-intro-active')
|
htmlAttrs: {
|
||||||
$lenis?.scrollTo(0, { immediate: true })
|
class: computed(() => unref(isLocked) ? 'is-home-intro-active' : undefined)
|
||||||
$lenis?.stop()
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
const unlockScroll = () => {
|
|
||||||
if (import.meta.client) {
|
|
||||||
document.documentElement.classList.remove('is-home-intro-active')
|
|
||||||
$lenis?.start()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// If passed a boolean, lock immediately and unlock on unmount
|
|
||||||
if (typeof isLocked === 'boolean') {
|
|
||||||
if (isLocked) {
|
|
||||||
onMounted(() => {
|
|
||||||
lockScroll()
|
|
||||||
})
|
})
|
||||||
onBeforeUnmount(() => {
|
|
||||||
unlockScroll()
|
let currentlyLocked = false
|
||||||
})
|
|
||||||
}
|
const updateLock = (locked: boolean) => {
|
||||||
} else {
|
// 防止重复触发相同状态
|
||||||
// If passed a ref, watch its value
|
if (locked === currentlyLocked) return
|
||||||
watch(isLocked, (locked) => {
|
currentlyLocked = locked
|
||||||
|
|
||||||
|
// Lenis 控制仅在客户端有效
|
||||||
|
if (import.meta.client && $lenis) {
|
||||||
if (locked) {
|
if (locked) {
|
||||||
lockScroll()
|
lenisLockCount++
|
||||||
|
// 只有第一个锁请求时,才真正停止 Lenis 并滚动到顶部
|
||||||
|
if (lenisLockCount === 1) {
|
||||||
|
$lenis.scrollTo(0, { immediate: true })
|
||||||
|
$lenis.stop()
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
unlockScroll()
|
lenisLockCount = Math.max(0, lenisLockCount - 1)
|
||||||
|
// 只有当所有锁请求都释放时,才重新启动 Lenis
|
||||||
|
if (lenisLockCount === 0) {
|
||||||
|
$lenis.start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, { immediate: true })
|
|
||||||
|
|
||||||
|
// 监听 Ref 变化,或立即处理 boolean 值
|
||||||
|
if (isRef(isLocked)) {
|
||||||
|
watch(isLocked, updateLock, { immediate: true })
|
||||||
|
} else {
|
||||||
|
updateLock(isLocked)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 组件卸载时安全释放当前持有的锁
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
unlockScroll()
|
updateLock(false)
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
return { lockScroll, unlockScroll }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,13 +20,6 @@ if (!userStore.isLoggedIn) {
|
|||||||
userStore.loadDemoUser()
|
userStore.loadDemoUser()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 让首屏入场的锁滚动在 SSR/首帧就生效,避免等 onMounted 才隐藏滚动条。
|
|
||||||
useHead({
|
|
||||||
htmlAttrs: {
|
|
||||||
class: computed(() => (isIntroActive.value ? 'is-home-intro-active' : undefined)),
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
// 使用组合式函数管理 Lenis 滚动锁定
|
// 使用组合式函数管理 Lenis 滚动锁定
|
||||||
usePageScrollLock(isIntroActive)
|
usePageScrollLock(isIntroActive)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user