diff --git a/packages/shared/src/helpers/scroll.ts b/packages/shared/src/helpers/scroll.ts index 94069c64..4e8dfeec 100644 --- a/packages/shared/src/helpers/scroll.ts +++ b/packages/shared/src/helpers/scroll.ts @@ -1,38 +1,108 @@ import { getDocument } from 'ssr-window'; import { $ } from '@mdui/jq/$.js'; import '@mdui/jq/methods/addClass.js'; +import '@mdui/jq/methods/append.js'; +import '@mdui/jq/methods/appendTo.js'; +import '@mdui/jq/methods/css.js'; +import '@mdui/jq/methods/remove.js'; import '@mdui/jq/methods/removeClass.js'; +import '@mdui/jq/methods/width.js'; +import { isUndefined } from '@mdui/jq/shared/helper.js'; -const locks = new Set(); -const CSS_CLASS = 'mdui-lock-screen'; +// 缓存滚动条宽度 +let scrollBarSizeCached: number; + +/** + * 获取滚动条宽度 + * @param fresh 是否重新计算 + */ +export const getScrollBarSize = (fresh?: boolean): number => { + if (isUndefined(document)) { + return 0; + } + + if (fresh || scrollBarSizeCached === undefined) { + const $inner = $('
').css({ + width: '100%', + height: '200px', + }); + + const $outer = $('
') + .css({ + position: 'absolute', + top: '0', + left: '0', + pointerEvents: 'none', + visibility: 'hidden', + width: '200px', + height: '150px', + overflow: 'hidden', + }) + .append($inner) + .appendTo(document.body); + + const widthContained = $inner[0].offsetWidth; + $outer.css('overflow', 'scroll'); + let widthScroll = $inner[0].offsetWidth; + + if (widthContained === widthScroll) { + widthScroll = $outer[0].clientWidth; + } + + $outer.remove(); + + scrollBarSizeCached = widthContained - widthScroll; + } + + return scrollBarSizeCached; +}; + +const lockMap = new WeakMap< + HTMLElement, // 被锁定的元素 + Set // 触发锁定的元素 +>(); +const className = 'mdui-lock-screen'; /** * 锁定指定元素,禁止滚动。对同一个元素多次调用此方法,只会锁定一次 - * @param element + * @param source 由该元素触发锁定 * @param target 锁定该元素的滚动状态,默认为 body */ -export const lockScreen = ( - element: HTMLElement, - target?: HTMLElement, -): void => { +export const lockScreen = (source: HTMLElement, target?: HTMLElement): void => { const document = getDocument(); - locks.add(element); - $(target || document.body).addClass(CSS_CLASS); + + target ??= document.body; + + if (!lockMap.has(target)) { + lockMap.set(target, new Set()); + } + + const lock = lockMap.get(target)!; + lock.add(source); + + $(target) + .addClass(className) + .css('width', `calc(100% - ${getScrollBarSize()}px)`); }; /** * 解除指定元素的滚动状态锁定。 - * @param element + * @param source 由该元素触发锁定 * @param target 锁定该元素的滚动状态,默认为 body */ export const unlockScreen = ( - element: HTMLElement, + source: HTMLElement, target?: HTMLElement, ): void => { const document = getDocument(); - locks.delete(element); - if (locks.size === 0) { - $(target || document.body).removeClass(CSS_CLASS); + target ??= document.body; + + const lock = lockMap.get(target)!; + lock.delete(source); + + if (lock.size === 0) { + lockMap.delete(target); + $(target).removeClass(className).width(''); } };