Get div's offsetTop positions in React

JavascriptScrollReactjsOffset

Javascript Problem Overview


I am trying to implement a List view in React. What I am trying to achieve is that to store the list headers informations and register the components and register the scroll event. every time when user scroll the window, I'd like to take out the stored div and re-calculate the offsetTop data.

The problem now is that, I found the console just print out the initial value (the value is fixed and never changed) offsetTop data never change in onscroll function.

Anyone suggest how to get latest offsetTop from the _instances object?

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>
    );
  }
}

The whole source code is on Github, you can clone and compile it from here:

Github

Javascript Solutions


Solution 1 - Javascript

You may be encouraged to use the Element.getBoundingClientRect() method to get the top offset of your element. This method provides the full offset values (left, top, right, bottom, width, height) of your element in the viewport.

Check the John Resig's post describing how helpful this method is.

Solution 2 - Javascript

I do realize that the author asks question in relation to a class-based component, however I think it's worth mentioning that as of React 16.8.0 (February 6, 2019) you can take advantage of hooks in function-based components.

Example code:

import { useRef } from 'react'

function Component() {
  const inputRef = useRef()

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

Solution 3 - Javascript

Eugene's answer uses the correct function to get the data, but for posterity I'd like to spell out exactly how to use it in React v0.14+ (according to this answer):

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

Is working for me perfectly, and I'm using the data to scroll to the top of the new component that just mounted.

Solution 4 - Javascript

A quicker way if you are using React 16.3 and above is by creating a ref in the constructor, then attaching it to the component you wish to use with as shown below.

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

}

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

}

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

}

Solution 5 - Javascript

A better solution with ref to avoid findDOMNode that is discouraged.

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

Solution 6 - Javascript

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

You can just grab the offsetTop from the Node.

Solution 7 - Javascript

onScroll has a events whict contains all the native and child elements inside this div so u can use it like this shown below and get the targetted element offsetTop.

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

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

Solution 8 - Javascript

Checking if height Property Is Not Set on Parent:

  • If the parent element has no height set then the sticky element won't have any area to stick to when scrolling. This happens because the
    sticky element is meant to stick/scroll within the height of a
    container.

Checking if a Parent Element Is a Flexbox

  • If sticky element's parent is a flexbox, there are two scenarios to check for:

    The sticky element has align-self: auto set (which is the default); The sticky element has align-self: stretch set. If the Sticky Element Has align-self: auto Set: In this case the value of align-self would compute to the parent's align-items value. So,

if the parent has align-items: normal (which is the default) or align-items: stretch set, then it means the height of the sticky element would stretch to fill the entire available space. This would leave no room for the sticky element to scroll within the parent.

If the Sticky Element Has align-self: stretch Set:

In this case, the sticky element would stretch to the height of the parent, and would not have any area to scroll within.

How to Make Sticky Element Scrollable Within a Flexbox: You could simply set the value of the align-self property to align-self: flex-start. This would put the sticky element at the start and won't stretch it.enter link description here

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionJavaScripterView Question on Stackoverflow
Solution 1 - JavascriptEugene TiurinView Answer on Stackoverflow
Solution 2 - JavascriptPavel YeView Answer on Stackoverflow
Solution 3 - JavascriptsonarforteView Answer on Stackoverflow
Solution 4 - JavascriptChikwado View Answer on Stackoverflow
Solution 5 - JavascriptPietroView Answer on Stackoverflow
Solution 6 - JavascriptrhigdonView Answer on Stackoverflow
Solution 7 - JavascriptdEaD_ hOlicXView Answer on Stackoverflow
Solution 8 - JavascriptYogesh PawarView Answer on Stackoverflow