# 回到顶部的几种玩法

> 前不久优化 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.

因此做兼容的话，可以通过两者相加来获取滚动条距离顶端的位置。

这两个属性可以被设置，所以业界常用的回到顶部的做法如下。

```text
document.body.scrollTop = document.documentElement.scrollTop = 0

```

最后扩充一个属性 `window.pageYOffset`, 这个属性同样是获取滚动条距离顶端的位置，在 Chrome 和 FF 都可以使用，**⚠️ 但是这个属性不能够设置位置，并且在 Safari 上不能用**

## scrollTo()

用过 React 的同学应该对这个方法不陌生。因为用到了 hashHistory，它是建立在 history 之上的，当路由发生变化时会记住原路由的状态，跳转新页面后默认停留在原页面的位置。\
所以我们经常用这个简单方法让页面回到顶部(当然关于 withRouter 是另外一个话题了，详情戳 [scroll-restoration](https://github.com/ReactTraining/react-router/blob/master/packages/react-router-dom/docs/guides/scroll-restoration.md))

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’ ，可以平滑的滚动到顶部，很赞。

```text
window.scrollTo({
    left: 0,
    top: 0,
    behavior: 'smooth' / 'instant' / 'auto'
})

```

在挖一下，还有一个方法是 `window.scroll()`， 它和 `window.scrollTo()` 的用法一模一样。

## scrollBy()

这个方法和 `scrollTo()` 的传参形式一样，都是传递 `x-coord, y-coord` 或者 `option`，不同的是， `scrollTo()` 传递的参数是一个对于文档的一个绝对的量，而 `scrollBy()` 传递的是一个相对于自身当前位置的一个量。

因此，可以用下面的方式回到顶部。

```text
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，但如果页面很长的话，这个速度就会很慢。

```text
   public scrollToTop1 = () => {
    let time: number = 0;
    document.documentElement.scrollTop -= 160;
    if (document.documentElement.scrollTop <= 0) {
      window.cancelAnimationFrame(time);
    } else {
      time = window.requestAnimationFrame(this.scrollToTop);
    }
  };
```

改进版：

```text
  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);
      }
    });
  };
```


---

Source: https://yanceyleo.com/post/2d652ca7-b7c4-4980-9085-cc4e590d07a9
Published: 2020-03-14
Tags: JavaScript, 回到顶部, WebAPI