简体   繁体   English

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

[英]How can I scroll a div to be visible in ReactJS?

I have a popup list which is a div that contains a vertical list of child div s.我有一个弹出列表这是一个div包含子的垂直列表div秒。 I have added up/down keyboard navigation to change which child is currently highlighted.我添加了向上/向下键盘导航以更改当前突出显示的子项。

Right now, if I press the down key enough times, the highlighted item is no longer visible.现在,如果我按下向下键足够多的次数,突出显示的项目将不再可见。 The same thing also occurs with the up key if the view is scrolled.如果滚动视图,向上键也会发生同样的事情。

What is the right way in React to automatically scroll a child div into view? React 中自动将子div滚动到视图中的正确方法是什么?

I assume that you have some sort of List component and some sort of Item component.我假设您有某种List组件和某种Item组件。 The way I did it in one project was to let the item know if it was active or not;在一个项目中的做法是让项目知道它是否处于活动状态; the item would ask the list to scroll it into view if necessary.如有必要,该项目会要求列表将其滚动到视图中。 Consider the following pseudocode:考虑以下伪代码:

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

A better solution is probably to make the list responsible for scrolling the item into view (without the item being aware that it's even in a list).更好的解决方案可能是让列表负责将项目滚动到视图中(项目不知道它甚至在列表中)。 To do so, you could add a ref attribute to a certain item and find it with that:为此,您可以向某个项目添加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.
  }
}

If you don't want to do the math to determine if the item is visible inside the list node, you could use the DOM method scrollIntoView() or the Webkit-specific scrollIntoViewIfNeeded , which has a polyfill available so you can use it in non-Webkit browsers.如果您不想通过数学运算来确定该项目是否在列表节点内可见,您可以使用DOM 方法scrollIntoView()或特定于 Webkit 的scrollIntoViewIfNeeded ,它具有可用的scrollIntoViewIfNeeded ,因此您可以在非-Webkit 浏览器。

For React 16, the correct answer is different from earlier answers:对于 React 16,正确答案与之前的答案不同:

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

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

Then to scroll, just add (after constructor):然后滚动,只需添加(在构造函数之后):

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

Note: You must use '.current,' and you can send options to scrollIntoView:注意:您必须使用“.current”,并且您可以向 scrollIntoView 发送选项:

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

(Found at http://www.albertgao.xyz/2018/06/07/scroll-a-not-in-view-component-into-the-view-using-react/ ) (见http://www.albertgao.xyz/2018/06/07/scroll-a-not-in-view-component-into-the-view-using-react/

Reading the spec, it was a little hard to suss out the meaning of block and inline, but after playing with it, I found that for a vertical scrolling list, block: 'end' made sure the element was visible without artificially scrolling the top of my content off the viewport.阅读规范,有点难以理解 block 和 inline 的含义,但是在玩弄它之后,我发现对于垂直滚动列表,block: 'end' 确保元素可见,而无需人为地滚动顶部我的内容离开视口。 With 'center', an element near the bottom would be slid up too far and empty space appeared below it.使用“center”,靠近底部的元素会向上滑动太多,其下方会出现空白空间。 But my container is a flex parent with justify: 'stretch' so that may affect the behavior.但是我的容器是一个带有 justify: 'stretch' 的 flex 父级,因此可能会影响行为。 I didn't dig too much further.我没有进一步挖掘。 Elements with overflow hidden will impact how the scrollIntoView acts, so you'll probably have to experiment on your own.隐藏溢出的元素会影响 scrollIntoView 的行为方式,因此您可能必须自己进行试验。

My application has a parent that must be in view and if a child is selected, it then also scrolls into view.我的应用程序有一个必须在视图中的父级,如果选择了一个子级,它也会滚动到视图中。 This worked well since parent DidMount happens before child's DidMount, so it scrolls to the parent, then when the active child is rendered, scrolls further to bring that one in view.这很有效,因为父类 DidMount 发生在子类的 DidMount 之前,所以它滚动到父类,然后在呈现活动子类时,进一步滚动以显示该子类。

Another example which uses function in ref rather than string另一个在 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>);
  }
}

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

To build on @Michelle Tilley's answer, I sometimes want to scroll if the user's selection changes, so I trigger the scroll on componentDidUpdate .为了建立在@Michelle Tilley 的回答之上,我有时想在用户的选择发生变化时滚动,所以我在componentDidUpdate上触发滚动。 I also did some math to figure out how far to scroll and whether scrolling was needed, which for me looks like the following:我还做了一些数学计算来弄清楚滚动的距离以及是否需要滚动,对我来说看起来像下面这样:

 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; } }

Just in case someone stumbles here, I did it this way以防万一有人在这里绊倒,我是这样做的

  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 is native DOM feature link scrollIntoView是原生 DOM 功能链接

It will always shows tracker div它将始终显示tracker div

With reacts Hooks:与反应挂钩:

  1. Import进口
import ReactDOM from 'react-dom';
import React, {useRef} from 'react';
  1. Make new hook:制作新钩子:
const divRef = useRef<HTMLDivElement>(null);
  1. Add new Div添加新的 Div
<div ref={divRef}/>
  1. Scroll function:滚动功能:
const scrollToDivRef  = () => {
    let node = ReactDOM.findDOMNode(divRef.current) as Element;
    node.scrollIntoView({block: 'start', behavior: 'smooth'});
}

In you keyup/down handler you just need to set the scrollTop property of the div you want to scroll to make it scroll down (or up).在您的 keyup/down 处理程序中,您只需要设置要滚动的 div 的scrollTop属性以使其向下(或向上)滚动。

For example:例如:

JSX: JSX:

<div ref="foo">{content}</div>

keyup/down handler:键上/下处理程序:

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

If you do something similar to above, your div will scroll down 10 pixels (assuming the div is set to overflow auto or scroll in css, and your content is overflowing of course).如果您执行与上述类似的操作,您的 div 将向下滚动 10 个像素(假设 div 设置为auto溢出或在 css 中scroll ,并且您的内容当然是溢出的)。

You will need to expand on this to find the offset of the element inside your scrolling div that you want to scroll the div down to, and then modify the scrollTop to scroll far enough to show the element based on it's height.您将需要对此进行扩展以找到滚动 div 中要向下滚动 div 的元素的偏移量,然后修改scrollTop以滚动到足够远的距离以根据元素的高度显示该元素。

Have a look at MDN's definitions of scrollTop, and offsetTop here:在这里查看 MDN 对 scrollTop 和 offsetTop 的定义:

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

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

I'm just adding another bit of info for others searching for a Scroll-To capability in React.我只是为其他在 React 中搜索 Scroll-To 功能的人添加了一些信息。 I had tied several libraries for doing Scroll-To for my app, and none worked from my use case until I found react-scrollchor, so I thought I'd pass it on.我已经绑定了几个库来为我的应用程序执行 Scroll-To,在我找到 react-scrollchor 之前,没有一个库在我的用例中起作用,所以我想我会传递它。 https://github.com/bySabi/react-scrollchor https://github.com/bySabi/react-scrollchor

I had a NavLink that I wanted to when clicked will scroll to that element like named anchor does.我有一个 NavLink,我想在点击它时会像命名锚一样滚动到那个元素。 I implemented it this way.我是这样实现的。

 <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'});

  }

I then give the component I wanted to scroll to a ref like this然后我给我想要滚动到这样的参考的组件

<Investments ref="plans"/>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM