如何滚动到一个元素?

444

我有一个聊天小部件,每次向上滚动时会拉出一系列消息的数组。 我现在面临的问题是当消息加载时,滑块保持固定在顶部。 我希望它能专注于以前数组的最后一个索引元素。 我想出了通过传递索引来生成动态引用的方法,但我还需要知道要使用什么样的滚动函数才能实现这一点。

 handleScrollToElement(event) {
    const tesNode = ReactDOM.findDOMNode(this.refs.test)
    if (some_logic){
      //scroll to testNode      
    }
  }

  render() {

    return (
      <div>
        <div ref="test"></div>
      </div>)
  }

1
针对捆绑解决方案,请访问:https://www.npmjs.com/package/react-scroll-to-component - tokland
30个回答

784

React 16.8 +,函数式组件

const ScrollDemo = () => {
   const myRef = useRef(null)

   const executeScroll = () => myRef.current.scrollIntoView()    
   // run this function from an event handler or an effect to execute scroll 

   return (
      <> 
         <div ref={myRef}>Element to scroll to</div> 
         <button onClick={executeScroll}> Click to scroll </button> 
      </>
   )
}

点击此处以获取有关StackBlitz的完整演示

React 16.3 +,类组件

class ReadyToScroll extends Component {
    constructor(props) {
        super(props)
        this.myRef = React.createRef()  
    }

    render() {
        return <div ref={this.myRef}>Element to scroll to</div> 
    }  

    executeScroll = () => this.myRef.current.scrollIntoView()
    // run this method to execute scrolling. 
}

类组件 - 引用回调

class ReadyToScroll extends Component {  
    render() {
        return <div ref={ (ref) => this.myRef=ref }>Element to scroll to</div>
    } 

    executeScroll = () => this.myRef.scrollIntoView()
    // run this method to execute scrolling. 
}

不要使用字符串引用。

字符串引用会影响性能,不可组合,并且正在逐渐被淘汰(2018年8月)。

字符串引用存在一些问题,被认为是遗留代码,将很可能在未来的版本中被移除。[React官方文档]

资源1资源2

可选:平滑滚动动画

/* css */
html {
    scroll-behavior: smooth;
}

传递 ref 给子组件

我们希望将 ref 附加到一个 DOM 元素上,而不是一个 React 组件上。因此,在将其传递给子组件时,我们不能将 prop 命名为 ref。

const MyComponent = () => {
    const myRef = useRef(null)
    return <ChildComp refProp={myRef}></ChildComp>
} 

然后将ref prop附加到DOM元素。
const ChildComp = (props) => {
    return <div ref={props.refProp} />
}

12
window.scrollTo(0, offsetTop)是当前浏览器支持更好的更好的选择。 - MoMo
1
你能确保你的示例是一致的吗?我们从myRef开始,接着是domRef,最后是tesNode?这很令人困惑。 - Louis Lecocq
10
事后显而易见,但需要强调的是,这仅适用于原生DOM元素,而不是任何React组件。 - jpunk11
2
@SimonFranzen 请看一下我的更新答案 - TLDR - 类组件情况。当调用 scrollToMyRef 时,它将滚动到您附加 ref 的子元素。您可以将该方法传递给另一个子组件,并从那里触发它。 - Ben Carp
1
这个代码 this.myRef.current.getBoundingClientRect().top 对我的情况有效,而 this.myRef.current.top 不会返回正确的元素位置。 - HungDQ
显示剩余31条评论

143

这对我有效

this.anyRef.current.scrollIntoView({ behavior: 'smooth', block: 'start' })

编辑:根据评论,我想进一步阐述这一点。

const scrollTo = (ref) => {
  if (ref && ref.current /* + other conditions */) {
    ref.current.scrollIntoView({ behavior: 'smooth', block: 'start' })
  }
}

<div ref={scrollTo}>Item</div>

2
把这个放在哪里? - su_sundariya
3
非常有效。以上所有方法对我都不起作用,这应该被接受为答案! - Shin
1
适用于我,只是请注意,“start”是“block”参数的默认值。 - Liron Lavi
我不确定你是否有其他滚动条(如身体/ html上的滚动条),但这将滚动所有内容。如果您的组件位于可滚动页面的底部,scrollIntoView将滚动父级和子级。 - Tek
即使在 API 调用并更新状态之后呈现元素,ref.current 仍然保持未定义。为什么? - Junaid
显示剩余5条评论

67

我有一个简单的场景,当用户点击我的Material UI导航栏中的菜单项时,我希望将他们滚动到页面上的某个部分。 我可以使用refs并将它们穿过所有组件,但我讨厌通过多个组件传递props,因为这会使代码变得脆弱。

我只在我的React组件中使用了原生JS,结果证明它完全正常。 在我想要滚动到的元素上放置了一个ID,在我的标题组件中我只是这样做。

const scroll = () => {
  const section = document.querySelector( '#contact-us' );
  section.scrollIntoView( { behavior: 'smooth', block: 'start' } );
};

我需要从组件A的点击滚动到组件B中的一个元素。这完美地起作用了! - av av
谢谢。在测试了其他解决方案后,只有这个符合我的预期。 - miko866
1
简单明了的解决方案,完美地运作。干得好! - i_am_daim
1
完美!2023年与React一起使用。 - Chefk5
使用document.querySelector()而不是ref是一种不好的做法吗?这是一种反模式吗? - yeln
1
是的,从技术上讲这是一种反模式,首选的方法是使用 refs。然而,如果你的组件在组件树中与其他组件不接近,那么线程引用会成为一场噩梦。考虑到使用引用带来的技术复杂性,我做出了一个权衡决策,在这种情况下不使用引用也是可以接受的。 - Jose

50

只需找到您已经确定的元素的顶部位置 https://www.w3schools.com/Jsref/prop_element_offsettop.asp,然后通过scrollTo方法滚动到该位置 https://www.w3schools.com/Jsref/met_win_scrollto.asp

类似这样的内容应该有效:

handleScrollToElement(event) {
  const tesNode = ReactDOM.findDOMNode(this.refs.test)
  if (some_logic){
    window.scrollTo(0, tesNode.offsetTop);
  }
}

render() {

  return (
    <div>
      <div ref="test"></div>
    </div>)
}

更新:

自从React v16.3以来,React.createRef()更受欢迎。

constructor(props) {
  super(props);
  this.myRef = React.createRef();
}

handleScrollToElement(event) {
  if (<some_logic>){
    window.scrollTo(0, this.myRef.current.offsetTop);
  }
}

render() {

  return (
    <div>
      <div ref={this.myRef}></div>
    </div>)
}

3
这是更好的答案。使用 ReactDOM.findDomNode() 是更好的实践 - 因为 React 重新渲染组件,通过 ID 获取的 div 在调用函数时可能不存在。 - Good Idea
5
根据官方文档,应尽量避免使用findDOMNode。在大多数情况下,您可以将ref附加到DOM节点上,并完全避免使用findDOMNode - Facyo Kouch
1
请注意,通过字符串映射使用this.refs已过时,请参见:https://dev59.com/LFcP5IYBdhLWcg3wxMs5 - Himmet Avsar
1
注意:我不得不使用this.myRef.current.scrollIntoView()而不是window.scrollTo(0, this.myRef) - Babbz77

21

2019年7月 - 专用钩子/函数

专用钩子/函数可以隐藏实现细节,并为组件提供简单的API。

React 16.8 + 函数式组件

const useScroll = () => {
  const elRef = useRef(null);
  const executeScroll = () => elRef.current.scrollIntoView();

  return [executeScroll, elRef];
};

在任何功能组件中使用它。

const ScrollDemo = () => {
    const [executeScroll, elRef] = useScroll()
    useEffect(executeScroll, []) // Runs after component mounts
    
    return <div ref={elRef}>Element to scroll to</div> 
}

完整演示

React 16.3 + 类组件

const utilizeScroll = () => {
  const elRef = React.createRef();
  const executeScroll = () => elRef.current.scrollIntoView();

  return { executeScroll, elRef };
};

可以在任何类组件中使用它。

class ScrollDemo extends Component {
  constructor(props) {
    super(props);
    this.elScroll = utilizeScroll();
  }

  componentDidMount() {
    this.elScroll.executeScroll();
  }

  render(){
    return <div ref={this.elScroll.elRef}>Element to scroll to</div> 
  }
} 

完整演示


1
对于任何使用Typescript的人,你应该将useScroll的返回类型声明为[() => void, RefObject<HTMLInputElement>]。然后你就可以毫无问题地调用executeScroll()了。 - kasztof
1
@kasztof,我是TS的粉丝,但我听到了一个强烈的观点,认为在JS SOF问题中不应该使用TS,因为这可能会使非TS开发人员更难理解。我知道你面临的问题。您还可以将返回值转换为const。我们可以在注释或答案末尾添加TS定义。 - Ben Carp
1
TypeScript: const myRef = useRef<HTMLDivElement>(null); const executeScroll = () => { if (myRef.current) { myRef.current.scrollIntoView(); } }; - user42488

18

我正在尝试使用您的代码。通过console.log可以看到它正在执行您的window.scrollTo语句(已调整为我的情况),但它仍然无法滚动。这可能与我是否使用了React Bootstrap Modal有关吗? - robertwerner_sf

14

使用findDOMNode方法最终会被弃用。

首选的方法是使用回调引用。

GitHub Eslint


3
请在您的回答中包含相关链接材料的部分,以防该链接被删除后您的回答变得无用。 - totymedli

11

最好的方法是使用 element.scrollIntoView({ behavior: 'smooth' })。这将使用漂亮的动画将元素滚动到视图中。

当与React的useRef()结合使用时,可以按照以下方式完成。

import React, { useRef } from 'react'

const Article = () => {
  const titleRef = useRef()

  function handleBackClick() {
      titleRef.current.scrollIntoView({ behavior: 'smooth' })
  }

  return (
      <article>
            <h1 ref={titleRef}>
                A React article for Latin readers
            </h1>

            // Rest of the article's content...

            <button onClick={handleBackClick}>
                Back to the top
            </button>
        </article>
    )
}

当您想要滚动到React组件时,需要将ref转发给呈现的元素。本文将更深入地探讨这个问题


1
这样好多了。我最初是使用 (ref) => window.scrollTo(0, ref.current.offsetTop),但只得到了一个很小的偏移量,没有到达目标位置。我认为这是因为引用位置在开始时被计算出来,然后没有更新。你的建议解决了我的问题,而被接受的答案却没有。 - Willy

10

您也可以使用 scrollIntoView 方法来滚动到指定的元素。

handleScrollToElement(event) {
const tesNode = ReactDOM.findDOMNode(this.refs.test)
 if (some_logic){
  tesNode.scrollIntoView();
  }
 }

 render() {
  return (
   <div>
     <div ref="test"></div>
   </div>)
}

10

我可能晚了加入这个派对,但我一直在尝试以正确的方式向我的项目实现动态引用,并且到目前为止我找到的所有答案都不能令我满意,因此我想出了一个解决方案,我认为它很简单并且使用了React的原生和推荐的方法来创建引用。

有时你会发现文档编写的方式假定你有一个已知数量的视图,在大多数情况下,这个数量是未知的,因此您需要一种解决这个问题的方法,创建动态引用以显示类中需要显示的未知数量的视图。

所以我能想到最简单的解决方案,也是完美无缺的解决方案,就是按照以下方式操作:

class YourClass extends component {

state={
 foo:"bar",
 dynamicViews:[],
 myData:[] //get some data from the web
}

inputRef = React.createRef()

componentDidMount(){
  this.createViews()
}


createViews = ()=>{
const trs=[]
for (let i = 1; i < this.state.myData.lenght; i++) {

let ref =`myrefRow ${i}`

this[ref]= React.createRef()

  const row = (
  <tr ref={this[ref]}>
<td>
  `myRow ${i}`
</td>
</tr>
)
trs.push(row)

}
this.setState({dynamicViews:trs})
}

clickHandler = ()=>{

//const scrollToView = this.inputRef.current.value
//That to select the value of the inputbox bt for demostrate the //example

value=`myrefRow ${30}`

  this[value].current.scrollIntoView({ behavior: "smooth", block: "start" });
}


render(){

return(
<div style={{display:"flex", flexDirection:"column"}}>
<Button onClick={this.clickHandler}> Search</Button>
<input ref={this.inputRef}/>
<table>
<tbody>
{this.state.dynamicViews}
<tbody>
<table>
</div>


)

}

}

export default YourClass

那样滚动条就会滑到你要查找的行...

祝好运并希望它能帮助其他人。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接