如何滚动 div 以在 ReactJS 中可见?
Posted
技术标签:
【中文标题】如何滚动 div 以在 ReactJS 中可见?【英文标题】:How can I scroll a div to be visible in ReactJS? 【发布时间】:2015-08-10 06:47:04 【问题描述】:我有一个弹出列表,它是一个div
,其中包含子div
s 的垂直列表。我添加了向上/向下键盘导航来更改当前突出显示的子项。
现在,如果我按下向下键的次数足够多,突出显示的项目将不再可见。如果滚动视图,向上键也会发生同样的情况。
在 React 中自动将子 div
滚动到视图中的正确方法是什么?
【问题讨论】:
【参考方案1】:我假设你有某种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()
或Webkit 特定的scrollIntoViewIfNeeded
,其中has a polyfill available 以便您可以使用它在非 Webkit 浏览器中。
【讨论】:
谢谢,你的第一个例子是我想的,但我更喜欢你的第二个例子,子节点不需要担心。 哇,Element.scrollIntoView()
太棒了。
请注意:在 React 15.x 及更高版本中,您将希望使用 ReactDom.findDOMNode
而不是 React.findDOMNode
我展示了另一种类似的方法,我在列表底部添加一个空元素,我总是可以在 this answer 中滚动到该元素。
将 ref 设置为字符串是 deprecated。建议改用callback pattern 或createRef API。【参考方案2】:
对于 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/)
阅读规范,有点难以理解块和内联的含义,但在玩过之后,我发现对于垂直滚动列表,块:'结束'确保元素可见,而不是人为地将我的内容顶部滚动到视口之外。使用“中心”,靠近底部的元素会向上滑动太多,并在其下方出现空白空间。但是我的容器是一个带有 justify: 'stretch' 的弹性父容器,因此可能会影响行为。我没有深入挖掘。隐藏溢出的元素会影响 scrollIntoView 的行为,因此您可能需要自己进行试验。
我的应用程序有一个必须在视图中的父级,如果选择了一个子级,它也会滚动到视图中。这很好用,因为父 DidMount 发生在子 DidMount 之前,因此它滚动到父级,然后在呈现活动子级时,进一步滚动以显示该子级。
【讨论】:
AFAIK,父母的 didMount 发生在孩子 ***.com/questions/32814970/… 是的,这是真的,@gaurav5430。不知道我在想什么! 如果您收到“scrollIntoView 不是函数”的错误消息,请记住将ref
分配给标准 DOM 元素,例如 div
,而不是 React 组件。更多详情here.【参考方案3】:
另一个在 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>);
演示:https://codepen.io/anon/pen/XpqJVe
【讨论】:
【参考方案4】:以@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;
【讨论】:
【参考方案5】:以防万一有人在这里绊倒,我是这样做的
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
【讨论】:
【参考方案6】:带有反应钩子:
-
导入
import ReactDOM from 'react-dom';
import React, useRef from 'react';
-
制作新的钩子:
const divRef = useRef<htmlDivElement>(null);
-
添加新的分区
<div ref=divRef/>
-
滚动功能:
const scrollToDivRef = () =>
let node = ReactDOM.findDOMNode(divRef.current) as Element;
node.scrollIntoView(block: 'start', behavior: 'smooth');
【讨论】:
【参考方案7】:在您的 keyup/down 处理程序中,您只需设置要滚动的 div 的 scrollTop
属性,使其向下(或向上)滚动。
例如:
JSX:
<div ref="foo">content</div>
keyup/down 处理程序:
this.refs.foo.getDOMNode().scrollTop += 10
如果您执行与上述类似的操作,您的 div 将向下滚动 10 个像素(假设 div 在 css 中设置为溢出 auto
或 scroll
,并且您的内容当然会溢出)。
您需要对此进行扩展以找到您想要将 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
【讨论】:
【参考方案8】:我只是为其他在 React 中搜索 Scroll-To 功能的人添加了一些信息。我已经绑定了几个库来为我的应用程序执行 Scroll-To,但在我找到 react-scrollchor 之前,没有一个库在我的用例中起作用,所以我想我会把它传递下去。 https://github.com/bySabi/react-scrollchor
【讨论】:
同样,我使用npmjs.com/package/scroll-into-view 效果很好。实现起来非常简单。在应该可见的组件内部:componentDidMount() scrollIntoView(React.findDOMNode(this)) @jsaven 我想你的意思是ReactDOM.findDOMNode(this)
: reactjs.org/docs/react-dom.html#finddomnode【参考方案9】:
我有一个 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');
然后我将想要滚动的组件提供给这样的引用
<Investments ref="plans"/>
【讨论】:
以上是关于如何滚动 div 以在 ReactJS 中可见?的主要内容,如果未能解决你的问题,请参考以下文章