在 React 中获取 div 的 offsetTop 位置
Get div's offsetTop positions in React
我正在尝试在 React 中实现列表视图。
我想要实现的是存储列表 headers 信息并注册组件和注册滚动事件。
每次当用户滚动 window 时,我想取出存储的 div 和 re-calculate offsetTop
数据。
现在的问题是,我发现控制台只是打印出初始值(该值是固定的,永远不会改变)offsetTop
数据在onscroll
函数中永远不会改变。
有人建议如何从 _instances
object 获取最新的 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,你可以从这里克隆和编译它:
我们建议您使用 Element.getBoundingClientRect() 方法来获取元素的顶部偏移量。此方法提供元素在视口中的完整偏移值(左、上、右、下、宽、高)。
检查 John Resig's post 描述此方法的帮助程度。
Eugene 的回答使用了正确的函数来获取数据,但为了后代,我想详细说明如何在 React v0.14+ 中使用它(根据 ):
import ReactDOM from 'react-dom';
//...
componentDidMount() {
var rect = ReactDOM.findDOMNode(this)
.getBoundingClientRect()
}
非常适合我,我正在使用数据滚动到刚刚安装的新组件的顶部。
使用 ref 的更好解决方案,以避免不鼓励的 findDOMNode。
...
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);
}
您可以直接从节点中获取 offsetTop。
如果您使用的是 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} />
}
我确实意识到作者提出了与基于 class 的组件相关的问题,但是我认为值得一提的是,从 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
...
}}
>
)
}
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>
)
检查高度 属性 是否未设置为 Parent:
- 如果 parent 元素没有设置高度,那么粘性元素不会
滚动时有任何区域可以坚持。发生这种情况是因为
粘性元素意味着 stick/scroll 在 a
的高度内
容器.
检查 Parent 元素是否为 Flexbox
如果sticky元素的parent是flexbox,有两种情况
检查:
粘性元素有 align-self: 自动设置(这是默认设置);
粘性元素具有 align-self: 拉伸集。如果粘性元素
有 align-self: auto Set: 在这种情况下,align-self 的值将
计算 parent 的 align-items 值。所以,
如果 parent 具有 align-items: 正常(默认)或
align-items: stretch set,那么这意味着粘性元素的高度将拉伸以填充整个可用 space。这将使粘性元素没有空间在 parent.
中滚动
如果粘性元素有align-self: stretch Set:
在这种情况下,粘性元素会拉伸到 parent 的高度,并且没有任何可滚动的区域。
如何使粘性元素在 Flexbox 中可滚动:
您可以简单地将 align-self 属性 的值设置为 align-self: flex-start。这会将粘性元素放在开头并且不会拉伸它。enter link description here
我正在尝试在 React 中实现列表视图。
我想要实现的是存储列表 headers 信息并注册组件和注册滚动事件。
每次当用户滚动 window 时,我想取出存储的 div 和 re-calculate offsetTop
数据。
现在的问题是,我发现控制台只是打印出初始值(该值是固定的,永远不会改变)offsetTop
数据在onscroll
函数中永远不会改变。
有人建议如何从 _instances
object 获取最新的 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,你可以从这里克隆和编译它:
我们建议您使用 Element.getBoundingClientRect() 方法来获取元素的顶部偏移量。此方法提供元素在视口中的完整偏移值(左、上、右、下、宽、高)。
检查 John Resig's post 描述此方法的帮助程度。
Eugene 的回答使用了正确的函数来获取数据,但为了后代,我想详细说明如何在 React v0.14+ 中使用它(根据
import ReactDOM from 'react-dom';
//...
componentDidMount() {
var rect = ReactDOM.findDOMNode(this)
.getBoundingClientRect()
}
非常适合我,我正在使用数据滚动到刚刚安装的新组件的顶部。
使用 ref 的更好解决方案,以避免不鼓励的 findDOMNode。
...
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);
}
您可以直接从节点中获取 offsetTop。
如果您使用的是 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} />
}
我确实意识到作者提出了与基于 class 的组件相关的问题,但是我认为值得一提的是,从 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
...
}}
>
)
}
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>
)
检查高度 属性 是否未设置为 Parent:
- 如果 parent 元素没有设置高度,那么粘性元素不会
滚动时有任何区域可以坚持。发生这种情况是因为
粘性元素意味着 stick/scroll 在 a
的高度内 容器.
检查 Parent 元素是否为 Flexbox
如果sticky元素的parent是flexbox,有两种情况 检查:
粘性元素有 align-self: 自动设置(这是默认设置); 粘性元素具有 align-self: 拉伸集。如果粘性元素 有 align-self: auto Set: 在这种情况下,align-self 的值将 计算 parent 的 align-items 值。所以,
如果 parent 具有 align-items: 正常(默认)或 align-items: stretch set,那么这意味着粘性元素的高度将拉伸以填充整个可用 space。这将使粘性元素没有空间在 parent.
中滚动如果粘性元素有align-self: stretch Set:
在这种情况下,粘性元素会拉伸到 parent 的高度,并且没有任何可滚动的区域。
如何使粘性元素在 Flexbox 中可滚动: 您可以简单地将 align-self 属性 的值设置为 align-self: flex-start。这会将粘性元素放在开头并且不会拉伸它。enter link description here