it-swarm.com.ru

Как я могу прокрутить div, чтобы быть видимым в ReactJS?

У меня есть всплывающий список, который является div, который содержит вертикальный список дочерних divs. Я добавил навигацию вверх/вниз по клавиатуре, чтобы изменить, какой дочерний элемент выделен в данный момент.

Прямо сейчас, если я нажимаю клавишу «вниз» достаточно много раз, выделенный элемент больше не виден. То же самое происходит и с клавишей вверх, если вид прокручивается.

Как правильно использовать React для автоматической прокрутки дочернего элемента div в поле зрения?

45
MindJuice

Я предполагаю, что у вас есть какой-то компонент List и какой-то компонент Item. То, как я это сделал в одном проекте должно было сообщить элементу, активен он или нет; элемент попросит список прокрутить его, если необходимо. Рассмотрим следующий псевдокод:

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 метод scrollIntoView() или специфичный для Webkit scrollIntoViewIfNeeded, который имеет доступ к полифиллу so Вы можете использовать его в браузерах без Webkit.

73
Michelle Tilley

Другой пример, который использует функцию в ссылке, а не в строке

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

5
Steven

Чтобы опираться на ответ @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
Yonatan Kogan

Для Реакта 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/ )

Читая спецификацию, было немного трудно разобраться в значении блока и встраивания, но поиграв с ним, я обнаружил, что для вертикальной прокрутки списка block: 'end' убедился, что элемент виден без искусственной прокрутки верхней части. моего контента из области просмотра. При «центре» элемент около дна будет скользить слишком далеко, и под ним появится пустое пространство. Но мой контейнер является гибким родителем с justify: 'stretch', так что это может повлиять на поведение. Я не копал слишком много дальше. Элементы со скрытым переполнением влияют на работу scrollIntoView, поэтому вам, вероятно, придется экспериментировать самостоятельно.

Мое приложение имеет родителя, который должен быть в поле зрения, и если выбран дочерний элемент, он также прокручивается в поле зрения. Это работало хорошо, так как родительский DidMount происходит раньше, чем дочерний DidMount, поэтому он прокручивается до родителя, а затем, когда отображается активный дочерний объект, прокручивается дальше, чтобы отобразить его.

2
eon

На всякий случай, если кто-то споткнется здесь, я сделал это так 

  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 ссылка

Это всегда показывает tracker div 

1
Aishwat Singh

В вашем обработчике keyup/down вам просто нужно установить свойство scrollTop для div, который вы хотите прокрутить, чтобы он прокручивался вниз (или вверх).

Например:

JSX: 

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

обработчик нажатия клавиш:

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

Если вы сделаете что-то похожее на вышеописанное, ваш div будет прокручиваться на 10 пикселей вниз (при условии, что div настроен на переполнение auto или scroll в css, и ваш контент, конечно, переполнен).

Вам нужно будет расширить это, чтобы найти смещение элемента внутри вашего прокручиваемого 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

1
Mike Driver

У меня был 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"/>
0
Everistus Olumese

Я просто добавляю еще немного информации для тех, кто ищет возможность прокрутки в React. Я связал несколько библиотек для выполнения Scroll-To для моего приложения, и ни одна не работала от моего варианта использования до тех пор, пока я не нашел response-scrollchor, поэтому я решил передать его. https://github.com/bySabi/react-scrollchor

0
MartinDuo