获取 div 的偏移量 React 中的顶部位置

我试图在 React 中实现 List 视图。 我试图实现的是存储列表头信息,注册组件和注册滚动事件。 每次当用户滚动窗口时,我想取出存储的 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

218937 次浏览

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.

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.

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 } />
...
  import ReactDOM from 'react-dom';
//...
componentDidMount() {
var n = ReactDOM.findDOMNode(this);
console.log(n.offsetTop);
}

You can just grab the offsetTop from the Node.

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} />

}

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

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

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