반응에서 아래로 스크롤하는 방법은 무엇입니까?


127

채팅 시스템을 구축하고 창에 들어갈 때와 새 메시지가 들어올 때 자동으로 하단으로 스크롤하고 싶습니다. React에서 컨테이너 하단으로 자동 스크롤하는 방법은 무엇입니까?

답변:


221

Tushar가 언급했듯이 채팅 하단에 더미 div를 유지할 수 있습니다.

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는 기능 구성 요소에서 사용할 수 없습니다."
Tomasz Mularczyk

1
this.messagesEnd.scrollIntoView()나를 위해 잘 작동했습니다. 사용할 필요가 없습니다 findDOMNode().
라자 Saxena는

에 변경된 기능 scrollToBottom(){this.scrollBottom.scrollIntoView({ behavior: 'smooth' })}이 최신 버전에서 작동하도록
Kunok

2
좋아, findDOMNode를 제거했습니다. 이것이 다른 사람에게 효과가 없다면 답변의 편집 내역을 확인할 수 있습니다.
metakermit

7
scrollIntoView가 TypeError : Cannot read property 'scrollIntoView'of undefined라는 오류가 있습니다. 무엇을해야합니까?
Feruza

90

React.createRef() 메서드 와 일치하도록 답변을 업데이트하고 싶지만 기본적으로 동일 current하며 생성 된 ref 의 속성을 염두에 두십시오 .

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>
    )
  }
}

최신 정보:

이제 후크를 사용할 수 있으므로 useRefuseEffect후크 사용을 추가하기 위해 답변을 업데이트하고 있습니다 . 실제 수행하는 마술 (React refs 및 scrollIntoViewDOM 메서드)은 동일하게 유지됩니다.

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>
  )
}

또한 https://codesandbox.io/s/scrolltobottomexample-f90lz 동작을 확인하려면 (매우 기본적인) 코드 샌드 박스를 만들었습니다.


2
componentDidUpdate는 React 라이프 사이클에서 여러 번 호출 할 수 있습니다. 그래서 우리는 ref this.messagesEnd.current가 scrollToBottom 함수에 존재하는지 확인해야합니다. this.messagesEnd.current가 없으면 오류 메시지에 TypeError : Cannot read property 'scrollIntoView'of null이 표시됩니다. 따라서이 if 조건도 추가하십시오 scrollToBottom = () => {if (this.messagesEnd.current) {this.messagesEnd.current.scrollIntoView ({behavior : 'smooth'})}}
Arpit

componentDidUpdate는 항상 첫 번째 렌더링 이후에 발생합니다 ( reactjs.org/docs/react-component.html#the-component-lifecycle ). 이 예에서는 오류가 없어야하며 this.messagesEnd.current항상 존재합니다. 그럼에도 불구하고 this.messagesEnd.current첫 번째 렌더링 전에 호출 하면 지적한 오류가 발생 한다는 점에 유의하는 것이 중요합니다 . Thnx.
Diego Lara

this.messagesEndscrollTo 메서드의 첫 번째 예제에는 무엇이 있습니까?
dcsan

@dcsan은 React ref이며, 다시 렌더링 한 후에도 DOM 요소를 추적하는 데 사용됩니다. reactjs.org/docs/refs-and-the-dom.html#creating-refs
Diego Lara

1
두 번째 예제 코드는 작동하지 않습니다. useEffect방법의 필요성에 배치합니다 () => {scrollToBottom()}. 어쨌든 감사합니다
Gaspar

36

사용하지 마세요 findDOMNode

ref가있는 클래스 구성 요소

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는 "반작용에 차단 certains 개선"하기 때문에 가능성이 더 이상 사용되지 않습니다 github.com/yannickcr/eslint-plugin-react/issues/...
tgdn

2
미국식 철자 behavior여야합니다 ( "편집은 6 자 이상이어야하기 때문에 편집 할 수 없음", 한숨).
Joe Freeman

1
scrollIntoViewwith smooth에 대한 지원 은 현재 매우 열악합니다.
Andreykul

@Andreykul, '부드러움'을 사용하여 비슷한 결과를 보는 것 같습니다. 일관성이 없습니다.
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();
    }
  }
}

7

react-scrollable-feed 는 사용자가 이미 스크롤 가능한 섹션의 맨 아래에있는 경우 자동으로 최신 요소로 스크롤합니다. 그렇지 않으면 사용자가 같은 위치에있게됩니다. 나는 이것이 채팅 구성 요소에 매우 유용하다고 생각합니다. :)

여기에 다른 답변은 스크롤 막대가 어디에 있든 상관없이 매번 강제로 스크롤 할 것이라고 생각합니다. 다른 문제 scrollIntoView는 스크롤 가능한 div가 보이지 않으면 전체 페이지를 스크롤한다는 것입니다.

다음과 같이 사용할 수 있습니다.

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

면책 조항 : 나는 패키지의 소유자입니다.


고마워요, 당신의 컨트롤을 사용했습니다. 참고 : forceScroll = true를 사용해야했기 때문에 원하는대로 작동하도록 만들어야했습니다. 어떤 이유로 스크롤 막대가 나타나기 시작했을 때 자동으로 맨 위로 스크롤되지 않았습니다.
Patric

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

메시지 끝에 빈 요소를 만들고 해당 요소로 스크롤했습니다. 심판을 추적 할 필요가 없습니다.


어떻게
Pedro JR

@mmla Safari에서 직면 한 문제는 무엇입니까? 안정적으로 스크롤되지 않습니까?
Tushar Agarwal

5

React Hooks로 이것을하고 싶다면이 방법을 따를 수 있습니다. 더미 div의 경우 채팅 하단에 배치되었습니다. useRef 후크가 여기에 사용됩니다.

Hooks API 참조 : 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


render()함수 내부의 HTML 구조

    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

작업 예 :

DOM scrollIntoView메서드를 사용하여 구성 요소를 뷰에 표시 할 수 있습니다.

이를 위해 컴포넌트를 렌더링하는 동안 ref속성을 사용하여 DOM 요소에 대한 참조 ID를 제공하십시오 . 그런 다음 라이프 사이클 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은 전달 된 특정 채팅 메시지의 고유 ID입니다.props


놀라운 쉐린은이 cake.Thank처럼 작동하고 bhai
모하메드 Sarfaraz

@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>
}

React 및 Typescript와 함께 ref를 사용하는 방법에 대한 자세한 내용은 여기 에서 훌륭한 기사를 찾을 수 있습니다 .


-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 19
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.