如何滚动 div 以在 ReactJS 中可见?

Posted

技术标签:

【中文标题】如何滚动 div 以在 ReactJS 中可见?【英文标题】:How can I scroll a div to be visible in ReactJS? 【发布时间】:2015-08-10 06:47:04 【问题描述】:

我有一个弹出列表,它是一个div,其中包含子divs 的垂直列表。我添加了向上/向下键盘导航来更改当前突出显示的子项。

现在,如果我按下向下键的次数足够多,突出显示的项目将不再可见。如果滚动视图,向上键也会发生同样的情况。

在 React 中自动将子 div 滚动到视图中的正确方法是什么?

【问题讨论】:

【参考方案1】:

我假设你有某种List 组件和某种Item 组件。我这样做in one project 的方式是让该项目知道它是否处于活动状态;如有必要,该项目会要求列表将其滚动到视图中。考虑以下伪代码:

class List extends React.Component 
  render() 
    return <div>this.props.items.map(this.renderItem)</div>;
  

  renderItem(item) 
    return <Item key=item.id item=item
                 active=item.id === this.props.activeId
                 scrollIntoView=this.scrollElementIntoViewIfNeeded />
  

  scrollElementIntoViewIfNeeded(domNode) 
    var containerDomNode = React.findDOMNode(this);
    // Determine if `domNode` fully fits inside `containerDomNode`.
    // If not, set the container's scrollTop appropriately.
  


class Item extends React.Component 
  render() 
    return <div>something...</div>;
  

  componentDidMount() 
    this.ensureVisible();
  

  componentDidUpdate() 
    this.ensureVisible();
  

  ensureVisible() 
    if (this.props.active) 
      this.props.scrollIntoView(React.findDOMNode(this));
    
  

一个更好的解决方案可能是让列表负责将项目滚动到视图中(而不让项目知道它甚至在列表中)。为此,您可以将ref 属性添加到某个项目并使用它找到它:

class List extends React.Component 
  render() 
    return <div>this.props.items.map(this.renderItem)</div>;
  

  renderItem(item) 
    var active = item.id === this.props.activeId;
    var props = 
      key: item.id,
      item: item,
      active: active
    ;
    if (active) 
      props.ref = "activeItem";
    
    return <Item ...props />
  

  componentDidUpdate(prevProps) 
    // only scroll into view if the active item changed last render
    if (this.props.activeId !== prevProps.activeId) 
      this.ensureActiveItemVisible();
    
  

  ensureActiveItemVisible() 
    var itemComponent = this.refs.activeItem;
    if (itemComponent) 
      var domNode = React.findDOMNode(itemComponent);
      this.scrollElementIntoViewIfNeeded(domNode);
    
  

  scrollElementIntoViewIfNeeded(domNode) 
    var containerDomNode = React.findDOMNode(this);
    // Determine if `domNode` fully fits inside `containerDomNode`.
    // If not, set the container's scrollTop appropriately.
  

如果您不想通过数学计算来确定该项目是否在列表节点中可见,您可以使用DOM method scrollIntoView() 或Webkit 特定的scrollIntoViewIfNeeded,其中has a polyfill available 以便您可以使用它在非 Webkit 浏览器中。

【讨论】:

谢谢,你的第一个例子是我想的,但我更喜欢你的第二个例子,子节点不需要担心。 哇,Element.scrollIntoView() 太棒了。 请注意:在 React 15.x 及更高版本中,您将希望使用 ReactDom.findDOMNode 而不是 React.findDOMNode 我展示了另一种类似的方法,我在列表底部添加一个空元素,我总是可以在 this answer 中滚动到该元素。 将 ref 设置为字符串是 deprecated。建议改用callback pattern 或createRef API。【参考方案2】:

对于 React 16,正确答案与之前的答案不同:

class Something extends Component 
  constructor(props) 
    super(props);
    this.boxRef = React.createRef();
  

  render() 
    return (
      <div ref=this.boxRef />
    );
  

然后滚动,只需添加(在构造函数之后):

componentDidMount() 
  if (this.props.active)  // whatever your test might be
    this.boxRef.current.scrollIntoView();
  

注意:你必须使用'.current',你可以发送选项到scrollIntoView:

scrollIntoView(
  behavior: 'smooth',
  block: 'center',
  inline: 'center',
);

(发现于http://www.albertgao.xyz/2018/06/07/scroll-a-not-in-view-component-into-the-view-using-react/)

阅读规范,有点难以理解块和内联的含义,但在玩过之后,我发现对于垂直滚动列表,块:'结束'确保元素可见,而不是人为地将我的内容顶部滚动到视口之外。使用“中心”,靠近底部的元素会向上滑动太多,并在其下方出现空白空间。但是我的容器是一个带有 justify: 'stretch' 的弹性父容器,因此可能会影响行为。我没有深入挖掘。隐藏溢出的元素会影响 scrollIntoView 的行为,因此您可能需要自己进行试验。

我的应用程序有一个必须在视图中的父级,如果选择了一个子级,它也会滚动到视图中。这很好用,因为父 DidMount 发生在子 DidMount 之前,因此它滚动到父级,然后在呈现活动子级时,进一步滚动以显示该子级。

【讨论】:

AFAIK,父母的 didMount 发生在孩子 ***.com/questions/32814970/… 是的,这是真的,@gaurav5430。不知道我在想什么! 如果您收到“scrollIntoView 不是函数”的错误消息,请记住将 ref 分配给标准 DOM 元素,例如 div,而不是 React 组件。更多详情here.【参考方案3】:

另一个在 ref 中使用函数而不是字符串的例子

class List extends React.Component 
  constructor(props) 
    super(props);
    this.state =  items:[], index: 0 ;
    this._nodes = new Map();

    this.handleAdd = this.handleAdd.bind(this);
    this.handleRemove = this.handleRemove.bind(this);
   

  handleAdd() 
    let startNumber = 0;
    if (this.state.items.length) 
      startNumber = this.state.items[this.state.items.length - 1];
    

    let newItems = this.state.items.splice(0);
    for (let i = startNumber; i < startNumber + 100; i++) 
      newItems.push(i);
    

    this.setState( items: newItems );
  

  handleRemove() 
    this.setState( items: this.state.items.slice(1) );
  

  handleShow(i) 
    this.setState(index: i);
    const node = this._nodes.get(i);
    console.log(this._nodes);
    if (node) 
      ReactDOM.findDOMNode(node).scrollIntoView(block: 'end', behavior: 'smooth');
    
  

  render() 
    return(
      <div>
        <ul>this.state.items.map((item, i) => (<Item key=i ref=(element) => this._nodes.set(i, element)>item</Item>))</ul>
        <button onClick=this.handleShow.bind(this, 0)>0</button>
        <button onClick=this.handleShow.bind(this, 50)>50</button>
        <button onClick=this.handleShow.bind(this, 99)>99</button>
        <button onClick=this.handleAdd>Add</button>
        <button onClick=this.handleRemove>Remove</button>
        this.state.index
      </div>
    );
  


class Item extends React.Component

  render() 
    return (<li ref= element => this.listItem = element >
      this.props.children
    </li>);
  

演示:https://codepen.io/anon/pen/XpqJVe

【讨论】:

【参考方案4】:

以@Michelle Tilley 的回答为基础,如果用户的选择发生变化,我有时想滚动,所以我在componentDidUpdate 上触发滚动。我还做了一些数学计算来确定滚动的距离以及是否需要滚动,这对我来说如下所示:

  componentDidUpdate() 
    let panel, node;
    if (this.refs.selectedSection && this.refs.selectedItem) 
      // This is the container you want to scroll.          
      panel = this.refs.listPanel;
      // This is the element you want to make visible w/i the container
      // Note: You can nest refs here if you want an item w/i the selected item          
      node = ReactDOM.findDOMNode(this.refs.selectedItem);
    

    if (panel && node &&
      (node.offsetTop > panel.scrollTop + panel.offsetHeight || node.offsetTop < panel.scrollTop)) 
      panel.scrollTop = node.offsetTop - panel.offsetTop;
    
  

【讨论】:

【参考方案5】:

以防万一有人在这里绊倒,我是这样做的

  componentDidMount()
    const node = this.refs.trackerRef;
    node && node.scrollIntoView(block: "end", behavior: 'smooth')
  
  componentDidUpdate() 
    const node = this.refs.trackerRef;
    node && node.scrollIntoView(block: "end", behavior: 'smooth')
  

  render() 
    return (

      <div>
        messages.map((msg, index) => 
          return (
            <Message key=index msgObj=msg
              /*<p>some test text</p>*/
            </Message>
          )
        )

        <div style=height: '30px' id='#tracker' ref="trackerRef"></div>
      </div>
    )
  

scrollIntoView 是原生 DOM 功能 link

它会一直显示tracker div

【讨论】:

【参考方案6】:

带有反应钩子:

    导入
import ReactDOM from 'react-dom';
import React, useRef from 'react';
    制作新的钩子:
const divRef = useRef<htmlDivElement>(null);
    添加新的分区
<div ref=divRef/>
    滚动功能:
const scrollToDivRef  = () => 
    let node = ReactDOM.findDOMNode(divRef.current) as Element;
    node.scrollIntoView(block: 'start', behavior: 'smooth');

【讨论】:

【参考方案7】:

在您的 keyup/down 处理程序中,您只需设置要滚动的 div 的 scrollTop 属性,使其向下(或向上)滚动。

例如:

JSX:

&lt;div ref="foo"&gt;content&lt;/div&gt;

keyup/down 处理程序:

this.refs.foo.getDOMNode().scrollTop += 10

如果您执行与上述类似的操作,您的 div 将向下滚动 10 个像素(假设 div 在 css 中设置为溢出 autoscroll,并且您的内容当然会溢出)。

您需要对此进行扩展以找到您想要将 div 向下滚动到的滚动 div 内元素的偏移量,然后修改 scrollTop 以滚动到足够远以根据元素的高度显示该元素.

在此处查看 MDN 对 scrollTop 和 offsetTop 的定义:

https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollTop

https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetTop

【讨论】:

【参考方案8】:

我只是为其他在 React 中搜索 Scroll-To 功能的人添加了一些信息。我已经绑定了几个库来为我的应用程序执行 Scroll-To,但在我找到 react-scrollchor 之前,没有一个库在我的用例中起作用,所以我想我会把它传递下去。 https://github.com/bySabi/react-scrollchor

【讨论】:

同样,我使用npmjs.com/package/scroll-into-view 效果很好。实现起来非常简单。在应该可见的组件内部:componentDidMount() scrollIntoView(React.findDOMNode(this)) @jsaven 我想你的意思是ReactDOM.findDOMNode(this): reactjs.org/docs/react-dom.html#finddomnode【参考方案9】:

我有一个 NavLink,当我点击它时,它会像命名锚点一样滚动到该元素。我是这样实现的。

 <NavLink onClick=() => this.scrollToHref('plans')>Our Plans</NavLink>
  scrollToHref = (element) =>
    let node;
    if(element === 'how')
      node = ReactDom.findDOMNode(this.refs.how);
      console.log(this.refs)
    else  if(element === 'plans')
      node = ReactDom.findDOMNode(this.refs.plans);
    else  if(element === 'about')
      node = ReactDom.findDOMNode(this.refs.about);
    

    node.scrollIntoView(block: 'start', behavior: 'smooth');

  

然后我将想要滚动的组件提供给这样的引用

<Investments ref="plans"/>

【讨论】:

以上是关于如何滚动 div 以在 ReactJS 中可见?的主要内容,如果未能解决你的问题,请参考以下文章

ReactJS - 获取两个 div 两个互相跟随滚动

在 WPF ListView 中,如何防止自动滚动?

如何单击以在多个实例中相互独立地滑动打开的 div?

如何从可滚动容器中仅选择可见元素?

滚动时始终可见 div

在 ReactJS 中滚动嵌套组件