1. 显示滚动条的条件
  2. scroll area
  3. 横轴最大滚动距离
  4. 纵轴最大滚动距离
  5. 滚动事件
  6. scrollTop scrollLeft
  7. scrollHeight scrollWidth
  8. scrollIntoView
  9. 滚动动画
  10. 示例
  11. 参考

scrolling box

当overflow为auto,且内部元素溢出时.元素沿padding edge向被内裁剪,在单侧显示滚动条
48303328-e7d70c80-e543-11e8-8347-f1f7a8d54797.png

一般来说, 元素会从右/下侧被裁剪,具体数值取决于滚动条宽度
206580fb.png

如上图,外层元素width为200px,padding为40px,border为10px.内有一宽高为300px的子元素
50391437-f12cd900-077f-11e9-848e-8b8fc392e9a2.gif
设置scroll属性后, 元素被从右/下方裁剪掉17px.并添加滚动条, 滚动条宽度为17px
被裁剪后,外层元素的实际显示宽度由200px变成了183px.再加上滚动条17px,总宽度跟原来相比没有发生变化

显示滚动条的条件

子元素不超出父元素也可能产生滚动条,具体取决于 内部元素宽度 + 滚动条宽度 是否大于容器宽度
48303337-09d08f00-e544-11e8-8b69-68487f767245.png

如上图,左侧内部子元素宽度为220px,未超出容器宽度

1
40 + 220 + 17 = 277 

右侧内部子元素宽度为224px,加上滚动条宽度,总宽度超出容器1px

1
40 + 224 + 17 = 281

scroll area

scroll area 是scroll box的实际内容区域,有以下4条边

  • top edge: 等于scroll box的top padding edge
  • left edge: 等于scroll box的left padding edge
  • right edge: 等于 max(right padding edge, 位于最右的后代元素的right margin edge)
  • bottom edge: 等于 max(bottom padding edge, 位于最下的后代元素的right margin edge)
    48303349-22d94000-e544-11e8-89ed-38731ae001f5.png

横轴最大滚动距离

scroll area用于scroll box可滚动范围的计算,计算方式为

1
scroll area width - padding area width

48303342-16ed7e00-e544-11e8-90a0-b52dcf8c404e.png
如上图, a到b为最大滚动距离

元素的scroll area宽度为340px(40px + 300px), padding edge width为263px(40px + 200px + 40px - 17px)
因此,横向的最大滚动距离为77px(340px - 263px)

纵轴最大滚动距离

对于padding-bottom属性,chrome和firefox/ie的表现不同.
firefox/ie会忽略padding-bottom,好象其不存在一样
而chrome会显示padding-bottom,哪怕内部子元素低于padding-bottom也一样
48303355-32f11f80-e544-11e8-9141-059883b72ecb.png
48303358-37b5d380-e544-11e8-8c92-3832cce0648f.png

chrome的计算方式

1
(40 + 300 + 40) - (280 - 17) = 117

firefox的计算方式

1
(40 + 300) - (280 - 17) = 77

滚动事件

1 当元素滚动时,会触发scroll事件
ie9+

1
2
3
4
container.addEventListener('scroll', function (event) {
var target = event.target;
console.log(target.scrollTop, target.scrollLeft);
})

ie5+

1
2
3
4
5
container.onscroll =  function (event) { 
event = event || window.event;
var target = event.target || event.srcElement;
console.log(target.scrollTop, target.scrollLeft);
}

ie5+

1
2
3
4
container.attachEvent('onscroll', function (event) {
var target = event.target || event.srcElement;
console.log(target.scrollTop, target.scrollLeft);
})

注意attachEvent的事件名为onscroll

2 当元素大小发生变化时,触发滚动事件
是否触发滚动事件,取决于原滚动条位置是否大于当前的最大滚动距离,计算方式为

1
2
max(0, min(x, element scrolling area width - element padding edge width))    
max(0, min(y, element scrolling area height - element padding edge height))

48303376-53b97500-e544-11e8-8a4f-3f5d324a6820.gif
如上图,元素初始width值为200时,最大滚动距离为

1
(40 + 300) - (40 + 200 + 40 - 17) = 77

当width值变为201时,最大滚动距离为

1
(40 + 300) - (40 + 201 + 40 - 17) = 76

max(0, min(77, 76)) = 76
如果之前x轴滚动距离为77, 则触发滚动事件

如果width值由200变为199,则width值为199时的最大滚动距离为

1
(40 + 300) - (40 + 199 + 40 - 17) = 78

max(0, min(77, 78)) = 77
48303377-57e59280-e544-11e8-98df-b98283238bc6.gif
因此,当元素尺寸变小时,不会触发滚动事件

如果需要在变小时触发滚动事件,可以将子元素大小修改为百分比
48303379-63d15480-e544-11e8-9d8d-de1a6d83014e.gif
如上图,滚动元素内子元素大小为200%
当滚动元素width值为200时,最大滚动距离为

1
(40 + (200 - 17) * 200%) - (40 + 200 + 40 -17) = 143

当width值变为199时,最大滚动距离为

1
(40 + (199 - 17) * 200%) - (40 + 199 + 40 -17) = 142

max(0, min(143, 142)) = 142
触发滚动事件

scrollTop scrollLeft

scrollTop可用于获取/设置元素的纵向动距离
scrollLeft可用于获取/设置元素的横向动距离

获取滚动距离

1
element.scrollTop

设置滚动距离

1
element.scrollTop = 1000

scrollTop/scrollLeft接受整数值,当值

  • 小于0时,设置为0
  • 当值超出最大滚动值时,设置为最大滚动值

注 overflow值为hidden也可以滚动

scrollHeight scrollWidth

Element.scrollHeight 是一个只读属性,返回内部元素的实际高度
Element.scrollWidth 是一个只读属性,返回内部元素的实际宽度

Chrome

1
2
container.scrollHeight // 380
container.scrollWidth // 340

Firefox

1
2
container.scrollHeight // 340
container.scrollWidth // 340

48303381-76e42480-e544-11e8-872e-08b09f26406c.png

因为Firefox不计算padding-bottom,因此高度比Chrome小了40px
而Chorme添加滚动条后会计算padding-bottom,因此滚动时高度是380px,不滚动时高度是340px

scrollIntoView

Element.scrollIntoView()用于将元素滚动到页面可视区域内

1
element.scrollIntoView({behavior: 'smooth'});

48303387-9a0ed400-e544-11e8-801b-9684dadb0d47.gif

具体参数见 MDN
兼容性见 CanIuse

滚动动画

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
72
73
74
75
76
77
/**
* 播放纵向滚动条滚动动画
*
* @param {*} 可滚动元素
* @param {*} 目标位置
* @param {*} 持续时间
*/
function scroll(element, scrollTop, duration) {
/**
* 计算当前时间点,滚动条scrollTop位置(easeInOut)
*
* @param {Number} t 调用时的时间
* @param {Number} b 初始位置
* @param {Number} c 移动距离
* @param {Number} d 总时间
* @returns {Number} 滚动条scrollTop高度
*/
function easeInOut(t, b, c, d) {
if (t == 0) return b;
if (t == d) return b + c;
if ((t /= d / 2) < 1) return c / 2 * Math.pow(2, 10 * (t - 1)) + b;
return c / 2 * (-Math.pow(2, -10 * --t) + 2) + b;
}

/**
* 获取元素最大可滚动高度
*
* @param {Element} element 文档对象
* @returns {Number} 最大可滚动高度值
*/
function getMaxScrollTop(element) {
var temp = element.scrollTop;
element.scrollTop = 10000000;
var maxScrollTop = element.scrollTop;
element.scrollTop = temp;
return maxScrollTop;
}

var startPosition = element.scrollTop;
var endPosition = scrollTop;

var maxScrollTop = getMaxScrollTop(element);
if (scrollTop > maxScrollTop) scrollTop = maxScrollTop;
if (scrollTop < 0) scrollTop = 0;

if (startPosition === endPosition) return;

var startTime = Date.now();
var endTime = startTime + duration;
var moveDistance = endPosition - startPosition;
function animate() {
var currentTime = Date.now();
if (currentTime >= endTime) {
element.scrollTop = endPosition;
return;
}
var scrollTop = easeInOut(currentTime - startTime, startPosition, moveDistance, duration);
element.scrollTop = scrollTop;
setTimeout(animate, 16.6);
}
function animateByRAF() {
var currentTime = Date.now();
if (currentTime >= endTime) {
element.scrollTop = endPosition;
return;
}
var scrollTop = easeInOut(currentTime - startTime, startPosition, moveDistance, duration);
element.scrollTop = scrollTop;
window.requestAnimationFrame(animateByRAF);
}
if (window.requestAnimationFrame) {
window.requestAnimationFrame(animateByRAF);
} else {
animate();
}
}
scroll(document.documentElement, 1000, 1000);

也可以用jQuery的animate函数实现滚动动画

1
2
3
$('html, body').animate({
scrollTop: 500;,
}, 200);

示例

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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<style>
body {
padding-top: 100px;
padding-left: 100px;
}

.container {
overflow: scroll;
width: 200px;
height: 200px;
padding: 40px;
border: 10px solid;
background: #ff0;
background-clip: content-box;
}

.child {
width: 300px;
height: 300px;
}

.child {
background-image: url('data:image/svg+xml,%3Csvg width=\'6\' height=\'6\' viewBox=\'0 0 6 6\' xmlns=\'http://www.w3.org/2000/svg\'%3E%3Cg fill=\'%23ff0000\' fill-opacity=\'0.4\' fill-rule=\'evenodd\'%3E%3Cpath d=\'M5 0h1L0 6V5zM6 5v1H5z\'/%3E%3C/g%3E%3C/svg%3E');
}

::-webkit-scrollbar {
width: 17px;
height: 17px;
background: transparent;
}

::-webkit-scrollbar-thumb {
background-color: blue;
}

::-webkit-scrollbar-corner {
display: none;
}
</style>
</head>

<body>
<div class="container" id="container">
<div class="child" id="child"></div>
</div>
<pre class="info" id="info"></pre>
<div>
<pre id="container-width"></pre>
<button id="des-width">-1</button>
<button id="ins-width">+1</button>
</div>
<script>;
var container = document.getElementById('container');
var child = document.getElementById('child');
var info = document.getElementById('info');
var containerWidth = document.getElementById('container-width');
var insWidth = document.getElementById('ins-width');
var desWidth = document.getElementById('des-width');

info.innerText += 'scrollTop ' + container.scrollTop + '\n';
info.innerText += 'scrollLeft ' + container.scrollLeft;
containerWidth.innerText = container.offsetWidth - 100;
container.addEventListener('scroll', function () {
var target = event.target;
console.log(target.scrollTop, target.scrollLeft)
info.innerText = '';
info.innerText += 'scrollTop ' + container.scrollTop + '\n';
info.innerText += 'scrollLeft ' + container.scrollLeft;
})
insWidth.addEventListener('click', function (event) {
var width = container.offsetWidth - 100;
width++;
container.style.width = width + 'px';
containerWidth.innerText = width;
})
desWidth.addEventListener('click', function (event) {
var width = container.offsetWidth - 100;
width--;
container.style.width = width + 'px';
containerWidth.innerText = width;
})
</script>
</body>
</html>

参考

W3C scroll-an-element
W3C scrolling-box
巧妙监测元素尺寸变化
css - applying padding to box with scroll, bottom-padding doesn't work