|
1 | 1 | /* eslint-disable no-param-reassign */
|
| 2 | +import { removeCSS, updateCSS } from './Dom/dynamicCSS'; |
2 | 3 |
|
3 |
| -let cached: number; |
| 4 | +type ScrollBarSize = { width: number; height: number }; |
4 | 5 |
|
5 |
| -export default function getScrollBarSize(fresh?: boolean) { |
6 |
| - if (typeof document === 'undefined') { |
7 |
| - return 0; |
8 |
| - } |
| 6 | +type ExtendCSSStyleDeclaration = CSSStyleDeclaration & { |
| 7 | + scrollbarColor?: string; |
| 8 | + scrollbarWidth?: string; |
| 9 | +}; |
9 | 10 |
|
10 |
| - if (fresh || cached === undefined) { |
11 |
| - const inner = document.createElement('div'); |
12 |
| - inner.style.width = '100%'; |
13 |
| - inner.style.height = '200px'; |
| 11 | +let cached: ScrollBarSize; |
14 | 12 |
|
15 |
| - const outer = document.createElement('div'); |
16 |
| - const outerStyle = outer.style; |
| 13 | +function measureScrollbarSize(ele?: HTMLElement): ScrollBarSize { |
| 14 | + const randomId = `rc-scrollbar-measure-${Math.random() |
| 15 | + .toString(36) |
| 16 | + .substring(7)}`; |
| 17 | + const measureEle = document.createElement('div'); |
| 18 | + measureEle.id = randomId; |
17 | 19 |
|
18 |
| - outerStyle.position = 'absolute'; |
19 |
| - outerStyle.top = '0'; |
20 |
| - outerStyle.left = '0'; |
21 |
| - outerStyle.pointerEvents = 'none'; |
22 |
| - outerStyle.visibility = 'hidden'; |
23 |
| - outerStyle.width = '200px'; |
24 |
| - outerStyle.height = '150px'; |
25 |
| - outerStyle.overflow = 'hidden'; |
| 20 | + // Create Style |
| 21 | + const measureStyle: ExtendCSSStyleDeclaration = measureEle.style; |
| 22 | + measureStyle.position = 'absolute'; |
| 23 | + measureStyle.left = '0'; |
| 24 | + measureStyle.top = '0'; |
| 25 | + measureStyle.width = '100px'; |
| 26 | + measureStyle.height = '100px'; |
| 27 | + measureStyle.overflow = 'scroll'; |
26 | 28 |
|
27 |
| - outer.appendChild(inner); |
| 29 | + // Clone Style if needed |
| 30 | + let fallbackWidth: number; |
| 31 | + let fallbackHeight: number; |
| 32 | + if (ele) { |
| 33 | + const targetStyle: ExtendCSSStyleDeclaration = getComputedStyle(ele); |
| 34 | + measureStyle.scrollbarColor = targetStyle.scrollbarColor; |
| 35 | + measureStyle.scrollbarWidth = targetStyle.scrollbarWidth; |
28 | 36 |
|
29 |
| - document.body.appendChild(outer); |
| 37 | + // Set Webkit style |
| 38 | + const webkitScrollbarStyle = getComputedStyle(ele, '::-webkit-scrollbar'); |
30 | 39 |
|
31 |
| - const widthContained = inner.offsetWidth; |
32 |
| - outer.style.overflow = 'scroll'; |
33 |
| - let widthScroll = inner.offsetWidth; |
| 40 | + // Try wrap to handle CSP case |
| 41 | + try { |
| 42 | + updateCSS( |
| 43 | + ` |
| 44 | + #${randomId}::-webkit-scrollbar { |
| 45 | + width: ${webkitScrollbarStyle.width}; |
| 46 | + height: ${webkitScrollbarStyle.height}; |
| 47 | + } |
| 48 | + `, |
| 49 | + randomId, |
| 50 | + ); |
| 51 | + } catch (e) { |
| 52 | + // Can't wrap, just log error |
| 53 | + console.error(e); |
34 | 54 |
|
35 |
| - if (widthContained === widthScroll) { |
36 |
| - widthScroll = outer.clientWidth; |
| 55 | + // Get from style directly |
| 56 | + fallbackWidth = parseInt(webkitScrollbarStyle.width, 10); |
| 57 | + fallbackHeight = parseInt(webkitScrollbarStyle.height, 10); |
37 | 58 | }
|
| 59 | + } |
38 | 60 |
|
39 |
| - document.body.removeChild(outer); |
| 61 | + document.body.appendChild(measureEle); |
40 | 62 |
|
41 |
| - cached = widthContained - widthScroll; |
42 |
| - } |
43 |
| - return cached; |
| 63 | + // Measure. Get fallback style if provided |
| 64 | + const scrollWidth = |
| 65 | + ele && fallbackWidth && !isNaN(fallbackWidth) |
| 66 | + ? fallbackWidth |
| 67 | + : measureEle.offsetWidth - measureEle.clientWidth; |
| 68 | + const scrollHeight = |
| 69 | + ele && fallbackHeight && !isNaN(fallbackHeight) |
| 70 | + ? fallbackHeight |
| 71 | + : measureEle.offsetHeight - measureEle.clientHeight; |
| 72 | + |
| 73 | + // Clean up |
| 74 | + document.body.removeChild(measureEle); |
| 75 | + removeCSS(randomId); |
| 76 | + |
| 77 | + return { |
| 78 | + width: scrollWidth, |
| 79 | + height: scrollHeight, |
| 80 | + }; |
44 | 81 | }
|
45 | 82 |
|
46 |
| -function ensureSize(str: string) { |
47 |
| - const match = str.match(/^(.*)px$/); |
48 |
| - const value = Number(match?.[1]); |
49 |
| - return Number.isNaN(value) ? getScrollBarSize() : value; |
| 83 | +export default function getScrollBarSize(fresh?: boolean): number { |
| 84 | + if (typeof document === 'undefined') { |
| 85 | + return 0; |
| 86 | + } |
| 87 | + |
| 88 | + if (fresh || cached === undefined) { |
| 89 | + cached = measureScrollbarSize(); |
| 90 | + } |
| 91 | + return cached.width; |
50 | 92 | }
|
51 | 93 |
|
52 | 94 | export function getTargetScrollBarSize(target: HTMLElement) {
|
53 |
| - if (typeof document === 'undefined' || !target || !(target instanceof Element)) { |
| 95 | + if ( |
| 96 | + typeof document === 'undefined' || |
| 97 | + !target || |
| 98 | + !(target instanceof Element) |
| 99 | + ) { |
54 | 100 | return { width: 0, height: 0 };
|
55 | 101 | }
|
56 | 102 |
|
57 |
| - const { width, height } = getComputedStyle(target, '::-webkit-scrollbar'); |
58 |
| - return { |
59 |
| - width: ensureSize(width), |
60 |
| - height: ensureSize(height), |
61 |
| - }; |
| 103 | + return measureScrollbarSize(target); |
62 | 104 | }
|
0 commit comments