回到顶部的几种玩法
前不久优化 FE 的时候用 withRouter 封装了一个 ScrollRestoration 组件,里面用到了 window.scrollTo(0, 0)。而 FE 回到顶部的小猫用的是 requestAnimationFrame,然后去网上搜索一下发现还有其他一些冷门的方法,遂结合 caniuse 和例子总结一番。
scrollTop
在 Chrome 中, 通过 document.documentElement.scrollTop
来获取滚动条距离顶端的位置;而在 FF 和 Safari 中,则是通过 document.body.scrollTop
.
在 Chrome 中,document.body.scrollTop
恒为 0, 在 FF 和 Safari 中,document.documentElement.scrollTop
恒为 0.
因此做兼容的话,可以通过两者相加来获取滚动条距离顶端的位置。
这两个属性可以被设置,所以业界常用的回到顶部的做法如下。
document.body.scrollTop = document.documentElement.scrollTop = 0
最后扩充一个属性 window.pageYOffset
, 这个属性同样是获取滚动条距离顶端的位置,在 Chrome 和 FF 都可以使用,⚠️ 但是这个属性不能够设置位置,并且在 Safari 上不能用
scrollTo()
用过 React 的同学应该对这个方法不陌生。因为用到了 hashHistory,它是建立在 history 之上的,当路由发生变化时会记住原路由的状态,跳转新页面后默认停留在原页面的位置。 所以我们经常用这个简单方法让页面回到顶部(当然关于 withRouter 是另外一个话题了,详情戳 scroll-restoration)
window.scrollTo() 接收两种参数形式。
第一种是 window.scrollTo(x-coord, y-coord )
, 其中 x-coord
是文档中的横轴坐标, y-coord
是文档中的纵轴坐标,所以一般通过 window.scrollTo(0, 0)
的方式回到顶部。
window.scrollTo(options)
第二种是 window.scrollTo(options)
, 接受一个参数对象,其中 top 相当于 y-coord,left 相当于 x-coord,而 behavior 里有个属性值是 ‘smooth’ ,可以平滑的滚动到顶部,很赞。
window.scrollTo({
left: 0,
top: 0,
behavior: 'smooth' / 'instant' / 'auto'
})
在挖一下,还有一个方法是 window.scroll()
, 它和 window.scrollTo()
的用法一模一样。
scrollBy()
这个方法和 scrollTo()
的传参形式一样,都是传递 x-coord, y-coord
或者 option
,不同的是, scrollTo()
传递的参数是一个对于文档的一个绝对的量,而 scrollBy()
传递的是一个相对于自身当前位置的一个量。
因此,可以用下面的方式回到顶部。
const top = document.body.scrollTop || document.documentElement.scrollTop
scrollBy(0,-top);
当然,FF 还有两个方法叫做 window.scrollByLines(lines)
和 window.scrollByPages()
, 两种方法在 Chrome 和 Safari 无效,当作了解即可。
其他
当然还看到别人在说在文档头部放一个隐藏的锚点,或者用 element.scrollIntoView()
之类的方式,这些就有些反人类了,这里不打算介绍。
番外篇:平滑滚动
这里主要谈一谈 requestAnimationFrame
,我在 FE 回到顶部的小猫用到了这个 API,一开始是这样写的,但这样有个问题是写死了每次递归只往上走 160px,但如果页面很长的话,这个速度就会很慢。
public scrollToTop1 = () => {
let time: number = 0;
document.documentElement.scrollTop -= 160;
if (document.documentElement.scrollTop <= 0) {
window.cancelAnimationFrame(time);
} else {
time = window.requestAnimationFrame(this.scrollToTop);
}
};
改进版:
public scrollToTop = () => {
let timer: number = 0;
cancelAnimationFrame(timer);
const startTime = +new Date();
const b = document.body.scrollTop || document.documentElement.scrollTop;
const d = 500;
const c = b;
timer = requestAnimationFrame(function func() {
const t = d - Math.max(0, startTime - +new Date() + d);
document.documentElement.scrollTop = document.body.scrollTop =
(t * -c) / d + b;
timer = requestAnimationFrame(func);
if (t === d) {
cancelAnimationFrame(timer);
}
});
};
PREVIOUS POST
防抖和节流
NEXT POST
深入理解并手写遵循 Promise/A+ 规范的 Promise