在React中获得div的offsetTop位置

Jav*_*ter 51 javascript scroll offset reactjs

我正在尝试在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>
    );
  }
}
Run Code Online (Sandbox Code Playgroud)

整个源代码在Github上,你可以从这里克隆和编译它:

Github上

Eug*_*rin 63

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

查看John Resig的帖子,描述这种方法的有用性.

  • 我想指出,与获得偏移相比,这种方法会产生显着的性能损失. (5认同)
  • @vise 您能否仅使用 offsetTop 或其他 getBoundingClientRect 来为这个问题提供更好的答案? (2认同)
  • 链接到外部URL时,请添加内容或摘要,以防将来该URL无效。 (2认同)

son*_*rte 26

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

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

对我来说是完美的,我正在使用数据滚动到刚安装的新组件的顶部.


Pie*_*tro 9

一个更好的解决方案,参考避免不鼓励使用findDOMNode.

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


rhi*_*don 9

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

你可以从Node中获取offsetTop.


小智 9

如果您使用的是React 16.3及更高版本,一种更快的方法是在构造函数中创建一个ref,然后将其附加到您希望使用的组件上,如下所示。

...
constructor(props){
   ...
   //create a ref
   this.someRefName = React.createRef();
Run Code Online (Sandbox Code Playgroud)

}

onScroll(){
let offsetTop = this.someRefName.current.offsetTop;
Run Code Online (Sandbox Code Playgroud)

}

render(){
...
<Component ref={this.someRefName} />
Run Code Online (Sandbox Code Playgroud)

}

  • 这不仅更快,而且还是为任何版本的 react &gt;= 16.x 创建 ref 的正确方法 (2认同)

Pav*_* Ye 9

我确实意识到作者提出了与基于类的组件有关的问题,但是我认为值得一提的是,从React 16.8.0(2019年2月6日)开始,您可以利用基于函数的组件中的钩子。

示例代码:

import { useRef } from 'react'

function Component() {
  const inputRef = useRef()

  return (
    <input ref={inputRef} />
    <div
      onScroll={() => {
        const { offsetTop } = inputRef.current
        ...
      }}
    >
  )
}
Run Code Online (Sandbox Code Playgroud)

  • 这似乎只给出了距最近父节点的 offsetTop —— 是否存在距视口的偏移量? (3认同)