Reactjs 人们如何使用react router v4处理滚动恢复?

Reactjs 人们如何使用react router v4处理滚动恢复?,reactjs,react-router,Reactjs,React Router,使用react router时,我在后退按钮(history popstate)上的滚动位置遇到一些问题。React router v4不处理开箱即用的滚动管理,因为浏览器正在实现一些自动滚动行为。这很好,除非浏览器窗口的高度从一个视图到另一个视图的变化太大。我已经实现了如下所述的ScrollToTop组件: 这很有效。当您单击一个链接并转到其他组件时,浏览器会滚动到顶部(就像普通服务器呈现的网站一样)。仅当您返回(通过浏览器的“返回”按钮)到窗口高度更高的视图时,才会发生此问题。在react呈

使用react router时,我在后退按钮(history popstate)上的滚动位置遇到一些问题。React router v4不处理开箱即用的滚动管理,因为浏览器正在实现一些自动滚动行为。这很好,除非浏览器窗口的高度从一个视图到另一个视图的变化太大。我已经实现了如下所述的ScrollToTop组件:

这很有效。当您单击一个链接并转到其他组件时,浏览器会滚动到顶部(就像普通服务器呈现的网站一样)。仅当您返回(通过浏览器的“返回”按钮)到窗口高度更高的视图时,才会发生此问题。在react呈现内容(和浏览器高度)之前,(chrome)似乎试图转到上一页的滚动位置。这会导致滚动条根据其所在视图的高度尽可能向下移动。想象一下这个场景:

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

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


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

如何处理滚动恢复

事实证明,浏览器有history.scroll恢复的实现

也许你可以用这个?查看这些链接

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


我希望这能有所帮助。

请注意,
历史记录。滚动恢复
只是一种禁用浏览器自动尝试滚动恢复的方法,滚动恢复大多不适用于单页应用程序,因此它们不会干扰应用程序想做的任何事情。除了切换到手动滚动恢复之外,您还需要某种库来提供浏览器历史API、React渲染、窗口的滚动位置和任何可滚动块元素之间的集成

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

npm安装反应滚动管理器

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>
    );
  }
}
从“React”导入React;
从“react Router dom”导入{Router};
从“react scroll manager”导入{scroll manager、WindowScroller、ElementScroller};
从“历史”导入{createBrowserHistory as createHistory};
类应用程序扩展了React.Component{
构造函数(){
超级();
this.history=createHistory();
}
render(){
返回(
...
...
);
}
}

请注意,需要HTML5浏览器(10+用于IE)和React 16。HTML5提供了历史API,该库使用React 16中的现代上下文和Ref API。

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

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

company.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}`)
}

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

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

滚动至top.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;

<BrowserRouter>
  <ScrollToTop />
  <App />
</BrowserRouter>
从'react'导入{useffect,FC};
从'react router dom'导入{useLocation};
const pathNameHistory=新集合();
常量索引:FC=():null=>{
const{pathname}=useLocation();
useffect(():void=>{
如果(!pathNameHistory.has(pathname)){
滚动到(0,0);
添加(路径名);
}
},[pathname]);
返回null;
};
出口违约指数;
app.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;

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

使用此库


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

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

scroll-manager.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;
从'react'导入{FC,useffect};
从'react router dom'导入{useLocation};
导出函数反Bounce(fn:(…参数:任意)=>void,wait:number):(…参数:任意)=>void{
let timer:any=null;
返回函数(…参数:任意){
清除超时(计时器);
计时器=设置超时(()=>{
fn(…参数)
},等等);
}
}
导出常量路径映射=新映射();
常数索引:FC=()=>{
const{pathname}=useLocation();
useffect(()=>{
if(pathMap.has(路径名)){
window.scr