Skip to content

Commit e96b0c6

Browse files
authored
fix: scrollbar measure should consider scrollbar-color::webkit-scrollbar mixing (#507)
* docs: update demo * chore: fix style shaking * test: update testcase * chore: comment
1 parent 871c2c1 commit e96b0c6

3 files changed

Lines changed: 142 additions & 89 deletions

File tree

‎docs/examples/getScrollBarSize.tsx‎

Lines changed: 53 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,83 @@
1-
importReactfrom'react';
21
importgetScrollBarSize,{
32
getTargetScrollBarSize,
43
}from'rc-util/es/getScrollBarSize';
4+
importReactfrom'react';
5+
6+
constcssText=`
7+
#customizeContainer::-webkit-scrollbar {
8+
width: 2em;
9+
height: 23px;
10+
background: blue;
11+
}
12+
13+
#customizeContainer::-webkit-scrollbar-thumb {
14+
background: red;
15+
height: 30px;
16+
}
17+
18+
#scrollContainer {
19+
scrollbar-color: red orange;
20+
scrollbar-width: thin;
21+
}
22+
`;
523

624
exportdefault()=>{
7-
constdivRef=React.useRef<HTMLDivElement>();
25+
constwebkitRef=React.useRef<HTMLDivElement>();
26+
constscrollRef=React.useRef<HTMLDivElement>();
827
const[sizeData,setSizeData]=React.useState('');
928

1029
React.useEffect(()=>{
1130
constoriginSize=getScrollBarSize();
12-
consttargetSize=getTargetScrollBarSize(divRef.current);
31+
constwebkitSize=getTargetScrollBarSize(webkitRef.current);
32+
constscrollSize=getTargetScrollBarSize(scrollRef.current);
1333

1434
setSizeData(
15-
`Origin: ${originSize}, Target: ${targetSize.width}/${targetSize.height}`,
35+
[
36+
`Origin: ${originSize}`,
37+
`Webkit: ${webkitSize.width}/${webkitSize.height}`,
38+
`Webkit: ${scrollSize.width}/${scrollSize.height}`,
39+
].join(', '),
1640
);
1741
},[]);
1842

1943
return(
2044
<div>
2145
<style
2246
dangerouslySetInnerHTML={{
23-
__html: `
24-
#customizeContainer::-webkit-scrollbar {
25-
width: 2em;
26-
height: 23px;
27-
background: blue;
28-
}
29-
30-
#customizeContainer::-webkit-scrollbar-thumb {
31-
background: red;
32-
height: 30px;
33-
}
34-
`,
47+
__html: cssText,
3548
}}
3649
/>
37-
<div
38-
style={{width: 100,height: 100,overflow: 'auto'}}
39-
id="customizeContainer"
40-
ref={divRef}
41-
>
42-
<divstyle={{width: '100vw',height: '100vh',background: 'green'}}>
43-
Hello World!
44-
</div>
45-
</div>
4650

4751
<div
4852
style={{
49-
width: 100,
53+
width: 300,
5054
height: 100,
5155
overflow: 'scroll',
5256
background: 'yellow',
5357
}}
54-
/>
58+
>
59+
Origin
60+
</div>
61+
62+
<div
63+
style={{width: 300,height: 100,overflow: 'auto'}}
64+
id="customizeContainer"
65+
ref={webkitRef}
66+
>
67+
<divstyle={{width: '200vw',height: '200vh',background: 'yellow'}}>
68+
Customize `-webkit-scrollbar`
69+
</div>
70+
</div>
71+
72+
<div
73+
style={{width: 300,height: 100,overflow: 'auto'}}
74+
id="scrollContainer"
75+
ref={scrollRef}
76+
>
77+
<divstyle={{width: '200vw',height: '200vh',background: 'yellow'}}>
78+
scrollbar-style
79+
</div>
80+
</div>
5581

5682
<pre>{sizeData}</pre>
5783
</div>

‎src/getScrollBarSize.tsx‎

Lines changed: 82 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,104 @@
11
/* eslint-disable no-param-reassign */
2+
import{removeCSS,updateCSS}from'./Dom/dynamicCSS';
23

3-
letcached: number;
4+
typeScrollBarSize={width: number;height: number};
45

5-
exportdefaultfunctiongetScrollBarSize(fresh?: boolean){
6-
if(typeofdocument==='undefined'){
7-
return0;
8-
}
6+
typeExtendCSSStyleDeclaration=CSSStyleDeclaration&{
7+
scrollbarColor?: string;
8+
scrollbarWidth?: string;
9+
};
910

10-
if(fresh||cached===undefined){
11-
constinner=document.createElement('div');
12-
inner.style.width='100%';
13-
inner.style.height='200px';
11+
letcached: ScrollBarSize;
1412

15-
constouter=document.createElement('div');
16-
constouterStyle=outer.style;
13+
functionmeasureScrollbarSize(ele?: HTMLElement): ScrollBarSize{
14+
constrandomId=`rc-scrollbar-measure-${Math.random()
15+
.toString(36)
16+
.substring(7)}`;
17+
constmeasureEle=document.createElement('div');
18+
measureEle.id=randomId;
1719

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+
constmeasureStyle: 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';
2628

27-
outer.appendChild(inner);
29+
// Clone Style if needed
30+
letfallbackWidth: number;
31+
letfallbackHeight: number;
32+
if(ele){
33+
consttargetStyle: ExtendCSSStyleDeclaration=getComputedStyle(ele);
34+
measureStyle.scrollbarColor=targetStyle.scrollbarColor;
35+
measureStyle.scrollbarWidth=targetStyle.scrollbarWidth;
2836

29-
document.body.appendChild(outer);
37+
// Set Webkit style
38+
constwebkitScrollbarStyle=getComputedStyle(ele,'::-webkit-scrollbar');
3039

31-
constwidthContained=inner.offsetWidth;
32-
outer.style.overflow='scroll';
33-
letwidthScroll=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);
3454

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);
3758
}
59+
}
3860

39-
document.body.removeChild(outer);
61+
document.body.appendChild(measureEle);
4062

41-
cached=widthContained-widthScroll;
42-
}
43-
returncached;
63+
// Measure. Get fallback style if provided
64+
constscrollWidth=
65+
ele&&fallbackWidth&&!isNaN(fallbackWidth)
66+
? fallbackWidth
67+
: measureEle.offsetWidth-measureEle.clientWidth;
68+
constscrollHeight=
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+
};
4481
}
4582

46-
functionensureSize(str: string){
47-
constmatch=str.match(/^(.*)px$/);
48-
constvalue=Number(match?.[1]);
49-
returnNumber.isNaN(value) ? getScrollBarSize() : value;
83+
exportdefaultfunctiongetScrollBarSize(fresh?: boolean): number{
84+
if(typeofdocument==='undefined'){
85+
return0;
86+
}
87+
88+
if(fresh||cached===undefined){
89+
cached=measureScrollbarSize();
90+
}
91+
returncached.width;
5092
}
5193

5294
exportfunctiongetTargetScrollBarSize(target: HTMLElement){
53-
if(typeofdocument==='undefined'||!target||!(targetinstanceofElement)){
95+
if(
96+
typeofdocument==='undefined'||
97+
!target||
98+
!(targetinstanceofElement)
99+
){
54100
return{width: 0,height: 0};
55101
}
56102

57-
const{ width, height }=getComputedStyle(target,'::-webkit-scrollbar');
58-
return{
59-
width: ensureSize(width),
60-
height: ensureSize(height),
61-
};
103+
returnmeasureScrollbarSize(target);
62104
}

‎tests/getScrollBarSize.test.ts‎

Lines changed: 7 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,23 @@
1-
import{spyElementPrototypes}from'../src/test/domHook';
21
importgetScrollBarSize,{
32
getTargetScrollBarSize,
43
}from'../src/getScrollBarSize';
4+
import{spyElementPrototypes}from'../src/test/domHook';
55

66
constDEFAULT_SIZE=16;
77

88
describe('getScrollBarSize',()=>{
99
letdefaultSize=DEFAULT_SIZE;
1010

1111
beforeAll(()=>{
12-
leti=0;
13-
1412
spyElementPrototypes(HTMLElement,{
1513
offsetWidth: {
1614
get: ()=>{
17-
i+=1;
18-
returni%2 ? 100 : 100-defaultSize;
15+
return100;
16+
},
17+
},
18+
clientWidth: {
19+
get: ()=>{
20+
return100-defaultSize;
1921
},
2022
},
2123
});
@@ -37,23 +39,6 @@ describe('getScrollBarSize', () => {
3739
});
3840

3941
describe('getTargetScrollBarSize',()=>{
40-
it('validate',()=>{
41-
constgetSpy=jest.spyOn(window,'getComputedStyle').mockImplementation(
42-
()=>
43-
({
44-
width: '23px',
45-
height: '93px',
46-
}asany),
47-
);
48-
49-
expect(getTargetScrollBarSize(document.createElement('div'))).toEqual({
50-
width: 23,
51-
height: 93,
52-
});
53-
54-
getSpy.mockRestore();
55-
});
56-
5742
it('invalidate',()=>{
5843
expect(
5944
getTargetScrollBarSize({notValidateObject: true}asany),

0 commit comments

Comments
 (0)