feat(scroll-lock): 使用 className 追踪锁定计数,优化多组件并发锁定处理
This commit is contained in:
@@ -1,8 +1,8 @@
|
|||||||
import { onBeforeUnmount, watch, type Ref, isRef, unref } from 'vue'
|
import { onBeforeUnmount, watch, type Ref, isRef, unref, computed } from 'vue'
|
||||||
import { useNuxtApp, useHead } from '#imports'
|
import { useNuxtApp, useHead } from '#imports'
|
||||||
|
|
||||||
// 全局模块级变量,用于记录锁定请求的次数,解决多组件并发锁定的竞态问题
|
// 按 className 维度追踪各锁的引用计数,解决多组件使用不同 className 并发锁定时的类名泄漏问题
|
||||||
let lenisLockCount = 0
|
const lockCounts = new Map<string, number>()
|
||||||
|
|
||||||
export const usePageScrollLock = (
|
export const usePageScrollLock = (
|
||||||
isLocked: Ref<boolean> | boolean,
|
isLocked: Ref<boolean> | boolean,
|
||||||
@@ -12,13 +12,17 @@ export const usePageScrollLock = (
|
|||||||
// 由于 $lenis 是从 client 插件中注入的,SSR 期间可能为 undefined
|
// 由于 $lenis 是从 client 插件中注入的,SSR 期间可能为 undefined
|
||||||
// 而且在 setup 期间解构可能拿不到最新的值,我们改为在函数内部动态获取
|
// 而且在 setup 期间解构可能拿不到最新的值,我们改为在函数内部动态获取
|
||||||
|
|
||||||
|
const lockClassName = options?.className ?? 'is-home-intro-active'
|
||||||
|
|
||||||
|
// SSR 和 CSR 都支持:自动将 class 注入到 HTML 标签上
|
||||||
|
// 多实例调用时 Unhead 会合并各自的 class 值
|
||||||
useHead({
|
useHead({
|
||||||
htmlAttrs: {
|
htmlAttrs: {
|
||||||
|
class: computed(() => unref(isLocked) ? lockClassName : ''),
|
||||||
lang: 'zh-CN',
|
lang: 'zh-CN',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const lockClassName = options?.className ?? 'is-home-intro-active'
|
|
||||||
let currentlyLocked = false
|
let currentlyLocked = false
|
||||||
|
|
||||||
const updateLock = (locked: boolean) => {
|
const updateLock = (locked: boolean) => {
|
||||||
@@ -29,11 +33,17 @@ export const usePageScrollLock = (
|
|||||||
// Lenis 控制仅在客户端有效,不需要依赖 $lenis 立即存在
|
// Lenis 控制仅在客户端有效,不需要依赖 $lenis 立即存在
|
||||||
if (import.meta.client) {
|
if (import.meta.client) {
|
||||||
if (locked) {
|
if (locked) {
|
||||||
lenisLockCount++
|
const prevCount = lockCounts.get(lockClassName) ?? 0
|
||||||
// 只有第一个锁请求时,才真正停止 Lenis 并滚动到顶部
|
lockCounts.set(lockClassName, prevCount + 1)
|
||||||
if (lenisLockCount === 1) {
|
|
||||||
|
// 该 className 首次被锁定时,添加对应类名
|
||||||
|
if (prevCount === 0) {
|
||||||
document.documentElement.classList.add(lockClassName)
|
document.documentElement.classList.add(lockClassName)
|
||||||
// 注意:需要在这里动态获取 $lenis,因为此时 client 插件才可能挂载完成
|
}
|
||||||
|
|
||||||
|
// 所有 className 的锁计数总和为 1 时(即全局第一个锁),停止 Lenis 并滚到顶部
|
||||||
|
const totalLocks = Array.from(lockCounts.values()).reduce((sum, c) => sum + c, 0)
|
||||||
|
if (totalLocks === 1) {
|
||||||
const lenisInstance = nuxtApp.$lenis
|
const lenisInstance = nuxtApp.$lenis
|
||||||
if (lenisInstance) {
|
if (lenisInstance) {
|
||||||
lenisInstance.scrollTo(0, { immediate: true })
|
lenisInstance.scrollTo(0, { immediate: true })
|
||||||
@@ -41,11 +51,18 @@ export const usePageScrollLock = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
lenisLockCount = Math.max(0, lenisLockCount - 1)
|
const prevCount = lockCounts.get(lockClassName) ?? 0
|
||||||
// 只有当所有锁请求都释放时,才重新启动 Lenis
|
const newCount = Math.max(0, prevCount - 1)
|
||||||
if (lenisLockCount === 0) {
|
lockCounts.set(lockClassName, newCount)
|
||||||
|
|
||||||
|
// 该 className 的锁计数归零时,移除对应类名
|
||||||
|
if (newCount === 0) {
|
||||||
document.documentElement.classList.remove(lockClassName)
|
document.documentElement.classList.remove(lockClassName)
|
||||||
// 同理,动态获取
|
}
|
||||||
|
|
||||||
|
// 所有 className 的锁计数总和为 0 时(即全局最后一个锁释放),重启 Lenis
|
||||||
|
const totalLocks = Array.from(lockCounts.values()).reduce((sum, c) => sum + c, 0)
|
||||||
|
if (totalLocks === 0) {
|
||||||
const lenisInstance = nuxtApp.$lenis
|
const lenisInstance = nuxtApp.$lenis
|
||||||
if (lenisInstance) {
|
if (lenisInstance) {
|
||||||
lenisInstance.start()
|
lenisInstance.start()
|
||||||
|
|||||||
Reference in New Issue
Block a user