【问题标题】:How do people handle scroll restoration with react-router v4?人们如何使用 react-router v4 处理滚动恢复?
【发布时间】:2017-12-11 17:20:31
【问题描述】:

在使用 react-router 时,我在返回按钮(历史弹出状态)上的滚动位置遇到了一些问题。 React 路由器 v4 不处理开箱即用的滚动管理,因为浏览器正在实现一些自动滚动行为。这很好,除非浏览器窗口的高度从一个视图到另一个视图变化太大。我已经实现了 ScrollToTop 组件,如下所述:https://reacttraining.com/react-router/web/guides/scroll-restoration

这很好用。当您单击链接并转到其他组件时,浏览器会滚动到顶部(就像普通的服务器渲染网站一样)。只有当您返回(通过浏览器后退按钮)到具有更高窗口高度的视图时,才会出现此问题。在 react 渲染内容(和浏览器高度)之前,似乎 (chrome) 试图转到上一页的滚动位置。这导致滚动只能根据它来自的视图的高度向下移动。想象一下这个场景:

View1:一长串电影(窗口高度 3500 像素)。
(点击电影)
View2:详细信息所选电影的视图(窗口高度:1000 像素)。
(单击浏览器后退按钮)
返回到视图 1,但滚动位置不能超过 1000px,因为 chrome 试图在 react 渲染长电影列表之前设置位置。

由于某种原因,这只是 Chrome 中的一个问题。 Firefox 和 Safari 似乎处理得很好。我想知道是否有其他人遇到过这个问题,以及你们通常如何在 React 中处理滚动恢复。

注意:所有电影都是从 sampleMovies.js 导入的——所以在我的示例中我不等待 API 响应。

【问题讨论】:

  • 嘿,你解决问题了吗?我面临着完全相同的问题
  • 4 年后仍然无法在 Chrome 中工作,但在 FF 和 Safari 中还可以。疯了……

标签: reactjs react-router


【解决方案1】:

请注意,history.scrollRestoration 只是一种禁用浏览器自动尝试滚动恢复的方法,这主要不适用于单页应用程序,因此它们不会干扰应用程序想要执行的任何操作。除了切换到手动滚动恢复之外,您还需要某种库来集成浏览器的历史 API、React 的渲染以及窗口的滚动位置和任何可滚动的块元素。

在找不到这样的 React Router 4 滚动恢复库后,我创建了一个名为 react-scroll-manager 的库。它支持在导航到新位置时滚动到顶部(又名历史推送)和向后/向前滚动恢复(又名历史弹出)。除了滚动窗口之外,它还可以滚动您包装在 ElementScroller 组件中的任何嵌套元素。它还支持延迟/异步渲染,方法是使用MutationObserver 在用户指定的时间限制内观看窗口/元素内容。这种延迟渲染支持适用于滚动恢复以及使用哈希链接滚动到特定元素。

npm install react-scroll-manager

import React from 'react';
import { Router } from 'react-router-dom';
import { ScrollManager, WindowScroller, ElementScroller } from 'react-scroll-manager';
import { createBrowserHistory as createHistory } from 'history';

class App extends React.Component {
  constructor() {
    super();
    this.history = createHistory();
  }
  render() {
    return (
      <ScrollManager history={this.history}>
        <Router history={this.history}>
          <WindowScroller>
            <ElementScroller scrollKey="nav">
              <div className="nav">
                ...
              </div>
            </ElementScroller>
            <div className="content">
              ...
            </div>
          </WindowScroller>
        </Router>
      </ScrollManager>
    );
  }
}

请注意,需要 HTML5 浏览器(IE 为 10+)和 React 16。 HTML5 提供历史 API,该库使用来自 React 16 的现代 Context 和 Ref API。

【讨论】:

  • 嘿,我只是想说我今天找到了你的图书馆,而且效果很好!谢谢你!
  • 您还应该在滚动到顶部的同时重置键盘焦点。我写了一个东西来处理它:github.com/oaf-project/oaf-react-router
  • @Trevor Robinson,您能否确认您的解决方案在 React Router v5 中有效?如您所述,我已经尝试过 ElementScroller 和 WindowScroller ,但它根本不起作用。没有错误。
  • 请看一下这个问题:stackoverflow.com/questions/58772954/… 谢谢!
  • 我确实让它部分地与 React Router v4 一起工作。导航到新页面的链接点击滚动到顶部,但历史记录中的滚动位置没有保留。
【解决方案2】:

您如何处理滚动恢复?

原来浏览器已经实现了 history.scrollRestoration。

也许你可以使用它?检查这些链接。

https://developer.mozilla.org/en/docs/Web/API/History#Specifications https://developers.google.com/web/updates/2015/09/history-api-scroll-restoration

此外,我发现了一个 npm 模块,它可能能够轻松地在 react 中处理滚动恢复,但这个库仅适用于 react router v3 及以下版本

https://www.npmjs.com/package/react-router-restore-scroll https://github.com/ryanflorence/react-router-restore-scroll

我希望这会有所帮助。

【讨论】:

  • 如 react-scroll-manager 文档中所述,history.scrollRestoration 在浏览器之间并不一致。我发现它在 Chrome 中根本不起作用。
【解决方案3】:

我最终使用 localStorage 来跟踪滚动位置 - 不确定这是否能处理所有情况。

在此示例中,有一个包含一组商店的公司页面,每个商店都有一组展示柜。我需要跟踪展示柜的滚动位置,因此将其保存到“storeScrollTop”键。需要添加 6 行代码。

公司.jsx:

// click on a store link
const handleClickStore = (evt) => {
  window.localStorage.removeItem('storeScrollTop') // <-- reset scroll value
  const storeId = evt.currentTarget.id
  history.push(`/store/${storeId}`)
}

store.jsx:

// initialize store page
React.useEffect(() => {
  // fetch displays
  getStoreDisplays(storeId).then(objs => setObjs(objs)).then(() => {
    // get the 'store' localstorage scrollvalue and scroll to it
    const scrollTop = Number(window.localStorage.getItem('storeScrollTop') || '0')
    const el = document.getElementsByClassName('contents')[0]
    el.scrollTop = scrollTop
  })
}, [storeId])

// click on a display link    
const handleClickDisplay = (evt) => {
  // save the scroll pos for return visit
  const el = document.getElementsByClassName('contents')[0]
  window.localStorage.setItem('storeScrollTop', String(el.scrollTop))
  // goto the display
  const displayId = evt.currentTarget.id
  history.push(`/display/${displayId}`)
}

最棘手的部分是找出哪个元素具有正确的 scrollTop 值 - 我必须在控制台中检查内容,直到找到它。

【讨论】:

    【解决方案4】:

    我的解决方案:使用 Map (ES6) 为每个路径名保存 window.scrollY

    滚动管理器.tsx

    import { FC, useEffect } from 'react';
    import { useLocation } from 'react-router-dom';
    
    export function debounce(fn: (...params: any) => void, wait: number): (...params: any) => void {
      let timer: any = null;
      return function(...params: any){
        clearTimeout(timer);
        timer = setTimeout(()=>{
          fn(...params)
        }, wait);
      }
    }
    
    export const pathMap = new Map<string, number>();
    
    const Index: FC = () => {
      const { pathname } = useLocation();
    
      useEffect(() => {
        if (pathMap.has(pathname)) {
          window.scrollTo(0, pathMap.get(pathname)!)
        } else {
          pathMap.set(pathname, 0);
          window.scrollTo(0, 0);
        }
      }, [pathname]);
    
      useEffect(() => {
        const fn = debounce(() => {
          pathMap.set(pathname, window.scrollY);
        }, 200);
    
        window.addEventListener('scroll', fn);
        return () => window.removeEventListener('scroll', fn);
      }, [pathname]);
    
      return null;
    };
    
    export default Index;
    

    App.tsx

    function App() {
    
      return (
          <Router>
            <ScrollManager/>
            <Switch>...</Switch>
          </Router>
      );
    }
    

    也可以使用pathMap.size === 1判断用户是否是第一次进入应用

    【讨论】:

      【解决方案5】:

      如果页面是新的但如果页面在恢复滚动之前看到,则此组件向上滚动。

      滚动到顶部.tsx 文件:

      import { useEffect, FC } from 'react';
      import { useLocation } from 'react-router-dom';
      
      const pathNameHistory = new Set<string>();
      
      const Index: FC = (): null => {
        const { pathname } = useLocation();
      
        useEffect((): void => {
          if (!pathNameHistory.has(pathname)) {
            window.scrollTo(0, 0);
            pathNameHistory.add(pathname);
          }
        }, [pathname]);
      
        return null;
      };
      
      export default Index;
      
      

      app.tsx 文件:

      <BrowserRouter>
        <ScrollToTop />
        <App />
      </BrowserRouter>
      

      【讨论】:

        【解决方案6】:

        使用这个库 https://www.npmjs.com/package/react-scroll-restoration

        React Router 不提供开箱即用的滚动恢复支持,目前也不支持,因为浏览器正在实现一些自动滚动行为

        【讨论】:

          猜你喜欢
          • 2021-06-29
          • 2020-03-05
          • 2021-08-27
          • 2022-10-19
          • 1970-01-01
          • 2018-01-21
          • 2019-01-02
          • 2018-03-22
          • 1970-01-01
          相关资源
          最近更新 更多