这一节主要来探讨下页面能够滚动的原因以及方式。
Element.getBoundingClientRect()
1.页面滚动原因
根据MDN定义:
文档视图或者一个元素在滚动时,会触发元素的 scroll
事件。
所以,我们将页面滚动可以分为两种情况:
document
文档滚动- 某一
element
内部滚动
前者的页面滚动原因,是元素超出了视口范围,浏览器默认产生的滚动效果。
后者的页面滚动原因,是子元素超出了固定范围(定宽或者定高)的容器,而容器需要设置 overflow: scroll;
。
而这俩种形式的滚动,其涉及到的事件监听是有区别的。
2.页面滚动事件监听
事件阶段分为事件捕获和事件冒泡。
scroll
事件的捕获阶段,在两种场景下是表现一致的。
而 scroll
事件的冒泡阶段,在两种场景下(document
或者 element
)表现不同。
假如是 document
,此时,事件冒泡会冒泡到 document
和 window
。
(但 document
和 window
都是顶级对象,可以看做是一体的。)
假如是 element
,此时,事件冒泡只会冒泡到 element
,并不会冒泡到 document
和 window
。
关于这个结论,可以稍显粗暴的总结成,scroll
事件的冒泡阶段只会触发一次。
本节参考文章:
2-1.document
滚动
可以点击本例,进行实际测试 console
:
在本例中,设置了超过页面高度的元素,以使页面自身发生滚动。
所以本例属于 document
滚动。
然后在 window
、document
以及 element
上都注册了 scroll
事件监听,分别在事件捕获和事件冒泡阶段进行触发。
而后的打印结果如下:
"scroll capture on window"
"scroll capture on document"
"scroll bubble on document"
"scroll bubble on window"
根据打印结果可知,scroll
事件监听只在 window
和 document
的捕获和冒泡阶段触发了。
2-2.element
滚动
可以点击本例,进行实际测试 console
:
在本例中,设置了定高的元素,其属性设置了 overflow: scroll;
以使内部元素滚动。
所以本例属于 element
滚动。
然后在 window
、document
以及 element
上都注册了 scroll
事件监听,分别在事件捕获和事件冒泡阶段进行触发。
而后的打印结果如下:
"scroll capture on window"
"scroll capture on document"
"scroll capture on app"
"scroll bubble on app"
根据打印结果可知,scroll
事件在冒泡阶段只触发了一次。
3.页面滚动相关方法
本节总结介绍日常开发中,关于页面滚动的一些方法。
3-1.获取滚动距离
export function getScrollTop () {
// safari使用的是window.pageYOffset
return document.documentElement.scrollTop || document.body.scrollTop || window.pageYOffset
}
export function getScrollLeft () {
// safari使用的是window.pageXOffset
return document.documentElement.scrollLeft || document.body.scrollLeft || window.pageXOffset
}
3-2.判断元素是否在视口中
export function getElementStatusInViewPort (ele) {
// 浏览器高度兼容
const windowHeight = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
const offsetHeight = ele.offsetHeight
const rectInfo = getRect(ele)
const { top, bottom } = rectInfo
// 在视口之下
if (top >= windowHeight) {
return 'UNSHOW'
}
// 正在出现
if (top < windowHeight && bottom >= windowHeight) {
return 'SHOWING'
}
// 在视口中
if (top > 0 && top < windowHeight && bottom > 0 && bottom < windowHeight) {
return 'SHOWED'
}
// 正在离开视口
if (top <= 0 && bottom <= offsetHeight && bottom > 0) {
return 'LEAVING'
}
// 已经离开视口
if (bottom <= 0) {
return 'LEAVED'
}
}
3-3.获取元素距离视口的上下左右边界
export function getRect (element) {
// 距离视窗的距离
const rect = element.getBoundingClientRect()
// html元素对象的上边框的宽度
const top = document.documentElement.clientTop ? document.documentElement.clientTop : 0
const left = document.documentElement.clientLeft ? document.documentElement.clientLeft : 0
return {
top: rect.top - top,
bottom: rect.bottom - top,
left: rect.left - left,
right: rect.right - left
}
}
3-4.获取某元素参照body的offsetTop
不适用于 position: fixed;
元素。
因为设置了 position: fixed;
的元素,其 offsetParent 是 null
。
export function getOffsetTop (el) {
const root = document.body
let height = 0
do {
height += el.offsetTop
el = el.offsetParent
} while (el && el !== root)
return height
}
3-5.滚动到指定位置
JavaScript
提供了相关方法,以使页面滚动到对应位置。
比较简单的一种方式是 element.scrollIntoView()
。
var element = document.querySelector('.scroll-target')
element.scrollIntoView({
behavior: 'smooth'
})
但在实际操作中,会发现 scrollIntoView
方法并不是 “十分好用”。
在这种情况下,可以计算目标元素需要偏移的距离,并使用 window.scrollTo
“精确”的使页面滚动到指定位置。
window.scrollTo({
top: 0,
left: 0,
behavior: 'smooth'
})
其中 top
对应顶部滚动距离,left
对应左侧滚动距离。
而 top
和 left
的具体值的获取,可以利用本节中已经声明的方法,有两种方式:
getScrollTop
或getScrollLeft
, 结合getRect
。- 直接利用
getOffsetTop
。
其中 behavior
属性可设置为 smooth
,页面会平滑过渡。
该属性也可以使用 css
属性进行替代。
在监听 scroll
事件的容器上设置:
.scroll-target {
scroll-behavior: 'smooth';
}
4.页面滚动到指定位置
在本节中,我们介绍下,实现页面滚动到指定位置的几种方式。
4-1.a
链接
该方法是 “原始时代” 流传下来的方式。
操作方便、实现简单。
但如果想要体验友好的话,需要设置 css
属性 scroll-behavior: 'smooth';
。
4-2.scrollIntoView
取决于其它元素的布局情况,此元素可能不会完全滚动到顶端或底端。
4-3.scrollTo
关于 window.scrollTo
方法的重点在于如何计算滚动距离。
在本例中利用了 scrollTop
属性结合 getBoundingClientRect
方法。
此外也可以使用我们在前俩节中提到的 offsetTop
进行计算。
相应的,也可以利用对应 API
来实现横向滚动:
5.滚动条
在 scroll
相关需求中,有可能需要设置滚动条。
在此粗略记录下。
5-1.隐藏滚动条
/* 隐藏滚动条 */
body::-webkit-scrollbar {
display: none; /* Chrome Safari */
}
html, body {
scrollbar-width: none; /* firefox */
-ms-overflow-style: none; /* IE 10+ */
/* overflow-x: hidden; */
/* overflow-y: auto; */
}