如何滚动 div 使其在 ReactJS 中可见?
How can I scroll a div to be visible in ReactJS?
我有一个弹出列表,它是一个 div
,其中包含子 div
的垂直列表。我添加了 up/down 键盘导航来更改当前突出显示的子项。
现在,如果我按下向下键的次数足够多,突出显示的项目将不再可见。如果滚动视图,向上键也会发生同样的事情。
在 React 中自动将子 div
滚动到视图中的正确方法是什么?
在您的 keyup/down 处理程序中,您只需设置要滚动的 div 的 scrollTop
属性 以使其向下(或向上)滚动。
例如:
JSX:
<div ref="foo">{content}</div>
keyup/down 处理程序:
this.refs.foo.getDOMNode().scrollTop += 10
如果您执行与上述类似的操作,您的 div 将向下滚动 10 个像素(假设 div 设置为溢出 auto
或 scroll
in css,你的内容当然是溢出了)。
您需要对此进行扩展,以找到要将 div 向下滚动到的滚动 div 内元素的偏移量,然后修改 scrollTop
滚动到足以根据元素的高度显示元素。
在这里查看 MDN 对 scrollTop 和 offsetTop 的定义:
https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollTop
https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetTop
我假设您有某种 List
组件和某种 Item
组件。我这样做的方法 in one project 是让该项目知道它是否处于活动状态;如有必要,该项目会要求列表将其滚动到视图中。考虑以下伪代码:
class List extends React.Component {
render() {
return <div>{this.props.items.map(this.renderItem)}</div>;
}
renderItem(item) {
return <Item key={item.id} item={item}
active={item.id === this.props.activeId}
scrollIntoView={this.scrollElementIntoViewIfNeeded} />
}
scrollElementIntoViewIfNeeded(domNode) {
var containerDomNode = React.findDOMNode(this);
// Determine if `domNode` fully fits inside `containerDomNode`.
// If not, set the container's scrollTop appropriately.
}
}
class Item extends React.Component {
render() {
return <div>something...</div>;
}
componentDidMount() {
this.ensureVisible();
}
componentDidUpdate() {
this.ensureVisible();
}
ensureVisible() {
if (this.props.active) {
this.props.scrollIntoView(React.findDOMNode(this));
}
}
}
更好的解决方案可能是让列表负责将项目滚动到视图中(项目甚至不知道它在列表中)。为此,您可以将 ref
属性添加到某个项目并通过该属性找到它:
class List extends React.Component {
render() {
return <div>{this.props.items.map(this.renderItem)}</div>;
}
renderItem(item) {
var active = item.id === this.props.activeId;
var props = {
key: item.id,
item: item,
active: active
};
if (active) {
props.ref = "activeItem";
}
return <Item {...props} />
}
componentDidUpdate(prevProps) {
// only scroll into view if the active item changed last render
if (this.props.activeId !== prevProps.activeId) {
this.ensureActiveItemVisible();
}
}
ensureActiveItemVisible() {
var itemComponent = this.refs.activeItem;
if (itemComponent) {
var domNode = React.findDOMNode(itemComponent);
this.scrollElementIntoViewIfNeeded(domNode);
}
}
scrollElementIntoViewIfNeeded(domNode) {
var containerDomNode = React.findDOMNode(this);
// Determine if `domNode` fully fits inside `containerDomNode`.
// If not, set the container's scrollTop appropriately.
}
}
如果您不想通过数学运算来确定该项目在列表节点内是否可见,您可以使用 DOM method scrollIntoView()
or the Webkit-specific scrollIntoViewIfNeeded
, which has a polyfill available 这样您就可以在非 Webkit 浏览器中使用它。
为了建立@Michelle Tilley 的回答,有时我想在用户的选择发生变化时滚动,所以我在 componentDidUpdate
上触发了滚动。我还做了一些数学计算,以确定滚动多远以及是否需要滚动,这对我来说如下所示:
componentDidUpdate() {
let panel, node;
if (this.refs.selectedSection && this.refs.selectedItem) {
// This is the container you want to scroll.
panel = this.refs.listPanel;
// This is the element you want to make visible w/i the container
// Note: You can nest refs here if you want an item w/i the selected item
node = ReactDOM.findDOMNode(this.refs.selectedItem);
}
if (panel && node &&
(node.offsetTop > panel.scrollTop + panel.offsetHeight || node.offsetTop < panel.scrollTop)) {
panel.scrollTop = node.offsetTop - panel.offsetTop;
}
}
另一个在 ref 中使用函数而不是字符串的例子
class List extends React.Component {
constructor(props) {
super(props);
this.state = { items:[], index: 0 };
this._nodes = new Map();
this.handleAdd = this.handleAdd.bind(this);
this.handleRemove = this.handleRemove.bind(this);
}
handleAdd() {
let startNumber = 0;
if (this.state.items.length) {
startNumber = this.state.items[this.state.items.length - 1];
}
let newItems = this.state.items.splice(0);
for (let i = startNumber; i < startNumber + 100; i++) {
newItems.push(i);
}
this.setState({ items: newItems });
}
handleRemove() {
this.setState({ items: this.state.items.slice(1) });
}
handleShow(i) {
this.setState({index: i});
const node = this._nodes.get(i);
console.log(this._nodes);
if (node) {
ReactDOM.findDOMNode(node).scrollIntoView({block: 'end', behavior: 'smooth'});
}
}
render() {
return(
<div>
<ul>{this.state.items.map((item, i) => (<Item key={i} ref={(element) => this._nodes.set(i, element)}>{item}</Item>))}</ul>
<button onClick={this.handleShow.bind(this, 0)}>0</button>
<button onClick={this.handleShow.bind(this, 50)}>50</button>
<button onClick={this.handleShow.bind(this, 99)}>99</button>
<button onClick={this.handleAdd}>Add</button>
<button onClick={this.handleRemove}>Remove</button>
{this.state.index}
</div>
);
}
}
class Item extends React.Component
{
render() {
return (<li ref={ element => this.listItem = element }>
{this.props.children}
</li>);
}
}
我只是为其他在 React 中搜索 Scroll-To 功能的人添加一些信息。我已经绑定了几个库来为我的应用程序做 Scroll-To,并且 none 从我的用例开始工作,直到我找到 react-scrollchor,所以我想我会把它传递下去。 https://github.com/bySabi/react-scrollchor
以防万一有人在这里绊倒,我是这样做的
componentDidMount(){
const node = this.refs.trackerRef;
node && node.scrollIntoView({block: "end", behavior: 'smooth'})
}
componentDidUpdate() {
const node = this.refs.trackerRef;
node && node.scrollIntoView({block: "end", behavior: 'smooth'})
}
render() {
return (
<div>
{messages.map((msg, index) => {
return (
<Message key={index} msgObj={msg}
{/*<p>some test text</p>*/}
</Message>
)
})}
<div style={{height: '30px'}} id='#tracker' ref="trackerRef"></div>
</div>
)
}
scrollIntoView
是原生 DOM 功能 link
会一直显示tracker
div
对于 React 16,正确答案与之前的答案不同:
class Something extends Component {
constructor(props) {
super(props);
this.boxRef = React.createRef();
}
render() {
return (
<div ref={this.boxRef} />
);
}
}
然后滚动,只需添加(在构造函数之后):
componentDidMount() {
if (this.props.active) { // whatever your test might be
this.boxRef.current.scrollIntoView();
}
}
注意:您必须使用“.current”,并且您可以将选项发送到 scrollIntoView:
scrollIntoView({
behavior: 'smooth',
block: 'center',
inline: 'center',
});
(发现于 http://www.albertgao.xyz/2018/06/07/scroll-a-not-in-view-component-into-the-view-using-react/)
看了规范,有点难以理解 block 和 inline 的含义,但玩过之后,我发现对于垂直滚动列表, block: 'end' 确定了元素是可见的,无需人为地将我的内容顶部滚动到视口之外。使用 'center',底部附近的元素会向上滑动太远,并且空的 space 会出现在其下方。但是我的容器是一个带有 justify: 'stretch' 的 flex parent,所以这可能会影响行为。我没有深入挖掘。隐藏溢出的元素会影响 scrollIntoView 的行为,因此您可能需要自己进行试验。
我的应用程序有一个必须在视图中的父项,如果选择了一个子项,它也会滚动到视图中。这很有效,因为父级 DidMount 发生在子级 DidMount 之前,因此它滚动到父级,然后在呈现活动子级时,进一步滚动以将其置于视图中。
我有一个 NavLink,当我点击它时,它会像命名锚一样滚动到该元素。我是这样实现的。
<NavLink onClick={() => this.scrollToHref('plans')}>Our Plans</NavLink>
scrollToHref = (element) =>{
let node;
if(element === 'how'){
node = ReactDom.findDOMNode(this.refs.how);
console.log(this.refs)
}else if(element === 'plans'){
node = ReactDom.findDOMNode(this.refs.plans);
}else if(element === 'about'){
node = ReactDom.findDOMNode(this.refs.about);
}
node.scrollIntoView({block: 'start', behavior: 'smooth'});
}
然后我将要滚动的组件提供给这样的 ref
<Investments ref="plans"/>
有反应钩子:
- 导入
import ReactDOM from 'react-dom';
import React, {useRef} from 'react';
- 制作新钩子:
const divRef = useRef<HTMLDivElement>(null);
- 新增Div
<div ref={divRef}/>
- 滚动功能:
const scrollToDivRef = () => {
let node = ReactDOM.findDOMNode(divRef.current) as Element;
node.scrollIntoView({block: 'start', behavior: 'smooth'});
}
我有一个弹出列表,它是一个 div
,其中包含子 div
的垂直列表。我添加了 up/down 键盘导航来更改当前突出显示的子项。
现在,如果我按下向下键的次数足够多,突出显示的项目将不再可见。如果滚动视图,向上键也会发生同样的事情。
在 React 中自动将子 div
滚动到视图中的正确方法是什么?
在您的 keyup/down 处理程序中,您只需设置要滚动的 div 的 scrollTop
属性 以使其向下(或向上)滚动。
例如:
JSX:
<div ref="foo">{content}</div>
keyup/down 处理程序:
this.refs.foo.getDOMNode().scrollTop += 10
如果您执行与上述类似的操作,您的 div 将向下滚动 10 个像素(假设 div 设置为溢出 auto
或 scroll
in css,你的内容当然是溢出了)。
您需要对此进行扩展,以找到要将 div 向下滚动到的滚动 div 内元素的偏移量,然后修改 scrollTop
滚动到足以根据元素的高度显示元素。
在这里查看 MDN 对 scrollTop 和 offsetTop 的定义:
https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollTop
https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/offsetTop
我假设您有某种 List
组件和某种 Item
组件。我这样做的方法 in one project 是让该项目知道它是否处于活动状态;如有必要,该项目会要求列表将其滚动到视图中。考虑以下伪代码:
class List extends React.Component {
render() {
return <div>{this.props.items.map(this.renderItem)}</div>;
}
renderItem(item) {
return <Item key={item.id} item={item}
active={item.id === this.props.activeId}
scrollIntoView={this.scrollElementIntoViewIfNeeded} />
}
scrollElementIntoViewIfNeeded(domNode) {
var containerDomNode = React.findDOMNode(this);
// Determine if `domNode` fully fits inside `containerDomNode`.
// If not, set the container's scrollTop appropriately.
}
}
class Item extends React.Component {
render() {
return <div>something...</div>;
}
componentDidMount() {
this.ensureVisible();
}
componentDidUpdate() {
this.ensureVisible();
}
ensureVisible() {
if (this.props.active) {
this.props.scrollIntoView(React.findDOMNode(this));
}
}
}
更好的解决方案可能是让列表负责将项目滚动到视图中(项目甚至不知道它在列表中)。为此,您可以将 ref
属性添加到某个项目并通过该属性找到它:
class List extends React.Component {
render() {
return <div>{this.props.items.map(this.renderItem)}</div>;
}
renderItem(item) {
var active = item.id === this.props.activeId;
var props = {
key: item.id,
item: item,
active: active
};
if (active) {
props.ref = "activeItem";
}
return <Item {...props} />
}
componentDidUpdate(prevProps) {
// only scroll into view if the active item changed last render
if (this.props.activeId !== prevProps.activeId) {
this.ensureActiveItemVisible();
}
}
ensureActiveItemVisible() {
var itemComponent = this.refs.activeItem;
if (itemComponent) {
var domNode = React.findDOMNode(itemComponent);
this.scrollElementIntoViewIfNeeded(domNode);
}
}
scrollElementIntoViewIfNeeded(domNode) {
var containerDomNode = React.findDOMNode(this);
// Determine if `domNode` fully fits inside `containerDomNode`.
// If not, set the container's scrollTop appropriately.
}
}
如果您不想通过数学运算来确定该项目在列表节点内是否可见,您可以使用 DOM method scrollIntoView()
or the Webkit-specific scrollIntoViewIfNeeded
, which has a polyfill available 这样您就可以在非 Webkit 浏览器中使用它。
为了建立@Michelle Tilley 的回答,有时我想在用户的选择发生变化时滚动,所以我在 componentDidUpdate
上触发了滚动。我还做了一些数学计算,以确定滚动多远以及是否需要滚动,这对我来说如下所示:
componentDidUpdate() {
let panel, node;
if (this.refs.selectedSection && this.refs.selectedItem) {
// This is the container you want to scroll.
panel = this.refs.listPanel;
// This is the element you want to make visible w/i the container
// Note: You can nest refs here if you want an item w/i the selected item
node = ReactDOM.findDOMNode(this.refs.selectedItem);
}
if (panel && node &&
(node.offsetTop > panel.scrollTop + panel.offsetHeight || node.offsetTop < panel.scrollTop)) {
panel.scrollTop = node.offsetTop - panel.offsetTop;
}
}
另一个在 ref 中使用函数而不是字符串的例子
class List extends React.Component {
constructor(props) {
super(props);
this.state = { items:[], index: 0 };
this._nodes = new Map();
this.handleAdd = this.handleAdd.bind(this);
this.handleRemove = this.handleRemove.bind(this);
}
handleAdd() {
let startNumber = 0;
if (this.state.items.length) {
startNumber = this.state.items[this.state.items.length - 1];
}
let newItems = this.state.items.splice(0);
for (let i = startNumber; i < startNumber + 100; i++) {
newItems.push(i);
}
this.setState({ items: newItems });
}
handleRemove() {
this.setState({ items: this.state.items.slice(1) });
}
handleShow(i) {
this.setState({index: i});
const node = this._nodes.get(i);
console.log(this._nodes);
if (node) {
ReactDOM.findDOMNode(node).scrollIntoView({block: 'end', behavior: 'smooth'});
}
}
render() {
return(
<div>
<ul>{this.state.items.map((item, i) => (<Item key={i} ref={(element) => this._nodes.set(i, element)}>{item}</Item>))}</ul>
<button onClick={this.handleShow.bind(this, 0)}>0</button>
<button onClick={this.handleShow.bind(this, 50)}>50</button>
<button onClick={this.handleShow.bind(this, 99)}>99</button>
<button onClick={this.handleAdd}>Add</button>
<button onClick={this.handleRemove}>Remove</button>
{this.state.index}
</div>
);
}
}
class Item extends React.Component
{
render() {
return (<li ref={ element => this.listItem = element }>
{this.props.children}
</li>);
}
}
我只是为其他在 React 中搜索 Scroll-To 功能的人添加一些信息。我已经绑定了几个库来为我的应用程序做 Scroll-To,并且 none 从我的用例开始工作,直到我找到 react-scrollchor,所以我想我会把它传递下去。 https://github.com/bySabi/react-scrollchor
以防万一有人在这里绊倒,我是这样做的
componentDidMount(){
const node = this.refs.trackerRef;
node && node.scrollIntoView({block: "end", behavior: 'smooth'})
}
componentDidUpdate() {
const node = this.refs.trackerRef;
node && node.scrollIntoView({block: "end", behavior: 'smooth'})
}
render() {
return (
<div>
{messages.map((msg, index) => {
return (
<Message key={index} msgObj={msg}
{/*<p>some test text</p>*/}
</Message>
)
})}
<div style={{height: '30px'}} id='#tracker' ref="trackerRef"></div>
</div>
)
}
scrollIntoView
是原生 DOM 功能 link
会一直显示tracker
div
对于 React 16,正确答案与之前的答案不同:
class Something extends Component {
constructor(props) {
super(props);
this.boxRef = React.createRef();
}
render() {
return (
<div ref={this.boxRef} />
);
}
}
然后滚动,只需添加(在构造函数之后):
componentDidMount() {
if (this.props.active) { // whatever your test might be
this.boxRef.current.scrollIntoView();
}
}
注意:您必须使用“.current”,并且您可以将选项发送到 scrollIntoView:
scrollIntoView({
behavior: 'smooth',
block: 'center',
inline: 'center',
});
(发现于 http://www.albertgao.xyz/2018/06/07/scroll-a-not-in-view-component-into-the-view-using-react/)
看了规范,有点难以理解 block 和 inline 的含义,但玩过之后,我发现对于垂直滚动列表, block: 'end' 确定了元素是可见的,无需人为地将我的内容顶部滚动到视口之外。使用 'center',底部附近的元素会向上滑动太远,并且空的 space 会出现在其下方。但是我的容器是一个带有 justify: 'stretch' 的 flex parent,所以这可能会影响行为。我没有深入挖掘。隐藏溢出的元素会影响 scrollIntoView 的行为,因此您可能需要自己进行试验。
我的应用程序有一个必须在视图中的父项,如果选择了一个子项,它也会滚动到视图中。这很有效,因为父级 DidMount 发生在子级 DidMount 之前,因此它滚动到父级,然后在呈现活动子级时,进一步滚动以将其置于视图中。
我有一个 NavLink,当我点击它时,它会像命名锚一样滚动到该元素。我是这样实现的。
<NavLink onClick={() => this.scrollToHref('plans')}>Our Plans</NavLink>
scrollToHref = (element) =>{
let node;
if(element === 'how'){
node = ReactDom.findDOMNode(this.refs.how);
console.log(this.refs)
}else if(element === 'plans'){
node = ReactDom.findDOMNode(this.refs.plans);
}else if(element === 'about'){
node = ReactDom.findDOMNode(this.refs.about);
}
node.scrollIntoView({block: 'start', behavior: 'smooth'});
}
然后我将要滚动的组件提供给这样的 ref
<Investments ref="plans"/>
有反应钩子:
- 导入
import ReactDOM from 'react-dom';
import React, {useRef} from 'react';
- 制作新钩子:
const divRef = useRef<HTMLDivElement>(null);
- 新增Div
<div ref={divRef}/>
- 滚动功能:
const scrollToDivRef = () => {
let node = ReactDOM.findDOMNode(divRef.current) as Element;
node.scrollIntoView({block: 'start', behavior: 'smooth'});
}