案发现场
今天给后台的表格组件替换为一个前同事写的表格组件,替换完发现除了滚动条不能用鼠标滚动以外,其他都没什么大问题。我是一个跟任何东西都可以较劲的人,我不信一个小小的滚动条还能奈我何?于是就发生了这个血案。从看源码,到问前同事,足足浪费了我一个下午来找问题,深深地感到自己的能力是如此的不足。
案发详情
滚动条归属的问题。
我们都知道,如何页面超过了浏览器窗口的大小,浏览器会自动生成一个滚动条。这个滚动条是属于谁的?是属于html
元素还是body
元素的?
都不是!!
当一个子元素超过了其父元素的限定高度并且在父元素设置了overflow:auto
或者overflow:scroll
父元素容器就会产生一个滚动条,这个滚动条毫无疑问是归属于父元素的。直接看demo01,以下是demo01的主要代码:
<div class="parent">
parent
<div class="child">
child
</div>
</div>
body{
margin:0;
}
.parent{
height:400px;
border:1px solid #ddd;
overflow:auto;
}
.child{
height:800px;
background-color:#eee;
}
可以看到,child
撑开了parent
的高度,于是parent
生成了滚动条,滚动条属于parent
,这个是没有争议的。
如何判断滚动条的归属呢?
一种直观的方法:可以通过控制台的Element面板,把鼠标hover在parent
元素上,可以看到滚动条是在元素内部的。(在Mac下,滚动条的表现形式不太一样,只有滚动的时候才会出现滚动条,没有滚动时自动隐藏)
另一种方法是可以通过对元素添加scroll
监听事件来判断。
如果是body
下的直接子元素的高度超过了body的限定高度,并且给body增加overflow :auto
,这时生成的滚动条是属于谁的?看一下例子demo02,以下是主要代码:
<div class="child">
Child
</div>
html, body{
height:100%;
}
body{
margin:0;
overflow:auto;
}
.child{
height:1000px;
background-color:#eee;
}
咦,好像滚动条并不属于body
元素??确实不属于body
,也不属于html
。Mac下按照上面第一种方法观察,滚动条好像是属于html
元素的,但其实不然,可以通过给元素添加scroll
事件的监听就可以发现,滚动的时候并不会触发html
元素或者body
元素的scroll
事件。
//监听window窗口的滚动事件
window.addEventListener('scroll', (e) => {
console.log('window');
})
//监听document对象的滚动事件
document.addEventListener('scroll', (e) => {
console.log('document');
})
//监听html元素的滚动事件
document.documentElement.addEventListener('scroll', (e) => {
console.log('html');
})
//监听body元素的滚动事件
document.body.addEventListener('scroll', (e) => {
console.log('body');
})
上面的四个监听器,滚动的时候只有前两个才会被触发,所以证明滚动条是属于document对象的。
为什么window
也能监听到scroll
事件呢?这是因为事件冒泡
当html元素也添加overflow:auto
时,滚动条会是谁的呢?看例子demo03
这时,滚动条貌似是属于body
的?是的没错,就是属于body
的,用监听器来监听一下滚动事件,只会触发document.body
的监听器,而不会触发window
和document
的。
为什么会这样?应该不仅只有我一个人才发现滚动条这个坑的吧?TAT
先来回顾一下overflow
有哪些值:
/* 默认值。内容不会被修剪,会呈现在元素框之外*/
overflow: visible;
/* 内容会被修剪,并且其余内容不可见 */
overflow: hidden;
/* 内容会被修剪,浏览器会显示滚动条以便查看其余内容 */
overflow: scroll;
/* 由浏览器定夺,如果内容被修剪,就会显示滚动条 */
overflow: auto;
/* 规定从父元素继承overflow属性的值 */
overflow: inherit;
于是得出结论(也可以说个人猜测,因为不一定正确):
html
和body
仅有一个元素会产生滚动条时,这个滚动条会默认归属于document
当html
和body
元素的overflow
属性都是默认值auto
时,因为body
高度可能超过html
的高度,这时就会产生一个滚动条,这个滚动条属于document
的,所以如果body
再产生一个滚动条,就只能是属于body
自己的了。
鼠标滚轮事件
页面出现滚动条时,我们可以用鼠标滚轮来对页面进行纵向滚动,那是否可以组织这个滚动行为呢?
肯定是可以的。e.preventDefault()
就是阻止事件的默认行为,在mousewheel
事件监听里加上这句,就能阻止鼠标滚轮触发滚动事件了。
因为这个,让我在组件源码里折腾了好一会,当然不是不知道e.preventDefault()
,是忽略了它的作用。
因为工作忙,这篇文章前后写了好久,当时的一些思路灵感都已经消逝了,写不下去了,先到这为止吧,之后有想起什么遗漏的再补上。