web-dev-qa-db-fra.com

Comment faire défiler une div pour qu'elle soit visible dans ReactJS?

J'ai une liste contextuelle qui est une div qui contient une liste verticale d'enfants divs. J'ai ajouté la navigation haut/bas du clavier pour changer l'enfant actuellement en surbrillance.

À l'heure actuelle, si j'appuie suffisamment sur la touche bas, l'élément en surbrillance n'est plus visible. La même chose se produit également avec la touche haut si la vue défile.

Quelle est la bonne manière dans React de faire défiler automatiquement un enfant div dans sa vue?

45
MindJuice

Je suppose que vous avez une sorte de composant List et une sorte de composant Item. La façon dont je l’ai fait dans un projet a été de laisser l’élément savoir s’il était actif ou non; l'élément demanderait à la liste de la faire défiler si nécessaire. Considérons le pseudocode suivant:

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

Une meilleure solution est probablement de rendre la liste responsable du défilement de l'élément dans la vue (sans que l'élément sache qu'il est même dans une liste). Pour ce faire, vous pouvez ajouter un attribut ref à un élément donné et le trouver avec:

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

Si vous ne voulez pas faire le calcul pour déterminer si l'élément est visible dans le nœud de la liste, vous pouvez utiliser la méthode DOM scrollIntoView() ou la variable scrollIntoViewIfNeeded propre au Webkit, qui dispose d'un remplissage multiple afin vous pouvez l'utiliser dans les navigateurs non-Webkit.

73
Michelle Tilley

Un autre exemple qui utilise function dans ref plutôt que string

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

Démo: https://codepen.io/anon/pen/XpqJVe

5
Steven

Pour donner suite à la réponse de @Michelle Tilley, je souhaite parfois faire défiler si la sélection de l'utilisateur change. Je déclenche donc le défilement sur componentDidUpdate. J'ai également fait quelques calculs pour déterminer le chemin à parcourir et déterminer si le défilement était nécessaire. Pour moi, cela ressemble à ce qui suit:

  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

Pour React 16, la réponse correcte est différente des réponses précédentes:

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

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

Ensuite, pour faire défiler, ajoutez simplement (après constructeur):

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

Remarque: vous devez utiliser '.current' et vous pouvez envoyer des options à scrollIntoView:

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

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

En lisant la spécification, il était un peu difficile d'expliquer la signification de block et inline, mais après avoir joué avec cela, j'ai trouvé que pour une liste de défilement verticale, block: 'end' s'assurait que l'élément était visible sans défiler artificiellement en haut de mon contenu hors de la fenêtre d'affichage. Avec 'center', un élément situé près du bas serait glissé trop loin et un espace vide apparaît en dessous. Mais mon conteneur est un parent flex avec justification: «stretch» ​​afin d’affecter le comportement. Je n'ai pas trop creusé. Les éléments dont le débordement est masqué auront une incidence sur le comportement de scrollIntoView. Vous devrez donc probablement faire des essais par vous-même.

Mon application a un parent qui doit être en vue et si un enfant est sélectionné, il défile également en vue. Cela a bien fonctionné puisque DidMount parent se produit avant DidMount de l'enfant. Il défile donc jusqu'au parent, puis, lorsque le fils actif est rendu, continue de défiler pour afficher celui-ci.

2
eon

Juste au cas où quelqu'un trébuche ici, je l'ai fait de cette façon 

  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 est une fonction DOM native link

Il affichera toujours tracker div 

1
Aishwat Singh

J'avais un lien NavLink que je voulais quand on clique dessus pour faire défiler cet élément comme le fait ancre nommée Je l'ai implémenté de cette façon.

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

  }

Je donne ensuite le composant que je voulais faire défiler à un ref comme celui-ci

<Investments ref="plans"/>
0
Everistus Olumese

J'ajoute simplement un autre élément d'information pour les autres personnes à la recherche d'une fonctionnalité Scroll-To dans React. J'avais lié plusieurs bibliothèques pour faire défiler vers mon application, et aucune ne fonctionnait à partir de mon cas d'utilisation jusqu'à ce que je découvre react-scrollchor, alors j'ai pensé le transmettre. https://github.com/bySabi/react-scrollchor

0
MartinDuo