0%

antd dialog的一个问题分析

最近项目中碰到一个奇怪问题,项目是react+antd+umi实现,当页面中点击对话框,页面会自动滚动到最上面。最终定位跟antd dialog的实现有关,在某种条件下会触发此问题,下面分享一下问题定位过程,希望对遇到类似问题的同学有所帮助。

问题现象

列表页滚动到下方,点击操作弹出对话框,列表自动滚动到最上面。此问题在上个版本还不存在。

image.png

image.png

问题定位

一定是触发了滚动事件才会滚动到列表最上方,怎么定位哪里触发的滚动事件呢,从代码入手毫无头绪。好在可以通过浏览器devtool打断点,跟踪一下哪里触发。首先尝试了scroll事件,确实能够进到断点,但是堆栈只有一贞,没法找到源头。接着尝试了focus事件。

image.png

进到focus事件时已经滚动到上方了,此时有堆栈信息,从堆栈定位到rc-dialog,其中有一句this.switchScrollingEffect(),高度怀疑这里触发了问题,在这里打上断点,证实了确实如此。

image.png

进一步调试到this.switchScrollingEffect里面,问题触发点在setStyle这里,设置了如下样式后,就会触发滚动到最上面。

image.png

switchScrollingEffect如下:

image.png

setStyle如下:

image.png

结合最近修改代码反复尝试,发现导致上面问题在于如下样式:

1
2
3
4
html {
overflow-x: hidden;
overflow-y: auto;
}

也就是当html有如上样式(body没有,如果body也有也不会触发问题),再设置body样式{overflow: hidden, overflow-x: hidden, overflow-y: hidden},就会出现自动滚动到最上面。

找到问题原因后我写了一小段代码最小化重现了这个问题。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<html>
<body id="content">
<button onclick="restore()">
restore
</button><br>
sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss<br>
s<br>s<br>s<br>s<br>s<br>s<br>s<br>s<br>s<br>s<br>s<br>s<br>s<br>s<br>s<br>s<br>s<br>s<br>s<br>s<br>s<br>s<br>s<br>s<br>s<br>s<br>s<br>s<br>s<br>s<br>s<br>s<br>s<br>s<br>s<br>s<br>s<br>s<br>s<br>s<br>s<br>s<br>s<br>s<br>s<br>s<br>s<br>s<br>
<button onclick="setStyle()">
setstyle
</button>
<button onclick="showsize()">
showsize
</button>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
html {
border: 1px solid red;
height: 100%;
overflow-y: auto;
}

body {
border: 1px solid blue;
height: 100%;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function setStyle() {
var s = document.getElementById("content");
s.style['overflow'] = 'hidden';
s.style['overflow-x'] = 'hidden';
s.style['overflow-y'] = 'hidden';

}
function showsize() {
var s = document.getElementById("content");
alert("document.body.offsetWidth="+document.body.offsetWidth+"\nwindow.innerWidth="+window.innerWidth+"\ndocument.body.scrollHeight="+document.body.scrollHeight+"\nwindow.innerHeight="+window.innerHeight);
}
function restore() {
var s = document.getElementById("content");
s.style['overflow'] = '';
s.style['overflow-x'] = '';
s.style['overflow-y'] = '';
}

问题分析

因为body没有设置overflow-y,默认是visible,所以内容显示到框外。

html设置了overflow-y: auto,所以出现纵向滚动条。

当点击setstyle后,body高度变小,html就滚动到最上面。

如果html没有设置overflow-y: auto,则默认是visible,不会滚动,仍然保持在底部。

如果body也设置了overflow-y:auto,则body会出现纵向滚动条,而html的滚动条只会覆盖body的clientheight区域。点击setstyle,只是将body的滚动条去掉,不会触发html滚动。

所以rc-dialog.switchScrollingEffect()的实现是存在一些缺陷的。

其他问题

上面调试过程中,isBodyOverflowing的实现引起了我的兴趣。

1
2
3
4
5
6
7
function isBodyOverflowing() {
return (
document.body.scrollHeight >
(window.innerHeight || document.documentElement.clientHeight) &&
window.innerWidth > document.body.offsetWidth
);
}

window.innerWidth > document.body.offsetWidth这个条件,表示body的width+padding没有超过浏览器窗口内宽。document.body.scrollHeight >

​ (window.innerHeight || document.documentElement.clientHeight)表示有纵向滚动。所以这个条件的意图有点让人不太理解,前一个条件很容易不满足,例如设置body的width为100vw。

image.png

(图片来自https://www.pianshen.com/article/6486132360/)

-------------本文结束感谢您的阅读-------------