Odpowiedzi:
Jak wspomniał Tushar, na dole czatu możesz umieścić fikcyjny element 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>
);
}
a następnie przewiń do niego przy każdej aktualizacji komponentu (tj. stan aktualizowany po dodaniu nowych wiadomości):
scrollToBottom = () => {
this.messagesEnd.scrollIntoView({ behavior: "smooth" });
}
componentDidMount() {
this.scrollToBottom();
}
componentDidUpdate() {
this.scrollToBottom();
}
Używam tutaj standardowej metody Element.scrollIntoView .
this.messagesEnd.scrollIntoView()działało dobrze dla mnie. Nie było potrzeby używania findDOMNode().
scrollToBottom(){this.scrollBottom.scrollIntoView({ behavior: 'smooth' })}aby działała w nowszych wersjach
Chcę tylko zaktualizować odpowiedź, aby pasowała do nowej React.createRef() metody , ale w zasadzie jest taka sama, pamiętaj tylko o currentwłaściwości w utworzonym 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>
)
}
}
AKTUALIZACJA:
Teraz, gdy hooki są dostępne, aktualizuję odpowiedź, dodając użycie haków useRefi useEffect, prawdziwa rzecz, która robi magię (refs React i scrollIntoViewmetoda DOM) pozostaje taka sama:
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>
)
}
Stworzyłem również (bardzo podstawowe) pole kodów, jeśli chcesz sprawdzić zachowanie https://codesandbox.io/s/scrolltobottomexample-f90lz
this.messagesEnd.currentzawsze istnieje. Niemniej jednak ważne jest, aby zauważyć, że wywołanie this.messagesEnd.currentprzed pierwszym renderowaniem spowoduje wskazany błąd. Thnx.
this.messagesEndw Twoim pierwszym przykładzie w metodzie scrollTo?
useEffectSposób muszą być umieszczone z () => {scrollToBottom()}. W każdym razie bardzo dziękuję
Nie używać 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} />;
}
behavior(nie można edytować, ponieważ „edycja musi mieć co najmniej 6 znaków”, westchnij).
scrollIntoViewwith smoothjest obecnie bardzo słabe.
Dzięki @enlitement
powinniśmy unikać używania findDOMNode, możemy użyć refsdo śledzenia komponentów
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();
}
odniesienie:
Możesz użyć refs do śledzenia komponentów.
Jeśli wiesz, jak ustawić refpojedynczy komponent (ostatni), napisz!
Oto, co okazało się dla mnie skuteczne:
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();
}
}
}
reakcja-przewijalne-źródło automatycznie przewija w dół do ostatniego elementu, jeśli użytkownik był już na dole przewijalnej sekcji. W przeciwnym razie pozostawi użytkownika w tej samej pozycji. Myślę, że jest to całkiem przydatne w przypadku komponentów czatu :)
Myślę, że inne odpowiedzi tutaj wymuszą przewijanie za każdym razem, bez względu na to, gdzie był pasek przewijania. Innym problemem scrollIntoViewjest to, że przewija całą stronę, jeśli nie widać przewijalnego elementu div.
Można go używać w następujący sposób:
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>
);
}
}
Po prostu upewnij się, że masz komponent opakowania z określonym heightlubmax-height
Zastrzeżenie: jestem właścicielem pakietu
Odwołaj się do kontenera wiadomości.
<div ref={(el) => { this.messagesContainer = el; }}> YOUR MESSAGES </div>Znajdź kontener wiadomości i wyrównaj jego scrollTopatrybut scrollHeight:
scrollToBottom = () => {
const messagesContainer = ReactDOM.findDOMNode(this.messagesContainer);
messagesContainer.scrollTop = messagesContainer.scrollHeight;
};Wywołaj powyższą metodę na componentDidMounti componentDidUpdate.
componentDidMount() {
this.scrollToBottom();
}
componentDidUpdate() {
this.scrollToBottom();
}Oto jak używam tego w moim kodzie:
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>
);
}
}
Utworzyłem pusty element na końcu wiadomości i przewinąłem do tego elementu. Nie ma potrzeby śledzenia referencji.
Jeśli chcesz to zrobić z React hookami, możesz zastosować tę metodę. Fikcyjny div został umieszczony na dole czatu. useRef Hook jest używany tutaj.
Dokumentacja interfejsu API hooków: 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>
);
}
Moja wersja ReactJS: 16.12.0
Struktura HTML wewnątrz render()funkcji
render()
return(
<body>
<div ref="messageList">
<div>Message 1</div>
<div>Message 2</div>
<div>Message 3</div>
</div>
</body>
)
)
scrollToBottom()funkcja, która uzyska odniesienie do elementu. i przewijaj zgodnie z scrollIntoView()funkcją.
scrollToBottom = () => {
const { messageList } = this.refs;
messageList.scrollIntoView({behavior: "smooth", block: "end", inline: "nearest"});
}
i wywołaj powyższą funkcję wewnątrz componentDidMount()icomponentDidUpdate()
aby uzyskać więcej informacji na temat Element.scrollIntoView()odwiedź developer.mozilla.org
Przykład roboczy:
Możesz użyć scrollIntoViewmetody DOM , aby wyświetlić komponent w widoku.
W tym celu podczas renderowania komponentu po prostu podaj identyfikator odniesienia dla elementu DOM za pomocą refatrybutu. Następnie użyć metody scrollIntoVieww componentDidMountcyklu życia. Właśnie umieszczam działający przykładowy kod tego rozwiązania. Poniżej przedstawiono komponent renderujący za każdym razem, gdy otrzymana wiadomość. Powinieneś napisać kod / metody renderowania tego komponentu.
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>
);
}
}
Oto this.props.message.MessageIdunikalny identyfikator konkretnej wiadomości czatu przekazanej jakoprops
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>
);
}
}
dziękuję „metakermit” za jego dobrą odpowiedź, ale myślę, że możemy to trochę ulepszyć, aby przewinąć do dołu, powinniśmy użyć tego:
scrollToBottom = () => {
this.messagesEnd.scrollIntoView({ behavior: "smooth", block: "end", inline: "nearest" });
}
ale jeśli chcesz przewijać do góry, użyj tego:
scrollToTop = () => {
this.messagesEnd.scrollIntoView({ behavior: "smooth", block: "start", inline: "nearest" });
}
i te kody są wspólne:
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>
);
}
Jako kolejną opcję warto przyjrzeć się komponentowi React scroll .
Za pomocą 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>
)
}
}
Oto jak można to rozwiązać w TypeScript (używając ref do docelowego elementu, do którego przewijasz):
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>
}
Aby uzyskać więcej informacji na temat używania ref z React i Typescript, możesz znaleźć świetny artykuł tutaj .
Pełna wersja (maszynopis):
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>'. a Type 'HTMLDivElement | null' is not assignable to type 'RefObject<unknown>'. Type 'null' is not assignable to type 'RefObject<unknown>'. więc ...