wake-up-neo.net

Wie kann ich ein div scrollen, um in ReactJS sichtbar zu sein?

Ich habe eine Popup-Liste, eine div, die eine vertikale Liste der untergeordneten divs enthält. Ich habe die Tastaturnavigation nach oben/unten hinzugefügt, um zu ändern, welches Kind gerade markiert ist.

Wenn ich jetzt mehrmals die Abwärts-Taste drücke, ist das hervorgehobene Element nicht mehr sichtbar. Dasselbe gilt auch für die Aufwärts-Taste, wenn die Ansicht gescrollt wird.

Was ist der richtige Weg in React, um automatisch ein Kind div in die Ansicht zu rollen?

45
MindJuice

Ich gehe davon aus, dass Sie eine List-Komponente und eine Item-Komponente haben. So wie ich es tat in einem Projekt war das Element wissen zu lassen, ob es aktiv war oder nicht; Das Element würde die Liste dazu auffordern, sie ggf. in die Ansicht zu rollen. Betrachten Sie den folgenden 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));
    }
  }
}

Eine bessere Lösung besteht wahrscheinlich darin, die Liste dafür verantwortlich zu machen, dass das Element in die Ansicht verschoben wird (ohne dass das Element sich dessen bewusst ist, dass es sich sogar in einer Liste befindet). Dazu können Sie einem bestimmten Element ein ref-Attribut hinzufügen und es damit suchen:

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

Wenn Sie nicht mathematisch ermitteln möchten, ob das Element im Listenknoten sichtbar ist, können Sie die DOM-Methode scrollIntoView() oder die Webkit-spezifische scrollIntoViewIfNeeded verwenden, die eine Polyfill verfügbar hat , damit Sie es in Nicht-Webkit-Browsern verwenden können.

73
Michelle Tilley

Ein weiteres Beispiel, bei dem die Funktion in ref anstelle von string verwendet wird

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

5
Steven

Um auf die Antwort von @Michelle Tilley aufzubauen, möchte ich manchmal einen Bildlauf durchführen, wenn sich die Auswahl des Benutzers ändert. Ich stoße den Bildlauf auf componentDidUpdate aus. Ich habe auch ein paar Berechnungen vorgenommen, um herauszufinden, wie weit ich scrollen muss und ob ein Scrollen erforderlich ist, was für mich wie folgt aussieht:

  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
Yonatan Kogan

Bei Reaktion 16 unterscheidet sich die richtige Antwort von früheren Antworten:

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

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

Dann zum Scrollen einfach hinzufügen (nach Konstruktor):

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

Hinweis: Sie müssen '.current' verwenden, und Sie können Optionen an scrollIntoView senden:

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

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

Beim Lesen der Spezifikation war es ein wenig schwierig, die Bedeutung von Block und Inline herauszufinden, aber nachdem ich damit gespielt hatte, stellte ich fest, dass bei einer vertikalen Bildlaufliste mit block: 'end' das Element sichtbar war, ohne dass die Oberseite künstlich gescrollt wurde von meinem Inhalt aus dem Darstellungsbereich. Mit 'center' würde ein Element in der Nähe des Bodens zu weit nach oben geschoben und leerer Raum erscheint darunter. Mein Container ist jedoch ein flexibler Elternteil mit Rechtfertigung: "Strecken", so dass das Verhalten beeinflusst werden kann. Ich habe nicht viel weiter gegraben. Elemente mit ausgeblendetem Überlauf wirken sich auf das Verhalten von scrollIntoView aus, sodass Sie wahrscheinlich selbst experimentieren müssen.

Meine Anwendung verfügt über ein übergeordnetes Element, das angezeigt werden muss. Wenn ein untergeordnetes Element ausgewählt ist, wird auch ein Bildlauf ausgeführt. Dies hat gut funktioniert, da das übergeordnete DidMount vor dem DidMount des Kindes geschieht. Es wird dann zum übergeordneten Element gescrollt. Wenn das aktive untergeordnete Element gerendert wird, wird das Bild weiter angezeigt, um dieses Element anzuzeigen.

2
eon

Nur für den Fall, dass jemand hier stolpert, habe ich es so gemacht 

  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 ist native DOM-Funktion link

Es wird immer tracker div angezeigt 

1
Aishwat Singh

In Ihrem Keyup/Down-Handler müssen Sie nur die scrollTop -Eigenschaft des zu scrollenden Div festlegen, damit es nach unten (oder oben) scrollt.

Zum Beispiel:

JSX:

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

keyup/Down-Handler:

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

Wenn Sie etwas Ähnliches wie oben tun, wird Ihr Div 10 Pixel nach unten scrollen (vorausgesetzt, das Div ist in CSS auf Überlauf auto oder scroll eingestellt, und Ihr Inhalt läuft natürlich über).

Sie müssen dies erweitern, um den Versatz des Elements in Ihrem Bildlauffeld zu ermitteln, zu dem Sie den Bildlauf durchführen möchten, und dann das scrollTop so zu ändern, dass der Bildlauf so weit ausgeführt wird, dass das Element basierend auf seiner Höhe angezeigt wird .

Schauen Sie sich die MDN-Definitionen von scrollTop und offsetTop hier an:

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

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

1
Mike Driver

Ich hatte einen NavLink, den ich wollte, wenn er angeklickt wird, wird zu diesem Element gescrollt wie mit dem Namen "Anker". Ich habe es so umgesetzt.

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

  }

Ich gebe dann die Komponente, die ich zu einem Ref wie diesem scrollen wollte

<Investments ref="plans"/>
0

Ich füge einfach weitere Informationen für andere hinzu, die in React nach einer Scroll-To-Funktion suchen. Ich hatte mehrere Bibliotheken für die Verwendung von Scroll-To für meine App gebunden und keine funktionierte von meinem Anwendungsfall, bis ich einen Reakti- ons-Scrollchor gefunden hatte. https://github.com/bySabi/react-scrollchor

0
MartinDuo