Как я могу прокрутить div, чтобы быть видимым в ReactJS?
У меня есть всплывающий список, который является div
, который содержит вертикальный список дочерних div
s. Я добавил вверх/вниз клавиатурную навигацию, чтобы изменить, какой ребенок в данный момент выделен.
Прямо сейчас, если я нажимаю клавишу вниз достаточно, выделенный элемент больше не отображается. То же самое происходит и с клавишей "вверх", если прокручивается вид.
Каков правильный способ в React для автоматического прокрутки дочернего элемента div
в представлении?
Ответы
Ответ 1
Я предполагаю, что у вас есть какой-то компонент List
и какой-то компонент Item
. То, как я это сделал в одном проекте, должен был сообщить этому элементу, был ли он активным или нет; элемент попросит список прокрутить его в поле зрения, если это необходимо. Рассмотрим следующий псевдокод:
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 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 scrollTop appropriately.
}
}
Если вы не хотите выполнять математику, чтобы определить, является ли элемент видимым внутри списка node, вы можете использовать метод DOM scrollIntoView()
или специфический для Webkit scrollIntoViewIfNeeded
, который имеет polyfill доступный, поэтому вы можете использовать его в браузерах, отличных от Webkit.
Ответ 2
Чтобы построить ответ на @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;
}
}
Ответ 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
Для 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/)
Читая спецификацию, было немного сложно понять значение блока и встроенного, но после игры с ним я обнаружил, что для вертикального списка прокрутки блок: "конец" убедился, что элемент был виден без искусственной прокрутки верхней части моего контента из окна просмотра. С "центром" элемент возле дна будет сдвинут слишком далеко, и под ним появится пустое пространство. Но мой контейнер является родителем flex с оправданием: "растянуть", чтобы он мог повлиять на поведение. Я не копал слишком много. Элементы с скрытым переполнением влияют на то, как действует scrollIntoView, поэтому вам, вероятно, придется экспериментировать самостоятельно.
В моем приложении есть родительский элемент, который должен быть в поле зрения, и если выбран ребенок, он также прокручивается в представлении. Это хорошо работает, поскольку родительский DidMount происходит до дочернего DidMount, поэтому он прокручивается к родительскому элементу, а затем, когда активный дочерний элемент отображается, прокручивается дальше, чтобы отобразить его.
Ответ 5
В обработчике keyup/down вам просто нужно установить свойство scrollTop
для div, который вы хотите прокрутить, чтобы прокрутить вниз (или вверх).
Например:
JSX:
<div ref="foo">{content}</div>
keyup/down handler:
this.refs.foo.getDOMNode().scrollTop += 10
Если вы делаете что-то подобное выше, ваш div будет прокручиваться вниз на 10 пикселей (при условии, что для div установлено значение переполнения auto
или scroll
в 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
Ответ 6
На всякий случай кто-то спотыкается здесь, я сделал это так
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
Он всегда будет показывать tracker
div
Ответ 7
Я просто добавляю еще немного информации для других, которые ищут возможность Scroll-To в React. Я связал несколько библиотек для выполнения Scroll-To для моего приложения, и никто из них не работал из моего прецедента до тех пор, пока не обнаружил реакцию scrollchor, поэтому я решил передать его. https://github.com/bySabi/react-scrollchor
Ответ 8
У меня был 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"/>