web-dev-qa-db-fra.com

Faites défiler vers le bas d'un div débordant dans React

J'ai un ul avec un max-height et overflow-y: auto.

Lorsque l'utilisateur entre suffisamment d'éléments li, le ul commence à défiler, mais je veux que le dernier li avec le form dedans soit toujours présent et visible par l'utilisateur.

J'ai essayé d'implémenter une fonction scrollToBottom qui ressemble à ceci:

scrollToBottom() {
    this.formLi.scrollIntoView({ behavior: 'smooth' });
}

Mais cela fait juste sauter le ul en haut de l'écran et afficher le li avec le formulaire comme seule chose visible.

Existe-t-il une meilleure façon d'accomplir cela? Les réponses que je trouve ont tendance à être un peu plus anciennes et à utiliser ReactDOM. Merci!

CSS:

.Prompt-box .options-holder {
    list-style: none;
    padding: 0;
    width: 95%;
    margin: 12px auto;
    max-height: 600px;
    overflow-y: auto;
}

HTML:

<ul className='options-holder'>
    {
        this.state.items.map((item, index) => (
            <li key={item.id} className={`option ${index === 0 ? 'first' : ''}`}>
                <div className='circle' onClick={this.removeItem} />

                <p className='item-text'>{ item.text }</p>
            </li>
        ))
    }

    <li key={0} className={`option form ${this.state.items.length === 0 ? 'only' : ''}`} ref={el => (this.formLi = el)}>
        <div className='circle form' />

        <form className='new-item-form' onSubmit={this.handleSubmit}>
            <input 
                autoFocus 
                className='new-item-input' 
                placeholder='Type something and press return...' 
                onChange={this.handleChange} 
                value={this.state.text}
                ref={(input) => (this.formInput = input)} />
        </form>
    </li> 
</ul>
28
Zack Shapiro

Utilisez la bibliothèque react-scroll . Cependant, vous devrez définir explicitement l'ID de votre ul.

import { animateScroll } from "react-scroll";

scrollToBottom() {
    animateScroll.scrollToBottom({
      containerId: "options-holder"
    });
}

Vous pouvez appeler scrollToBottom lors du rappel setState.

Par exemple

this.setState({ items: newItems}, this.scrollToBottom);

Ou en utilisant setTimeout.

Ou vous pouvez même définir Element à partir de react-scroll et faites défiler jusqu'à un certain li.

7
emil

On dirait que vous construisez une interface de type chat. Vous pouvez essayer d'envelopper le <ul> et le div.circle-form dans une div séparée comme ci-dessous:

ul{
  border:1px solid red;
  height:13em;
  overflow-y:auto;
  position:relative;
}
ul li{
  border-bottom:1px solid #ddd;
  list-style:none;
  margin-left:0;
  padding:6px;
}

.wrapper{
  background:#eee;
  height:15em;
  position:relative;
}
.circle-form{
  background:#ddd;
  height:2em;
  padding:3px;
  position:absolute;
  bottom:0;
  left:0;
  right:0;
  z-index:2;
}
.circle-form input[type=text]{
  padding:8px;
  width:50%;
}
<div class="wrapper">
   <ul id="list">
       <li>item</li>
       <li>item</li>
       <li>item</li>
       <li>item</li>
       <li>item</li>
       <li>item</li>
       <li>item</li>
       <li>item</li>
    </ul>
    <div class="circle-form">
        <input type="text" name="type_here" placeholder="Enter something..." autofocus/>
    </div>
</div>

[~ # ~] modifier [~ # ~]

Et pour faire défiler vers le bas de la liste avec javascript

var list = document.getElementById("list");
list.scrollTop = list.offsetHeight;
4
William

J'ai eu un problème similaire lors du rendu d'un tableau de composants provenant d'une importation utilisateur. J'ai fini par utiliser la fonction componentDidUpdate () pour faire fonctionner la mienne.

componentDidUpdate() {
        // I was not using an li but may work to keep your div scrolled to the bottom as li's are getting pushed to the div
        const objDiv = document.getElementById('div');
        objDiv.scrollTop = objDiv.scrollHeight;
      }
2
Chris Paro

J'ai un script que j'ai utilisé dans l'un de mes projets pour faire défiler le haut en douceur, j'ai fait un petit refactor pour faire défiler la hauteur de votre div (défiler en bas) j'espère que ça aide.

scroll.js

function currentYPosition() {
  if (self.pageYOffset) return self.pageYOffset;
  if (document.documentElement && document.documentElement.scrollHeight) return document.documentElement.scrollHeight;
  if (document.body.scrollHeight) return document.body.scrollHeight;

  return 0;
}

function elmYPosition(eID) {
  let Elm = document.getElementById(eID);
  let y = Elm.offsetHeight;
  let node = Elm;
  while (node.offsetParent && node.offsetParent != document.body) {
    node = node.offsetParent;
    y += node.offsetHeight;
  }
  return y;
}

export default function smoothScroll(eID, string) {
  let startY = currentYPosition();
  let stopY = elmYPosition(eID);
  let distance = stopY > startY ? stopY - startY : startY - stopY;
  let speed = Math.round(distance / 10);
  let speedTimeout = 250;
  if (speed >= 100) speed = 100;
  if (string) speed = 1;
  let step = Math.round(distance / 25);
  let leapY = stopY > startY ? startY + step : startY - step;
  let timer = 0;
  if (stopY > startY) {
    for (let i = startY; i < stopY; i += step) {
      setTimeout('window.scrollTo(0, ' + leapY + ')', timer * speed);
      leapY += step;
      if (leapY > stopY) leapY = stopY;
      timer++;
    }
    return;
  }
  for (let i = startY; i > stopY; i -= step) {
    setTimeout('window.scrollTo(0, ' + (leapY) + ')', timer * speed);
    leapY -= step;
    if (leapY < stopY){
      leapY = stopY;
    } 
    timer++;
  }
}

Vous devez importer ceci dans votre composant, il y a 2 paramètres (l'ID de votre élément, dans ce cas, vous pouvez utiliser ref. Le second est une chaîne que j'ai utilisée pour traiter la vitesse de défilement.

import scroll from './your-path/scroll.js';
.
.
.
<ul className='options-holder'>
{
    this.state.items.map((item, index) => (
        <li key={item.id} className={`option ${index === 0 ? 'first' : ''}`} ref={el => (this.formLi = el)}>
            <div className='circle' onClick={this.removeItem} />

            <p className='item-text'>{ item.text }</p>
        </li>
    ))
}

<li key={0} className={`option form ${this.state.items.length === 0 ? 'only' : ''}`} ref={el => (this.formLi = el)}>
    <div className='circle form' />

    <form className='new-item-form' onSubmit={this.handleSubmit}>
        <input 
            autoFocus 
            className='new-item-input' 
            placeholder='Type something and press return...' 
            onChange={this.handleChange} 
            value={this.state.text}
            ref={(input) => (this.formInput = input)} />
    </form>
</li> 

Idk comment vous mappez ce LI à l'intérieur de votre rendu, mais vous devez faire une vérification et s'il y a la propriété Overflow, vous devez exécuter le défilement.

Il y a une réponse raisonnable pour que votre composant passe au premier élément, vous frappez la référence pour le PREMIER élément, pas le dernier.

Solution possible:

scroll(this.state.items[this.state.items.length - 1]);

Mise à jour 1: Gist of the original scroll.js, scrolling to the top

1
Yuri Ramos

Utilisez simplement position:sticky s.t. le dernier élément de la liste est toujours affiché.

li:last-child {
  position:sticky;
  bottom:0;
}

Démo

Caniuse

1
user943702