You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
相应的只是在 body 上增加 overflow:hidden,下面是打开抽屉时body的样式:
但相应的,这种方式也会出现一个问题,如上面的gif图显示的一样,当抽屉打开时,遮罩下面的内容块会向右移动,该移动的距离就是滚动条的宽度,这是因为此时 body 溢出时设置了hidden,所以不会出现滚动条了,自然下面的内容块向右移动了;当抽屉关闭时,由于body上的 overflow:hidden 被取消,滚动条重新出现了,导致了内容块又向左移动了一个滚动条宽度的距离,所以这就是我们今天要讨论的滚动条对抽屉的影响,以及如何消除该影响。
抽屉组件在平时的业务中很常见,所以在不久前参考了
antd
的Drawer
组件实现了下,不过本文并不会讲述内部的具体实现,而是了解下其是如何解决滚动条
对该组件的影响的。我们知道,
Drawer
组件的两部分——遮罩
和抽屉
一般都是基于fixed
定位的,当出现滚动条,且我们没有对其做兼容性处理的时候,会出现如下bug:抽屉并不能覆盖掉滚动条,这时我们可以在抽屉弹出的时候让body不能滚动,这样就不会出现滚动条了,如下:
相应的只是在
body
上增加overflow:hidden
,下面是打开抽屉时body的样式:但相应的,这种方式也会出现一个问题,如上面的gif图显示的一样,当抽屉打开时,遮罩下面的内容块会向右移动,该移动的距离就是滚动条的宽度,这是因为此时
body
溢出时设置了hidden
,所以不会出现滚动条了,自然下面的内容块向右移动
了;当抽屉关闭时,由于body上的overflow:hidden
被取消,滚动条重新出现了,导致了内容块又向左移动了一个滚动条宽度
的距离,所以这就是我们今天要讨论的滚动条对抽屉的影响,以及如何消除该影响。首先我们来了解下如何判断是否出现了滚动条以及如何获取滚动条的宽度。这里只讨论当抽屉的容器为
body
的情况。是否出现了滚动条
一般情况下,我们只需要判断页面实际内容高度是否超过窗口的可视区高度就能判断出是否存在滚动条:
这个在body不存在
overflow:hidden
的情况下是有效的,但在body
上添加overflow:hidden
之后,如果实际内容超出了可视区,上面的条件判断仍然为true,但此时并没有滚动条,所以上面判断并不能符合,那要如何处理呢?下面是rc-drawer
做的判断:由于
window.innerWidth
是包含滚动条的宽度,而document.body.offsetWidth
并不包含滚动条的宽度,所以可以做此处理,从而真正判断出是否出现了滚动条,那么是否可以直接通过window.innerWidth > document.body.offsetWidth
来判断是否出现了滚动条呢??答案也是不行的,由于
document.body.offsetWidth
并不包含margin的大小,所以当出现body
有margin
的时候,它是始终小于window.innerWidth
的,当此时没有滚动条,示例如下:但是又会出现一个问题,将上面的
div
增加到1000
个,同时将body
设为overflow:hidden
,此时用上面[1]
的处理方法,条件返回的是true,但仍然没有滚动条:所以上面的判断还是有问题的,但是
antd
中是将body
的margin/padding
全部置为0
的,所以上述的判断在使用antd的过程中并没有毛病。我们来对上面的条件做一定的修改,来做到更好的判断。上面问题出现在
overflow
上,所以我们来判断下是否存在overflow
为hidden
关于如何判断是否存在ScrollBar,可以参考Detect if a page has a vertical scrollbar?
如何获取滚动条的宽度
知道是是否出现滚动条之后,我们就要来获取其宽度,其实现方式均大同小异,这里直接给出antd中是如何判断的,
下面是两个div的截图:
外层div:
内层div:
从代码中我们可以了解到:判断方法主要是通过设置两个div,
内层div
的高度
大于外层
的,同时设置外层div
在overflow
分别为hidden
和scroll
时,导致内层的div
宽度的变化。因为当设置了scroll
之后,内层div
的offsetWidth
是不包含
滚动条的宽度的,相反,当设置了hidden
之后,由于内层div
的宽度为100%
,所以是与外层div等宽
,这样,就可以获取到了滚动条的大小了。但同时我们注意到代码中有如下判断:那么什么时候会出现hidden与scroll时,
内层div
的offsetWidth
相等呢?换句话说,类似于滚动条不存在?这里我只想到一种情况(欢迎补充):在mac上,点击系统偏好设置 -> 通用 -> 显示滚动条 -> 点选 根据鼠标或者触控板自动显示
之后就会出现相等的情况:前面讲解了关于如何判断是否出现滚动条以及如何获取其宽度,现在回到组件本身,来了解如何消除滚动条对组件的影响。
打开组件源码,先看
getChildToRender
这个函数:translate
属性setLevelDomTransform
setLevelDomTransform
关于滚动条的设置主要是发生在该函数中
level
内的元素,均设置style.transform
的translate
值,有兴趣的可以自行了解下它是如何收集这些level元素的。body
的时候,处理body
的滚动,这里不考虑移动端的情况,下面代码阉割了移动端的处理:当抽屉从右边弹出,设置
this.dom.style.transform =
translateX(-${right}px);
原因,cong从下图我们可以看见,当抽屉从右边弹出时,由于此时body的宽度减去了一个滚动条的宽度,但此时抽屉组件是固定定位,并且width为100%,所以此时抽屉的宽度是视口的宽度,比body多了一个滚动条的宽度,所以打开抽屉时会下面这样的空白区,所以此时将抽屉整体向左移动一个滚动条的宽度就能掩盖住之前的空白区,当然最后还是要将抽屉整体向右回移回去,不然右边就会出现下面第二个图的情况,同时显得更加平滑:未回移的情况:
关闭抽屉时,如果不处理遮罩位置时,出现的bug:
总结
总的来说,
rc-drawer
是通过改变body
的宽度,然后修改响应的抽屉的打开或者关闭过程中的位置关系(保持body
与组件同在一个“位置”
上,即假设没有滚动条的情况下),最后在下一个事件循环时,消除之前过程中所带来的副作用
来达到消除滚动条的影响。同时也了解了如何判断是否存在滚动条以及滚动条的宽度如何获取。有兴趣的可以clone下该组件,并细读其源码,可以学习到不少东西,如该组件没有设置open
的默认值,因为存在永久抽屉;再者当组件第一次进入时没有动画效果是如何处理的,这里是直接重新渲染组件,等等...以上全为个人见解,如有不当,欢迎指出以及交流
^_^
The text was updated successfully, but these errors were encountered: