在React中获取div的offsetTop位置

时间:2015-09-19 11:47:25

标签: javascript scroll reactjs offset

我正在尝试在React中实现List视图。 我想要实现的是存储列表标题信息并注册组件并注册滚动事件。 每当用户滚动窗口时,我都想取出存储的div并重新计算offsetTop数据。

现在的问题是,我发现控制台只打印出初始值(值是固定的,永不改变)offsetTop数据在onscroll函数中永远不会改变。

有人建议如何从offsetTop对象获取最新的_instances吗?

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

7 个答案:

答案 0 :(得分:54)

可能会鼓励您使用Element.getBoundingClientRect()方法获取元素的顶部偏移量。此方法提供元素在视口中的完整偏移值(左,上,右,底,宽,高)。

检查描述此方法有用程度的John Resig's post

答案 1 :(得分:22)

<> Eugene的答案使用正确的函数来获取数据,但对于后人,我想详细说明如何在React v0.14 +中使用它(根据this answer) :

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

完美地为我工作,并且我使用数据滚动到刚安装的新组件的顶部。

答案 2 :(得分:7)

更好的解决方案,以避免不鼓励使用findDOMNode。

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

答案 3 :(得分:7)

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

您可以从节点中获取offsetTop。

答案 4 :(得分:5)

如果您使用的是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} />

}

答案 5 :(得分:5)

我确实意识到作者提出了与基于类的组件有关的问题,但是我认为值得一提的是,从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
        ...
      }}
    >
  )
}

答案 6 :(得分:0)

onScroll 有一个事件,其中包含此 div 中的所有本机和子元素,因此您可以像下面这样使用它并获取目标元素 offsetTop。

const getoffSet = e => {
   console.log(e, e.natiiveEvent.target.childNodes[0].offsetTop)
}

return (
   <div onScroll={(e) => getoffSet(e)} ref={listview} style={styles}> 
   </div>
)