Merge pull request #3867 from oswaldoacauan/chat-performance-improvement
[HTML5] Chat performance improvement
This commit is contained in:
commit
8363d76f98
@ -107,9 +107,8 @@ export default injectIntl(createContainer(({ params, intl }) => {
|
|||||||
handleClosePrivateChat: chatID => ChatService.closePrivateChat(chatID),
|
handleClosePrivateChat: chatID => ChatService.closePrivateChat(chatID),
|
||||||
|
|
||||||
handleSendMessage: message => {
|
handleSendMessage: message => {
|
||||||
|
ChatService.updateScrollPosition(chatID, null);
|
||||||
let sentMessage = ChatService.sendMessage(chatID, message);
|
let sentMessage = ChatService.sendMessage(chatID, message);
|
||||||
ChatService.updateScrollPosition(chatID, null); //null so its scrolls to bottom
|
|
||||||
// ChatService.updateUnreadMessage(chatID, sentMessage.from_time);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
handleScrollUpdate: position => ChatService.updateScrollPosition(chatID, position),
|
handleScrollUpdate: position => ChatService.updateScrollPosition(chatID, position),
|
||||||
|
@ -18,7 +18,7 @@ const intlMessages = defineMessages({
|
|||||||
emptyLogLabel: {
|
emptyLogLabel: {
|
||||||
id: 'app.chat.emptyLogLabel',
|
id: 'app.chat.emptyLogLabel',
|
||||||
description: 'aria-label used when chat log is empty',
|
description: 'aria-label used when chat log is empty',
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
class MessageList extends Component {
|
class MessageList extends Component {
|
||||||
@ -73,7 +73,6 @@ class MessageList extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
componentWillUpdate(nextProps) {
|
componentWillUpdate(nextProps) {
|
||||||
|
|
||||||
if (this.props.chatId !== nextProps.chatId) {
|
if (this.props.chatId !== nextProps.chatId) {
|
||||||
this.shouldScrollBottom = false;
|
this.shouldScrollBottom = false;
|
||||||
return;
|
return;
|
||||||
@ -86,7 +85,8 @@ class MessageList extends Component {
|
|||||||
//Compare with <1 to account for the chance scrollArea.scrollTop is a float
|
//Compare with <1 to account for the chance scrollArea.scrollTop is a float
|
||||||
//value in some browsers.
|
//value in some browsers.
|
||||||
this.shouldScrollBottom = position === scrollArea.scrollHeight ||
|
this.shouldScrollBottom = position === scrollArea.scrollHeight ||
|
||||||
(scrollArea.scrollHeight - position < 1);
|
(scrollArea.scrollHeight - position < 1) ||
|
||||||
|
nextProps.scrollPosition === null;
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate(prevProps) {
|
componentDidUpdate(prevProps) {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import React, { Component, PropTypes } from 'react';
|
import React, { Component, PropTypes } from 'react';
|
||||||
import { FormattedTime } from 'react-intl';
|
import { FormattedTime } from 'react-intl';
|
||||||
import cx from 'classnames';
|
import cx from 'classnames';
|
||||||
|
import _ from 'lodash';
|
||||||
|
|
||||||
import UserAvatar from '/imports/ui/components/user-avatar/component';
|
import UserAvatar from '/imports/ui/components/user-avatar/component';
|
||||||
import Message from './message/component';
|
import Message from './message/component';
|
||||||
@ -16,10 +17,74 @@ const propTypes = {
|
|||||||
const defaultProps = {
|
const defaultProps = {
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const eventsToBeBound = [
|
||||||
|
'scroll',
|
||||||
|
'resize',
|
||||||
|
];
|
||||||
|
|
||||||
|
const isElementInViewport = (el) => {
|
||||||
|
const rect = el.getBoundingClientRect();
|
||||||
|
const clientHeight = window.innerHeight || document.documentElement.clientHeight;
|
||||||
|
const prefetchHeight = 125;
|
||||||
|
|
||||||
|
return (
|
||||||
|
rect.top >= -(prefetchHeight) &&
|
||||||
|
rect.bottom <= clientHeight + prefetchHeight
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default class MessageListItem extends Component {
|
export default class MessageListItem extends Component {
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
pendingChanges: false,
|
||||||
|
preventRender: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.handleMessageInViewport = _.debounce(this.handleMessageInViewport.bind(this), 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMessageInViewport() {
|
||||||
|
window.requestAnimationFrame(() => {
|
||||||
|
const node = this.refs.item;
|
||||||
|
this.setState({ preventRender: !isElementInViewport(node) });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
const scrollArea = document.getElementById(this.props.chatAreaId);
|
||||||
|
eventsToBeBound.forEach(
|
||||||
|
e => scrollArea.addEventListener(e, this.handleMessageInViewport, false)
|
||||||
|
);
|
||||||
|
|
||||||
|
this.handleMessageInViewport();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillUnmount() {
|
||||||
|
const scrollArea = document.getElementById(this.props.chatAreaId);
|
||||||
|
eventsToBeBound.forEach(
|
||||||
|
e => scrollArea.removeEventListener(e, this.handleMessageInViewport, false)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps, prevState) {
|
||||||
|
if (prevState.preventRender && !this.state.preventRender && this.state.pendingChanges) {
|
||||||
|
this.setState({ pendingChanges: false });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
componentWillReceiveProps(nextProps) {
|
||||||
|
if (this.state.pendingChanges) return;
|
||||||
|
|
||||||
|
const hasNewMessage = this.props.messages.length !== nextProps.messages.length;
|
||||||
|
const hasUserChanged = !_.isEqual(this.props.user, nextProps.user);
|
||||||
|
|
||||||
|
this.setState({ pendingChanges: hasNewMessage || hasUserChanged });
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldComponentUpdate(nextProps, nextState) {
|
||||||
|
return !nextState.preventRender && nextState.pendingChanges;
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@ -36,32 +101,34 @@ export default class MessageListItem extends Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.item}>
|
<div className={styles.item}>
|
||||||
<div className={styles.avatar}>
|
<div className={styles.wrapper} ref="item">
|
||||||
<UserAvatar user={user} />
|
<div className={styles.avatar}>
|
||||||
</div>
|
<UserAvatar user={user} />
|
||||||
<div className={styles.content}>
|
|
||||||
<div className={styles.meta}>
|
|
||||||
<div className={user.isOnline ? styles.name : styles.logout} aria-label={user.name}>
|
|
||||||
<span>{user.name}</span>
|
|
||||||
{user.isOnline ? null : <span className={styles.offline}>(offline)</span>}
|
|
||||||
</div>
|
|
||||||
<time className={styles.time} dateTime={dateTime}>
|
|
||||||
<FormattedTime value={dateTime}/>
|
|
||||||
</time>
|
|
||||||
</div>
|
</div>
|
||||||
<div className={styles.messages}>
|
<div className={styles.content}>
|
||||||
{messages.map((message, i) => (
|
<div className={styles.meta}>
|
||||||
<Message
|
<div className={!user.isOnline ? styles.name : styles.logout}>
|
||||||
className={styles.message}
|
<span>{user.name}</span>
|
||||||
key={message.id}
|
{user.isOnline ? null : <span className={styles.offline}>(offline)</span>}
|
||||||
text={message.text}
|
</div>
|
||||||
time={message.time}
|
<time className={styles.time} dateTime={dateTime}>
|
||||||
chatAreaId={this.props.chatAreaId}
|
<FormattedTime value={dateTime}/>
|
||||||
lastReadMessageTime={this.props.lastReadMessageTime}
|
</time>
|
||||||
handleReadMessage={this.props.handleReadMessage}
|
</div>
|
||||||
/>
|
<div className={styles.messages}>
|
||||||
))}
|
{messages.map((message, i) => (
|
||||||
|
<Message
|
||||||
|
className={styles.message}
|
||||||
|
key={message.id}
|
||||||
|
text={message.text}
|
||||||
|
time={message.time}
|
||||||
|
chatAreaId={this.props.chatAreaId}
|
||||||
|
lastReadMessageTime={this.props.lastReadMessageTime}
|
||||||
|
handleReadMessage={this.props.handleReadMessage}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -75,7 +142,7 @@ export default class MessageListItem extends Component {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cx(styles.item, styles.systemMessage)}>
|
<div className={cx(styles.item, styles.systemMessage)}>
|
||||||
<div className={styles.content}>
|
<div className={styles.content} ref="item">
|
||||||
<div className={styles.messages}>
|
<div className={styles.messages}>
|
||||||
{messages.map((message, i) => (
|
{messages.map((message, i) => (
|
||||||
<Message
|
<Message
|
||||||
|
@ -1,17 +1,20 @@
|
|||||||
@import "../../../../stylesheets/variables/_all";
|
@import "../../../../stylesheets/variables/_all";
|
||||||
|
|
||||||
.item {
|
.item {
|
||||||
display: flex;
|
|
||||||
flex-flow: row;
|
|
||||||
flex: 1;
|
|
||||||
margin-bottom: $line-height-computed;
|
|
||||||
font-size: $font-size-base * .90;
|
font-size: $font-size-base * .90;
|
||||||
|
margin-bottom: $line-height-computed;
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
margin-bottom: 0 !important;
|
margin-bottom: 0 !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.systemMessage {
|
.systemMessage {
|
||||||
|
|
||||||
.item + &,
|
.item + &,
|
||||||
|
Loading…
Reference in New Issue
Block a user