Як реагувати вниз?


127

Я хочу створити систему чату і автоматично прокручувати донизу при вході у вікно та при надходженні нових повідомлень. Як ви автоматично прокручуєте до дна контейнера в React?

Відповіді:


221

Як згадував Тушар, ви можете зберігати манекен, який розкривається внизу чату:

render () {
  return (
    <div>
      <div className="MessageContainer" >
        <div className="MessagesList">
          {this.renderMessages()}
        </div>
        <div style={{ float:"left", clear: "both" }}
             ref={(el) => { this.messagesEnd = el; }}>
        </div>
      </div>
    </div>
  );
}

а потім прокручуйте до нього щоразу, коли ваш компонент оновлюється (тобто стан оновлюється після додавання нових повідомлень):

scrollToBottom = () => {
  this.messagesEnd.scrollIntoView({ behavior: "smooth" });
}

componentDidMount() {
  this.scrollToBottom();
}

componentDidUpdate() {
  this.scrollToBottom();
}

Тут я використовую стандартний метод Element.scrollIntoView .


3
попередження з документації: "findDOMNode не можна використовувати на функціональних компонентах."
Томаш Муларчик

1
this.messagesEnd.scrollIntoView()добре працював для мене. Користуватися не було потреби findDOMNode().
Rajat Saxena

змінили функцію, щоб scrollToBottom(){this.scrollBottom.scrollIntoView({ behavior: 'smooth' })}змусити її працювати в новіших версіях
Kunok

2
Гаразд, я видалив findDOMNode. Якщо це не працює для когось, ви можете перевірити історію редагування відповіді.
metakermit

7
У мене є помилка, що для scrollIntoView є TypeError: Неможливо прочитати властивість 'scrollIntoView' з невизначеного. Що робити?
Феруза

90

Я просто хочу оновити відповідь, щоб вона відповідала новому React.createRef() методу , але це в основному те саме, просто майте на увазі currentвластивість у створеному реф.

class Messages extends React.Component {

  const messagesEndRef = React.createRef()

  componentDidMount () {
    this.scrollToBottom()
  }
  componentDidUpdate () {
    this.scrollToBottom()
  }
  scrollToBottom = () => {
    this.messagesEnd.current.scrollIntoView({ behavior: 'smooth' })
  }
  render () {
    const { messages } = this.props
    return (
      <div>
        {messages.map(message => <Message key={message.id} {...message} />)}
        <div ref={this.messagesEndRef} />
      </div>
    )
  }
}

ОНОВЛЕННЯ:

Тепер, коли гачки доступні, я оновлюю відповідь, щоб додати використання useRefта useEffectгачки, реальна річ, що робить магію (реагувати реф. І scrollIntoViewметод DOM) залишається тією ж:

import React, { useEffect, useRef } from 'react'

const Messages = ({ messages }) => {

  const messagesEndRef = useRef(null)

  const scrollToBottom = () => {
    messagesEndRef.current.scrollIntoView({ behavior: "smooth" })
  }

  useEffect(scrollToBottom, [messages]);

  return (
    <div>
      {messages.map(message => <Message key={message.id} {...message} />)}
      <div ref={messagesEndRef} />
    </div>
  )
}

Також зробив (дуже основні) кодиandbox, якщо ви хочете перевірити поведінку https://codesandbox.io/s/scrolltobottomexample-f90lz


2
компонентDidUpdate може викликати багато разів у життєвому циклі React. Отже, ми повинні перевірити ref this.messagesEnd.current існує чи немає у функції scrollToBottom. Якщо this.messagesEnd.current не існує, з’явиться повідомлення про помилку TypeError: Неможливо прочитати властивість 'scrollIntoView' з null. Отже, додайте це, якщо умова також прокручуєToBottom = () => {if (this.messagesEnd.current) {this.messagesEnd.current.scrollIntoView ({поведінка: 'smooth'})}}
Arpit

компонентDidUpdate завжди відбувається після першого візуалізації ( reactjs.org/docs/react-component.html#the-component-lifecycle ). У цьому прикладі помилок не повинно бути і this.messagesEnd.currentіснує завжди. Однак важливо зауважити, що дзвінок this.messagesEnd.currentперед першою візуалізацією призведе до вказаної вами помилки. Thnx.
Дієго Лара

що є this.messagesEndу вашому першому прикладі методу scrollTo?
dcsan

@dcsan - це React ref, вони використовуються для відстеження елемента DOM навіть після рендерингу. reactjs.org/docs/refs-and-the-dom.html#creating-refs
Дієго Лара

1
Другий приклад коду не працює. useEffectПотреба метод , який буде розміщений з () => {scrollToBottom()}. Все одно дуже дякую
Гаспар

36

Не використовувати findDOMNode

Компоненти класу з реф

class MyComponent extends Component {
  componentDidMount() {
    this.scrollToBottom();
  }

  componentDidUpdate() {
    this.scrollToBottom();
  }

  scrollToBottom() {
    this.el.scrollIntoView({ behavior: 'smooth' });
  }

  render() {
    return <div ref={el => { this.el = el; }} />
  }
}

Функціональні компоненти з гачками:

import React, { useRef, useEffect } from 'react';

const MyComponent = () => {
  const divRref = useRef(null);

  useEffect(() => {
    divRef.current.scrollIntoView({ behavior: 'smooth' });
  });

  return <div ref={divRef} />;
}

2
Чи можете ви пояснити, чому не слід використовувати findDOMNode?
один stevy boi

2
@steviekins Тому що "він блокує певні покращення в React" і, ймовірно, буде застарілим github.com/yannickcr/eslint-plugin-react/isissue/…
tgdn

2
Повинен бути американський правопис behavior(не можна редагувати, оскільки "правки повинні бути не менше 6 символів", зітхають).
Джо Фріман

1
Підтримка scrollIntoViewз на smoothсьогодні дуже бідна.
Андрейкуль

@Andreykul, я, мабуть, бачу подібні результати із використанням 'smooth'. Це не послідовно.
flimflam57

18

Завдяки @enlitement

нам слід уникати використання findDOMNode, ми можемо використовувати refsдля відстеження компонентів

render() {
  ...

  return (
    <div>
      <div
        className="MessageList"
        ref={(div) => {
          this.messageList = div;
        }}
      >
        { messageListContent }
      </div>
    </div>
  );
}



scrollToBottom() {
  const scrollHeight = this.messageList.scrollHeight;
  const height = this.messageList.clientHeight;
  const maxScrollTop = scrollHeight - height;
  this.messageList.scrollTop = maxScrollTop > 0 ? maxScrollTop : 0;
}

componentDidUpdate() {
  this.scrollToBottom();
}

довідка:


Я вважаю це рішення найбільш прийнятним, оскільки воно не додає нових (фіктивних) елементів у DOM, а має справу буквально з існуючими, дякую jk2k
devplayer

7

Ви можете використовувати refs для відстеження компонентів.

Якщо ви знаєте спосіб встановити refодин окремий компонент (останній), будь ласка, опублікуйте!

Ось що я знайшов, що працював для мене:

class ChatContainer extends React.Component {
  render() {
    const {
      messages
    } = this.props;

    var messageBubbles = messages.map((message, idx) => (
      <MessageBubble
        key={message.id}
        message={message.body}
        ref={(ref) => this['_div' + idx] = ref}
      />
    ));

    return (
      <div>
        {messageBubbles}
      </div>
    );
  }

  componentDidMount() {
    this.handleResize();

    // Scroll to the bottom on initialization
    var len = this.props.messages.length - 1;
    const node = ReactDOM.findDOMNode(this['_div' + len]);
    if (node) {
      node.scrollIntoView();
    }
  }

  componentDidUpdate() {
    // Scroll as new elements come along
    var len = this.props.messages.length - 1;
    const node = ReactDOM.findDOMNode(this['_div' + len]);
    if (node) {
      node.scrollIntoView();
    }
  }
}

6
  1. Посилайтеся на контейнер для повідомлень.

    <div ref={(el) => { this.messagesContainer = el; }}> YOUR MESSAGES </div>
  2. Знайдіть контейнер для повідомлень і зробіть його scrollTopатрибут рівним scrollHeight:

    scrollToBottom = () => {
        const messagesContainer = ReactDOM.findDOMNode(this.messagesContainer);
        messagesContainer.scrollTop = messagesContainer.scrollHeight;
    };
  3. Вищеописаний метод на componentDidMountі componentDidUpdate.

    componentDidMount() {
         this.scrollToBottom();
    }
    
    componentDidUpdate() {
         this.scrollToBottom();
    }

Ось як це я використовую у своєму коді:

 export default class StoryView extends Component {

    constructor(props) {
        super(props);
        this.scrollToBottom = this.scrollToBottom.bind(this);
    }

    scrollToBottom = () => {
        const messagesContainer = ReactDOM.findDOMNode(this.messagesContainer);
        messagesContainer.scrollTop = messagesContainer.scrollHeight;
    };

    componentDidMount() {
        this.scrollToBottom();
    }

    componentDidUpdate() {
        this.scrollToBottom();
    }

    render() {
        return (
            <div>
                <Grid className="storyView">
                    <Row>
                        <div className="codeView">
                            <Col md={8} mdOffset={2}>
                                <div ref={(el) => { this.messagesContainer = el; }} 
                                     className="chat">
                                    {
                                        this.props.messages.map(function (message, i) {
                                            return (
                                                <div key={i}>
                                                    <div className="bubble" >
                                                        {message.body}
                                                    </div>
                                                </div>
                                            );
                                        }, this)
                                    }
                                </div>
                            </Col>
                        </div>
                    </Row>
                </Grid>
            </div>
        );
    }
}

6

feed-reakct-feed автоматично прокручується до останнього елемента, якщо користувач уже був у нижній частині секції, що прокручується. В іншому випадку він залишить користувача в тому самому положенні. Я думаю, що це дуже корисно для компонентів чату :)

Я думаю, що інші відповіді тут змусять прокручувати щоразу, незалежно від того, де була смуга прокрутки. Інша проблема scrollIntoViewполягає в тому, що вона буде прокручувати всю сторінку, якщо вашого прокручуваного дива не було в режимі перегляду.

Його можна використовувати так:

import * as React from 'react'

import ScrollableFeed from 'react-scrollable-feed'

class App extends React.Component {
  render() {
    const messages = ['Item 1', 'Item 2'];

    return (
      <ScrollableFeed>
        {messages.map((message, i) => <div key={i}>{message}</div>)}
      </ScrollableFeed>
    );
  }
}

Просто переконайтесь, що компонент обгортки має певний heightабоmax-height

Відмова: Я є власником пакету


6

Я створив порожній елемент у кінці повідомлень і прокрутив до цього елемента. Немає необхідності слідкувати за реф.


як ти це зробив
Педро JR

@mmla З якою проблемою ви стикалися з Safari? Це не прокручується надійно?
Тушар Агарвал

5

Якщо ви хочете зробити це за допомогою React Hooks, цього методу можна дотримуватися. Для манекена діва розміщено внизу чату. useRef Hook використовується тут.

Довідка API Hooks: https://reactjs.org/docs/hooks-reference.html#useref

import React, { useEffect, useRef } from 'react';

const ChatView = ({ ...props }) => {
const el = useRef(null);

useEffect(() => {
    el.current.scrollIntoView({ block: 'end', behavior: 'smooth' });
});

 return (
   <div>
     <div className="MessageContainer" >
       <div className="MessagesList">
         {this.renderMessages()}
       </div>
       <div id={'el'} ref={el}>
       </div>
     </div>
    </div>
  );
}

5

Найпростіший і найкращий спосіб, який я рекомендував би - це.

Моя версія ReactJS: 16.12.0


Структура HTML всередині render()функції

    render()
        return(
            <body>
                <div ref="messageList">
                    <div>Message 1</div>
                    <div>Message 2</div>
                    <div>Message 3</div>
                </div>
            </body>
        )
    )

scrollToBottom()функція, яка отримає посилання на елемент. і прокручуйте відповідно до scrollIntoView()функції.

  scrollToBottom = () => {
    const { messageList } = this.refs;
    messageList.scrollIntoView({behavior: "smooth", block: "end", inline: "nearest"});
  }

і викликати вищевказану функцію всередині componentDidMount()іcomponentDidUpdate()

для отримання додаткових пояснень про Element.scrollIntoView()відвідування developer.mozilla.org


Справді потрібно оголосити в повідомленні div, а не в контейнері
toing_toing

4

Я не зміг отримати жоден з наведених нижче відповідей на роботу, але простий js зробив для мене трюк:

  window.scrollTo({
  top: document.body.scrollHeight,
  left: 0,
  behavior: 'smooth'
});

3

Приклад роботи:

Ви можете використовувати scrollIntoViewметод DOM, щоб зробити компонент видимим у поданні.

Для цього під час візуалізації компонента просто вказуйте ідентифікаційний номер для елемента DOM за допомогою refатрибута. Потім використовуйте метод scrollIntoViewна componentDidMountжиттєвому циклі. Я просто ставлю робочий зразок коду для цього рішення. Далі наведено компонентне відображення кожного разу, коли надходить повідомлення. Ви повинні написати код / ​​методи для надання цього компонента.

class ChatMessage extends Component {
    scrollToBottom = (ref) => {
        this.refs[ref].scrollIntoView({ behavior: "smooth" });
    }

    componentDidMount() {
        this.scrollToBottom(this.props.message.MessageId);
    }

    render() {
        return(
            <div ref={this.props.message.MessageId}>
                <div>Message content here...</div>
            </div>
        );
    }
}

Ось this.props.message.MessageIdунікальний ідентифікатор конкретного повідомлення в чаті, переданого якprops


Дивовижний Шерін Бхай працює як торт. Дякую
Мохаммед Сарфараз

@MohammedSarfaraz Рада, що можу допомогти :)
Шерін Хосе

2
import React, {Component} from 'react';

export default class ChatOutPut extends Component {

    constructor(props) {
        super(props);
        this.state = {
            messages: props.chatmessages
        };
    }
    componentDidUpdate = (previousProps, previousState) => {
        if (this.refs.chatoutput != null) {
            this.refs.chatoutput.scrollTop = this.refs.chatoutput.scrollHeight;
        }
    }
    renderMessage(data) {
        return (
            <div key={data.key}>
                {data.message}
            </div>
        );
    }
    render() {
        return (
            <div ref='chatoutput' className={classes.chatoutputcontainer}>
                {this.state.messages.map(this.renderMessage, this)}
            </div>
        );
    }
}

1

Мені подобається робити це наступним чином.

componentDidUpdate(prevProps, prevState){
  this.scrollToBottom();
}

scrollToBottom() {
  const {thing} = this.refs;
  thing.scrollTop = thing.scrollHeight - thing.clientHeight;
}

render(){
  return(
    <div ref={`thing`}>
      <ManyThings things={}>
    </div>
  )
}

1

дякую "metakermit" за його гарну відповідь, але я думаю, що ми можемо зробити це трохи краще, щоб прокрутити донизу, ми повинні використовувати це:

scrollToBottom = () => {
   this.messagesEnd.scrollIntoView({ behavior: "smooth", block: "end", inline: "nearest" });
}

але якщо ви хочете прокрутити верх, вам слід скористатися цим:

scrollToTop = () => {
   this.messagesEnd.scrollIntoView({ behavior: "smooth", block: "start", inline: "nearest" });
}   

і ці коди є загальними:

componentDidMount() {
  this.scrollToBottom();
}

componentDidUpdate() {
  this.scrollToBottom();
}


render () {
  return (
    <div>
      <div className="MessageContainer" >
        <div className="MessagesList">
          {this.renderMessages()}
        </div>
        <div style={{ float:"left", clear: "both" }}
             ref={(el) => { this.messagesEnd = el; }}>
        </div>
      </div>
    </div>
  );
}


0

Використання React.createRef()

class MessageBox extends Component {
        constructor(props) {
            super(props)
            this.boxRef = React.createRef()
        }

        scrollToBottom = () => {
            this.boxRef.current.scrollTop = this.boxRef.current.scrollHeight
        }

        componentDidUpdate = () => {
            this.scrollToBottom()
        }

        render() {
            return (
                        <div ref={this.boxRef}></div>
                    )
        }
}

0

Ось як би ви вирішили це в TypeScript (використовуючи посилання на цільовий елемент, де ви прокручуєте):

class Chat extends Component <TextChatPropsType, TextChatStateType> {
  private scrollTarget = React.createRef<HTMLDivElement>();
  componentDidMount() {
    this.scrollToBottom();//scroll to bottom on mount
  }

  componentDidUpdate() {
    this.scrollToBottom();//scroll to bottom when new message was added
  }

  scrollToBottom = () => {
    const node: HTMLDivElement | null = this.scrollTarget.current; //get the element via ref

    if (node) { //current ref can be null, so we have to check
        node.scrollIntoView({behavior: 'smooth'}); //scroll to the targeted element
    }
  };

  render <div>
    {message.map((m: Message) => <ChatMessage key={`chat--${m.id}`} message={m}/>}
     <div ref={this.scrollTarget} data-explanation="This is where we scroll to"></div>
   </div>
}

Для отримання додаткової інформації про використання ref за допомогою React та Typescript ви можете знайти чудову статтю тут .


-1

Повна версія (Typescript):

import * as React from 'react'

export class DivWithScrollHere extends React.Component<any, any> {

  loading:any = React.createRef();

  componentDidMount() {
    this.loading.scrollIntoView(false);
  }

  render() {

    return (
      <div ref={e => { this.loading = e; }}> <LoadingTile /> </div>
    )
  }
}


це дає всілякі помилки для мене: Property 'scrollIntoView' does not exist on type 'RefObject<unknown>'. і Type 'HTMLDivElement | null' is not assignable to type 'RefObject<unknown>'. Type 'null' is not assignable to type 'RefObject<unknown>'. так ...
dcsan

Версія ReactJS pls? Я використовую 1.16.0
TechTurtle
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.