web-dev-qa-db-ja.com

ReactでdivのoffsetTop位置を取得します

Reactでリストビューを実装しようとしています。私が達成しようとしているのは、リストヘッダー情報を保存し、コンポーネントを登録し、スクロールイベントを登録することです。ユーザーがウィンドウをスクロールするたびに、保存されているdivを取り出してoffsetTopデータを再計算します。

問題は、コンソールが初期値を出力するだけだということです(値は固定されており、変更されません)offsetTopデータはonscroll関数で変更されません。

_instancesオブジェクトから最新のoffsetTopを取得する方法を提案する人はいますか?

import React, { Component } from 'react';
import ListHeader from './lib/ListHeader';
import ListItems from './lib/ListItems';

const styles = {
  'height': '400px',
  'overflowY': 'auto',
  'outline': '1px dashed red',
  'width': '40%'
};

class HeaderPosInfo {
  constructor(headerObj, originalPosition, originalHeight) {
    this.headerObj = headerObj;
    this.originalPosition = originalPosition;
    this.originalHeight = originalHeight; 
  }
}

export default class ReactListView extends Component {
  static defaultProps = {
    events: ['scroll', 'mousewheel', 'DOMMouseScroll', 'MozMousePixelScroll', 'resize', 'touchmove', 'touchend'],
    _instances:[],
    _positionMap: new Set(),
    _topPos:'',
    _topWrapper:''
  }

  static propTypes = {
    data: React.PropTypes.array.isRequired,
    headerAttName: React.PropTypes.string.isRequired,
    itemsAttName: React.PropTypes.string.isRequired,
    events: React.PropTypes.array,
    _instances: React.PropTypes.array,
    _positionMap: React.PropTypes.object,
    _topPos: React.PropTypes.string,
    _topWrapper: React.PropTypes.object
  };

  state = {
    events: this.props.events,
    _instances: this.props._instances,
    _positionMap: this.props._positionMap,
    _topPos: this.props._topPos
  }

  componentDidMount() {
    this.initStickyHeaders();
  }

  componentWillUnmount() {

  }

  componentDidUpdate() {

  }

  refsToArray(ctx, prefix){
    let results = [];
    for (let i=0;;i++){
      let ref = ctx.refs[prefix + '-' + String(i)];
      if (ref) results.Push(ref);
      else return results;
    }
  }

  initHeaderPositions() {
    // Retrieve all instance of headers and store position info
    this.props._instances.forEach((k)=>{
      this.props._positionMap.add(new HeaderPosInfo(
          k, 
          k.refs.header.getDOMNode().offsetTop,
          k.refs.header.getDOMNode().offsetHeight
        ));
    });
    let it = this.props._positionMap.values();
    let first = it.next();
    this.props._topPos = first.value.originalPosition;
    this.props._topWrapper = first.value.headerObj;
  }

  initStickyHeaders () {
    this.props._instances = this.refsToArray(this, 'ListHeader');
    this.initHeaderPositions();

    // Register events listeners with the listview div
    this.props.events.forEach(type => {
      if (window.addEventListener) {
        React.findDOMNode(this.refs.listview).addEventListener(type, this.onScroll.bind(this), false);
      } else {
        React.findDOMNode(this.refs.listview).attachEvent('on' + type, this.onScroll.bind(this), false);
      }
    });
  }

  onScroll() {

    // update current header positions and apply fixed positions to the top one
    console.log(1);
    let offsetTop  = React.findDOMNode(this.props._instances[0].refs.header).offsetTop;

  }

  render() {
    const { data, headerAttName, itemsAttName } = this.props;
    let _refi = 0;
    let makeRef = () => {
      return 'ListHeader-' + (_refi++);
    };

    return (
      <div ref="listview" style={styles}>
      {
        Object.keys(data).map(k => {
        const header = data[k][headerAttName];
        const items  = data[k][itemsAttName];
          return (
            <ul key={k}>     
              <ListHeader ref={makeRef()} header={header} />
              <ListItems  items={items} />
            </ul>
          );
        })
      }
      </div>
    );
  }
}

ソースコード全体はGithubにあります。ここからクローンを作成してコンパイルできます。

Github

49
JavaScripter

Element.getBoundingClientRect() メソッドを使用して、要素の上部オフセットを取得することをお勧めします。このメソッドは、ビューポート内の要素の完全なオフセット値(左、上、右、下、幅、高さ)を提供します。

John Resigの投稿 をチェックして、この方法の有用性を説明してください。

60
Eugene Tiurin

Eugeneの答えは正しい関数を使用してデータを取得しますが、後世のために、React v0.14 +( this answer ):

  import ReactDOM from 'react-dom';
  //...
  componentDidMount() {
    var rect = ReactDOM.findDOMNode(this)
      .getBoundingClientRect()
  }

私のために完璧に働いており、私はデータを使用して、マウントしたばかりの新しいコンポーネントの一番上までスクロールしています。

25
sonarforte
  import ReactDOM from 'react-dom';
  //...
  componentDidMount() {
    var n = ReactDOM.findDOMNode(this);
    console.log(n.offsetTop);
  }

NodeからoffsetTopを取得するだけです。

9
rhigdon

推奨されていないfindDOMNodeを回避するrefを使用したより良いソリューション。

...
onScroll() {
    let offsetTop  = this.instance.getBoundingClientRect().top;
}
...
render() {
...
<Component ref={(el) => this.instance = el } />
...
8
Pietro

React 16.3以降を使用している場合のより簡単な方法は、コンストラクターでrefを作成し、次に示すように、使用するコンポーネントにそれを添付することです。

...
constructor(props){
   ...
   //create a ref
   this.someRefName = React.createRef();

}

onScroll(){
let offsetTop = this.someRefName.current.offsetTop;

}

render(){
...
<Component ref={this.someRefName} />

}

8
Chikwado

私は著者がクラスベースのコンポーネントに関連して質問をすることを理解していますが、 React 16.8.0(February 6、2019) でフックを利用できることに言及する価値があると思います関数ベースのコンポーネント。

サンプルコード:

import { useRef } from 'react'

function Component() {
  const inputRef = useRef()

  return (
    <input ref={inputRef} />
    <div
      onScroll={() => {
        const { offsetTop } = inputRef.current
        ...
      }}
    >
  )
}
7
Powell Ye