จะเลื่อนไปด้านล่างในการตอบสนองได้อย่างไร?


128

ฉันต้องการสร้างระบบแชทและเลื่อนไปที่ด้านล่างโดยอัตโนมัติเมื่อเข้าสู่หน้าต่างและเมื่อมีข้อความใหม่เข้ามาคุณจะเลื่อนไปที่ด้านล่างของคอนเทนเนอร์โดยอัตโนมัติใน React ได้อย่างไร?

คำตอบ:


222

ตามที่ 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()ไม่มีความจำเป็นที่จะใช้เป็น
Rajat Saxena

เปลี่ยนฟังก์ชันเพื่อscrollToBottom(){this.scrollBottom.scrollIntoView({ behavior: 'smooth' })}ให้ใช้งานได้ในเวอร์ชันที่ใหม่กว่า
Kunok

2
โอเคฉันลบ findDOMNode แล้ว หากไม่ได้ผลสำหรับบางคนคุณสามารถตรวจสอบประวัติการแก้ไขของคำตอบได้
metakermit

7
ฉันมีข้อผิดพลาดที่ scrollIntoView เป็น TypeError: ไม่สามารถอ่านคุณสมบัติ 'scrollIntoView' ของ undefined จะทำอย่างไร?
Feruza

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

UPDATE:

ตอนนี้มี hooks แล้วฉันกำลังอัปเดตคำตอบเพื่อเพิ่มการใช้งานuseRefและuseEffecthooks ของจริงที่ใช้เวทมนตร์ (React refs และscrollIntoViewDOM method) ยังคงเหมือนเดิม:

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 lifecycle ดังนั้นเราควรตรวจสอบ ref this.messagesEnd.current ว่ามีอยู่หรือไม่ในฟังก์ชัน scrollToBottom หาก this.messagesEnd.current ไม่มีอยู่ข้อความแสดงข้อผิดพลาดจะแสดง TypeError: ไม่สามารถอ่านคุณสมบัติ 'scrollIntoView' ของ null ดังนั้นเพิ่มสิ่งนี้ if condition ด้วย 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ก่อนการเรนเดอร์ครั้งแรกจะทำให้เกิดข้อผิดพลาดที่คุณชี้ ขอบคุณ
Diego Lara

สิ่งที่อยู่this.messagesEndในตัวอย่างแรกของคุณในวิธีการ scrollTo?
dcsan

@dcsan เป็น React ref ซึ่งใช้ในการติดตามองค์ประกอบ DOM แม้หลังจาก renderers reactjs.org/docs/refs-and-the-dom.html#creating-refs
Diego Lara

1
โค้ดตัวอย่างที่สองไม่ทำงาน วิธีการจะต้องมีการวางไว้กับuseEffect () => {scrollToBottom()}ขอบคุณมากอย่างไรก็ตาม
Gaspar

37

ไม่ได้ใช้ 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
one stevy boi

2
@steviekins เนื่องจาก "มันบล็อกการปรับปรุงบางอย่างใน React" และมีแนวโน้มที่จะเลิกใช้งานgithub.com/yannickcr/eslint-plugin-react/issues/…
tgdn

2
ควรสะกดแบบอเมริกันbehavior(แก้ไขไม่ได้เพราะ "แก้ไขต้องมีอย่างน้อย 6 ตัวอักษร" ถอนหายใจ)
Joe Freeman

1
การสนับสนุนscrollIntoViewด้วยsmoothนั้นแย่มากในขณะนี้
Andreykul

@Andreykul ดูเหมือนว่าฉันจะเห็นผลลัพธ์ที่คล้ายกันกับการใช้ 'smooth' มันไม่สอดคล้องกัน
flimflam57

18

ขอบคุณ @enl

เราควรหลีกเลี่ยงการใช้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

@Patric หากคุณสามารถเปิดปัญหาบน GitHubพร้อมรายละเอียดบางอย่างเกี่ยวกับการตั้งค่าของคุณบางทีเราอาจจะเข้าใจว่ามีอะไรผิดพลาด
Gabriel Bourgault

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. ทำให้เกิดวิธีการดังกล่าวข้างต้นในและcomponentDidMountcomponentDidUpdate

    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 Hook ถูกใช้ที่นี่

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


โครงสร้าง 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


ควรประกาศอ้างอิงในข้อความ divs ไม่ใช่ในคอนเทนเนอร์
toing_toing

4

ฉันไม่สามารถรับคำตอบใด ๆ ด้านล่างสำหรับการทำงาน แต่ js ง่ายๆทำเคล็ดลับให้ฉัน:

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

3

ตัวอย่างการทำงาน:

คุณสามารถใช้scrollIntoViewวิธีDOM เพื่อทำให้ส่วนประกอบมองเห็นได้ในมุมมอง

สำหรับสิ่งนี้ในขณะที่แสดงผลคอมโพเนนต์ให้ระบุ ID อ้างอิงสำหรับองค์ประกอบ 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


Sherin bhai ที่น่าอัศจรรย์มันทำงานเหมือนเค้กขอบคุณ
Mohammed Sarfaraz

@MohammedSarfaraz ดีใจที่ช่วยได้ :)
Sherin Jose

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

สำหรับข้อมูลเพิ่มเติมเกี่ยวกับการใช้โทษกับตอบสนองและ 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 กรุณา? ฉันใช้
1.16.0
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.