Skip to content

这一节主要来探讨下页面能够滚动的原因以及方式。

Element.getBoundingClientRect()

1.页面滚动原因

根据MDN定义:

文档视图或者一个元素在滚动时,会触发元素的 scroll 事件。

所以,我们将页面滚动可以分为两种情况:

  1. document 文档滚动
  2. 某一 element 内部滚动

前者的页面滚动原因,是元素超出了视口范围,浏览器默认产生的滚动效果

后者的页面滚动原因,是子元素超出了固定范围(定宽或者定高)的容器,而容器需要设置 overflow: scroll;

而这俩种形式的滚动,其涉及到的事件监听是有区别的。

2.页面滚动事件监听

事件阶段分为事件捕获事件冒泡

scroll 事件的捕获阶段,在两种场景下是表现一致的。

scroll 事件的冒泡阶段,在两种场景下(document 或者 element)表现不同。

假如是 document,此时,事件冒泡会冒泡到 documentwindow

(但 documentwindow 都是顶级对象,可以看做是一体的。)

假如是 element,此时,事件冒泡只会冒泡到 element,并不会冒泡到 documentwindow

关于这个结论,可以稍显粗暴的总结成,scroll 事件的冒泡阶段只会触发一次

本节参考文章:

2-1.document 滚动

可以点击本例,进行实际测试 console:

在本例中,设置了超过页面高度的元素,以使页面自身发生滚动。

所以本例属于 document 滚动

然后在 windowdocument 以及 element 上都注册了 scroll 事件监听,分别在事件捕获和事件冒泡阶段进行触发。

而后的打印结果如下:

"scroll capture on window"

"scroll capture on document"

"scroll bubble on document"

"scroll bubble on window"

根据打印结果可知,scroll 事件监听只在 windowdocument 的捕获和冒泡阶段触发了。

2-2.element 滚动

可以点击本例,进行实际测试 console:

在本例中,设置了定高的元素,其属性设置了 overflow: scroll; 以使内部元素滚动。

所以本例属于 element 滚动。

然后在 windowdocument 以及 element 上都注册了 scroll 事件监听,分别在事件捕获和事件冒泡阶段进行触发。

而后的打印结果如下:

"scroll capture on window"

"scroll capture on document"

"scroll capture on app"

"scroll bubble on app"

根据打印结果可知,scroll 事件在冒泡阶段只触发了一次。

3.页面滚动相关方法

本节总结介绍日常开发中,关于页面滚动的一些方法。

3-1.获取滚动距离

js
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.判断元素是否在视口中

js
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.获取元素距离视口的上下左右边界

js
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; 的元素,其 offsetParentnull

js
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()

js
var element = document.querySelector('.scroll-target')
element.scrollIntoView({
  behavior: 'smooth'
})

但在实际操作中,会发现 scrollIntoView 方法并不是 “十分好用”。

在这种情况下,可以计算目标元素需要偏移的距离,并使用 window.scrollTo “精确”的使页面滚动到指定位置。

js
window.scrollTo({
  top: 0,
  left: 0,
  behavior: 'smooth'
})

其中 top 对应顶部滚动距离,left 对应左侧滚动距离。

topleft 的具体值的获取,可以利用本节中已经声明的方法,有两种方式:

  1. getScrollTopgetScrollLeft, 结合 getRect
  2. 直接利用 getOffsetTop

其中 behavior 属性可设置为 smooth,页面会平滑过渡。

该属性也可以使用 css 属性进行替代。

监听 scroll 事件的容器上设置:

css
.scroll-target {
  scroll-behavior: 'smooth';
}

4.页面滚动到指定位置

在本节中,我们介绍下,实现页面滚动到指定位置的几种方式。

4-1.a 链接

该方法是 “原始时代” 流传下来的方式。

操作方便、实现简单。

但如果想要体验友好的话,需要设置 css 属性 scroll-behavior: 'smooth';

4-2.scrollIntoView

scrollIntoView

取决于其它元素的布局情况,此元素可能不会完全滚动到顶端或底端

4-3.scrollTo

关于 window.scrollTo 方法的重点在于如何计算滚动距离

在本例中利用了 scrollTop 属性结合 getBoundingClientRect 方法。

此外也可以使用我们在前俩节中提到的 offsetTop 进行计算。

相应的,也可以利用对应 API 来实现横向滚动:

5.滚动条

scroll 相关需求中,有可能需要设置滚动条。

在此粗略记录下。

5-1.隐藏滚动条

css
/* 隐藏滚动条 */
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; */
}