window scroll 事件

scroll 事件最熟悉不过的,我自己其实一直有一些疑问:

  • scroll 事件不支持冒泡,那 window 上的 scroll 事件又是如何捕获到的?
  • 为何可以对 document 添加 scroll 事件,怎么又冒泡到了 window 上?
  • window scrollTop 的值到底在 html 还是 body 上 ?

原本想找到一些官方的规范或者说明文档,但是并没有 …
以下所有测试都是在 DOCTYPE 声明为 html5 的情况下,旧的不在关注

Scroll Bubbles

关于 Scroll 事件冒泡机制这里先做个说明:

  • Element 元素 scroll 事件确实 不冒泡 传播事件,也就是我们的日常体验,比如一个div元素内的滚动事件你无法去委托事件。
  • Ducument 文档 scroll 事件是 冒泡事件,所以在window上去监听 scroll 事件,event targetdocument

这里要注意的就是 Ducument文档 的scroll事件是支持冒泡的,可以在 测试Demo 中代码运行。

测试Demo

下面做一个简单的例子:(可以点击代码里面的运行悬浮按钮,可在浏览器直接运行当前代码)

分别监听了 div body html document window 的 scroll事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<!--GLOBAL html-->
<div id="js_parent" style="width: 400px; height: 200px;overflow: scroll;">
<div id="js_child" style="background-color: rgba(1,1,1,0.4);height: 400px;padding: 10px;">
<div>scroll element</div>
<p>child</p>
<p>child</p>
<p>child</p>
<p>child</p>
<p>child</p>
<p>child</p>
<p>child</p>
<p>child</p>
<p>child</p>
<p>child</p>
</div>
</div>
<div class="placeholerBox">
<div style="padding: 30px 0;font-size: 12px;">滚动鼠标 滚动鼠标 滚动鼠标 滚动鼠标 滚动鼠标 滚动鼠标 滚动鼠标 滚动鼠标 滚动鼠标 ................ </div>
</div>
<div class="showContainer js_showContainer"></div>

<style>
.showContainer {
position: fixed;
bottom: 10px;
padding: 0 20px 20px 0;
background-color: rgba(0,0,0,0.1);
font-size: 0;
}
.showBox_item {
width: 180px;
height: 100px;
padding: 10px;
display: inline-block;
vertical-align: top;
font-size: 13px;
line-height: 2;
margin-left: 20px;
margin-top: 20px;
background-color: #FFFFFF;
box-shadow: 0 0 5px 1px rgba(0,0,0,0.2);
transition: box-shadow 1s ease;
}
.showBox_item.scroll-active {
box-shadow: 0 0 10px 4px rgba(247, 14, 14, 0.7);
}
.placeholerBox {
height: 200vh;
}
#__vconsole {
display: none;
}
</style>

js: 滚动时会创建会在屏幕底部创建一个高亮元素显示正在滚动的元素 NodeName

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
/* GLOBAL js */
function debounce(func, wait) {
var timer = null;
wait = wait || 200;
return function() {
var args = arguments;
var self = this;
timer && clearTimeout(timer);

// 先执行一次
// !timer && func.apply(self, args);

timer = setTimeout(function() {
func.apply(self, args);
timer = null;
}, wait);
};
}

function getSingleShowBox(fn, type) {
var result;
return function () {
if (!result) {
result = fn.apply(this, [type].concat(Array.prototype.slice.call(arguments)));
}
return result;
};
}

function createShowBox(type) {
var $html = $('<div class="showBox_item"><p>[ ' + type + ' ] scroll event - target:<span class="js_box"></span></p></div>');
$('.js_showContainer').append($html);
return $html.find('.js_box');
}

function scrollEvent(type) {
var getDivDom = getSingleShowBox(createShowBox, type);
var scrollEndFn = debounce(function($dom) {
$dom.closest('.showBox_item').removeClass('scroll-active');
}, 600);

return function(event) {
var $dome = getDivDom();
$dome.html(event.target.nodeName + '<br>' + new Date().toISOString())
.closest('.showBox_item').addClass('scroll-active');
scrollEndFn($dome);

// console.log('\n\n-------------------');
// console.log(type + ' scroll event');
// console.log('target:', event.target);
// console.log('currentTarget:', event.currentTarget);
// console.log('-------------------');
}
}

window.scrollEvent = scrollEvent;

// div scroll
document.getElementById('js_parent').addEventListener('scroll', scrollEvent('div'), false);

// body scroll
document.body.addEventListener('scroll', scrollEvent('body'), false);

// html scroll
document.documentElement.addEventListener('scroll', scrollEvent('html'), false);

// document scroll
document.addEventListener('scroll', scrollEvent('document'), false);

// window scroll
window.addEventListener('scroll', scrollEvent('window'), false)

Document scroll 事件来自哪里

这个问题我很想找到相对官方的说明,但是没找到,下面最要记录下日常工作中的体验。

这里在前面已经说了,window scroll 的事件是由 document scroll 事件所冒泡上去的,那弄清 document scroll 来自于哪里,实际上问题就清楚了。

onscroll 事件

如果直接在 htmlbody 上绑定 onscroll 事件,是不是就可以确定scroll事件是来自哪里?

如下点击 运行单个

1
2
3
4
5
// body onscroll
document.body.onscroll = scrollEvent('body(onscroll))');

// html onscroll
document.documentElement.onscroll = scrollEvent('html(onscroll))');

这里在ie下会触发 html onscroll,非ie都统一为 body onscrolltarget 依旧为 document

触发的 onscroll 事件和 window.onscroll 相同引用,也就是说:

ie: window.onscroll === document.documentElement.onscroll
非ie: window.onscroll === document.body.onscroll

bodyhtml 可以通过 onscroll 捕获滚动事件,但 addEventListener 注册的 scroll 事件却捕获不了,啊哈太棒了,还是没找到说明文档

这里对触发 onscroll 的元素,暂且理解为滚动元素也就是滚动条所在的元素 (后面还是会解释不通…)

document scrollTop

scrollTop 如果正常理解哪里触发滚动事件,那就在哪里取 scrollTop 的值。

如果按上面描述的 非iebody 上触发,那就应该是 document.body.scrollTop ,但实际并不是这样,这里 ie 和 非ie 都是在 html 上取值。

有些场景会在body上,据说不申明 DOCTYPE 时,所以看你需不需要兼容:

1
document.documentElement.scrollTop || document.body.scrollTop

另外还有一个 window.pageYOffset 来直接获取页面滚动条高度,兼容性也是妥妥的,只不过是只读,如果需要设置滚动条高度还是要用上面的。

不同浏览器处理方式不太一样,( target 已经很清楚就是在 document 文档触发的滚动事件了 orz…)

最后

原本是觉得这些事件触发和scrollTop取值有些怪怪的,想弄清楚,不过没找到相关的 官方文档,那就这样吧…

  • ie: window.onscroll === document.documentElement.onscroll
  • 非ie: window.onscroll === document.body.onscroll
  • 获取页面滚动条高度:window.pageYOffset || document.documentElement.scrollTop
  • 设置页面滚动条高度:document.documentElement.scrollTop = x,使用 window.scrollTo(x, y) 兼容不太行
  • window 捕获的 scroll 事件是由 document 冒泡的

其它补充

  • 把html元素作为滚动元素而不触发document的滚动事件,好像做不到。

  • 把body元素作为滚动元素而不触发document的滚动事件,可以如下。

    1
    2
    3
    html, body {height: 100%;}
    html {overflow: hidden;}
    body {overflow: auto;}
  • 移动端烦人的弹窗滚动穿透,如果body没有滚动条就可以了,所以如果新项目可以直接让body没有滚动条,直接顶层div是做滚动,弹窗弹在body下规避这个问题。

  • 关于元素尺寸计算,点击这里查看 浏览器视窗或者元素宽高计算