web-dev-qa-db-ja.com

ReactJSで表示されるようにdivをスクロールするにはどうすればよいですか?

divsの垂直リストを含むdivであるポップアップリストがあります。上下のキーボードナビゲーションを追加して、現在強調表示されている子を変更しました。

今、下キーを十分に押すと、強調表示されたアイテムは表示されなくなります。ビューがスクロールされた場合、上キーでも同じことが発生します。

Reactで子divを自動的にスクロールして表示する正しい方法は何ですか?

50
MindJuice

ある種のListコンポーネントとある種のItemコンポーネントがあると仮定します。私がそれをした方法 1つのプロジェクトで は、アイテムがアクティブかどうかを知らせることでした。アイテムは、必要に応じてリストにスクロールして表示するように要求します。次の擬似コードを検討してください。

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を使用できます。 、これは ポリフィルが使用可能 であるため、非Webkitブラウザーで使用できます。

76
Michelle Tilley

文字列ではなく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>);
  }
}

デモ: https://codepen.io/anon/pen/XpqJVe

6
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;
    }
  }
6
Yonatan Kogan

React 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/ で見つかりました)

仕様を読んで、ブロックとインラインの意味を理解するのは少し困難でしたが、それで遊んだ後、垂直スクロールリストの場合、ブロック: 'end'はトップを人為的にスクロールせずに要素が表示されることを確認しましたビューポート以外のコンテンツの「中央」では、下部近くの要素が大きくスライドし、その下に空のスペースが表示されます。しかし、私のコンテナーは、justify: 'stretch'を持つflex親であり、動作に影響を与える可能性があります。私はこれ以上掘りませんでした。オーバーフローが非表示の要素はscrollIntoViewの動作に影響を与えるため、おそらく自分で実験する必要があります。

私のアプリケーションには、親が表示されている必要があり、子が選択されている場合は、スクロールして表示されます。親のDidMountは子のDidMountの前に発生するため、これはうまく機能しました。したがって、親までスクロールし、アクティブな子がレンダリングされると、さらにスクロールしてその子を表示します。

5
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機能です link

常にtracker divが表示されます

1
Aishwat Singh

キーアップ/ダウンハンドラーでは、スクロールするdivのscrollTopプロパティを設定するだけで、下(または上)にスクロールできます。

例えば:

JSX:

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

キーアップ/ダウンハンドラー:

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

上記と同様の操作を行うと、divは10ピクセル下にスクロールします(divがcssでautoまたはscrollにオーバーフローするように設定されており、当然コンテンツがオーバーフローしています)。

これを展開して、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

ReactでScroll-To機能を検索している他のユーザー向けに、もう少し情報を追加しています。私はアプリのScroll-Toを行うためにいくつかのライブラリを結び付けていましたが、react-scrollchorが見つかるまでユースケースから機能しなかったため、それを渡すと思いました。 https://github.com/bySabi/react-scrollchor

0
MartinDuo