滚动锁定技术解析:以Ant Design的useScrollLocker为例

一、滚动锁定的必要性

当模态框(Modal)、侧边抽屉(Drawer)等组件被激活时,为防止背景内容滚动造成的视觉干扰和操作失误,通常需要锁定页面滚动。然而,直接设置 body.style.overflow = 'hidden' 会引入以下问题:

  1. 页面布局跳动: 滚动条突然消失会导致页面内容宽度瞬间变化,引发布局跳动。
  2. 多弹窗样式冲突: 在多个弹窗嵌套或依次打开的场景下,对 body 样式的直接修改容易产生覆盖或冲突。
  3. 浏览器兼容性问题: 不同浏览器对滚动条的处理可能存在细微差异,直接操作 body 样式可能无法在所有环境下完美工作。

二、Ant Design 的解决方案:useScrollLocker

Ant Design 通过 useScrollLocker 钩子函数实现了更智能的滚动控制。其核心思路是:

  1. 隐藏滚动条: 使用 overflow-y: hidden 阻止背景滚动。
  2. 保留滚动条空间: 通过计算并补偿滚动条宽度,避免布局跳动。

关键源码解析:

javascript 复制代码
export default function useScrollLocker(lock?: Ref<boolean>) {
  const mergedLock = computed(() => !!lock && !!lock.value); // 确定是否需要锁定
  uuid += 1; // 生成唯一ID
  const id = `${UNIQUE_ID}_${uuid}`; // 构造样式表唯一标识

  watchEffect(
    onClear => {
      if (!canUseDom()) return; // 确保在DOM环境中执行

      if (mergedLock.value) {
        const scrollbarSize = getScrollBarSize(); // 获取当前滚动条宽度
        const isOverflow = isBodyOverflowing(); // 判断body是否原本有滚动条

        // 动态注入CSS规则
        updateCSS(
          `
html body {
  overflow-y: hidden; // 核心:隐藏垂直滚动条
  ${isOverflow ? `width: calc(100% - ${scrollbarSize}px);` : ''} // 核心:补偿滚动条宽度(仅在需要时)
}`,
          id // 使用唯一ID标识样式
        );
      } else {
        removeCSS(id); // 移除注入的CSS规则
      }
      onClear(() => {
        removeCSS(id); // 组件卸载时清理样式
      });
    },
    { flush: 'post' } // 确保在DOM更新后执行
  );
}

方案亮点解析:

  • 滚动条空间补偿 (width: calc(100% - ${scrollbarSize}px)): 这是解决布局跳动的关键。当页面原本有滚动条时 (isOverflowtrue),通过将 body 的宽度计算为视口宽度减去滚动条宽度 (scrollbarSize),预留出滚动条消失后的空间,保持页面布局稳定。
  • 唯一标识 (uuid): uuid += 1; 确保每次调用 useScrollLocker 生成的 CSS 规则具有唯一标识 (id)。这解决了多弹窗场景下的样式冲突问题:每个锁定的弹窗独立添加自己的补偿样式规则;只有当所有锁定的弹窗都关闭(所有对应的样式规则被移除)后,页面滚动才会恢复。
  • 按需补偿: 只有 isBodyOverflowing()true(即页面原本有垂直滚动条需要占用空间)时,才会应用 calc(100% - ${scrollbarSize}px) 的宽度补偿规则,避免不必要的样式覆盖。
  • 资源管理: 使用 onClearremoveCSS 确保组件卸载或锁定状态解除时,能及时清理注入的 CSS 规则,防止内存泄漏和样式残留。

总结

滚动锁定的核心目标是隐藏滚动条 (通过 overflow-y: hidden)以阻止背景滚动。其关键在于:隐藏滚动条会导致页面内容宽度瞬间增加(等于滚动条宽度),引发布局跳动

Ant Design useScrollLocker 的解决方案重点在于动态计算并补偿滚动条宽度

  1. 使用 getScrollBarSize() 获取精确的滚动条宽度。
  2. 在需要锁定滚动时,通过设置 body 的宽度为 calc(100% - ${scrollbarSize}px) 预留出滚动条消失后的空间,从而保持页面整体宽度不变,完美消除布局跳动。

补充说明: 固定内容宽度、避免跳动的另一种常见方案是给 body 添加 padding-right: ${scrollbarSize}px。其原理也是预留滚动条空间,但实现方式与修改 width 不同。useScrollLocker 采用的是 width 计算的方式。

相关推荐
落霞的思绪32 分钟前
配置React和React-dom为CDN引入
前端·react.js·前端框架
Hacker_Z&Q34 分钟前
CSS 笔记2 (属性)
前端·css·笔记
Anastasiozzzz42 分钟前
LeetCode Hot100 295. 数据流的中位数 MedianFinder
java·服务器·前端
Exquisite.1 小时前
Nginx
服务器·前端·nginx
打小就很皮...2 小时前
dnd-kit 实现表格拖拽排序
前端·react.js·表格拖拽·dnd-kit
Ulyanov2 小时前
从静态到沉浸:打造惊艳的Web技术发展历程3D时间轴
前端·javascript·html5·gui开发
打小就很皮...2 小时前
React 19 + Vite 6 + SWC 构建优化实践
前端·react.js·vite·swc
Highcharts.js2 小时前
使用Highcharts与React集成 官网文档使用说明
前端·react.js·前端框架·react·highcharts·官方文档
这是个栗子2 小时前
AI辅助编程(二) - 通译千问
前端·ai·通译千问
VT.馒头2 小时前
【力扣】2625. 扁平化嵌套数组
前端·javascript·算法·leetcode·职场和发展·typescript