Fix livestream autoscroll
## Ticket 6886 Livestream auto-scroll problems ## Issue - `performedInitialScroll` was problematic as it won't allow auto-scroll even when user scrolled back to the bottom. - The dependence on `commentElement` seems to assume a certain comment height? Not sure. ## Approach - Add a scroll listener and stash the last scroll position. - When a message is received, check if it's at the bottom. If yes, maintain that position after the new comment is added. If not, leave as is. - When submitting a comment, always reset to the bottom.
This commit is contained in:
parent
05d358ac0b
commit
e1fc5fd6e3
1 changed files with 38 additions and 40 deletions
|
@ -28,7 +28,6 @@ type Props = {
|
||||||
|
|
||||||
const VIEW_MODE_CHAT = 'view_chat';
|
const VIEW_MODE_CHAT = 'view_chat';
|
||||||
const VIEW_MODE_SUPER_CHAT = 'view_superchat';
|
const VIEW_MODE_SUPER_CHAT = 'view_superchat';
|
||||||
const COMMENT_SCROLL_OFFSET = 100;
|
|
||||||
const COMMENT_SCROLL_TIMEOUT = 25;
|
const COMMENT_SCROLL_TIMEOUT = 25;
|
||||||
|
|
||||||
export default function LivestreamComments(props: Props) {
|
export default function LivestreamComments(props: Props) {
|
||||||
|
@ -50,15 +49,13 @@ export default function LivestreamComments(props: Props) {
|
||||||
let superChatsFiatAmount, superChatsTotalAmount;
|
let superChatsFiatAmount, superChatsTotalAmount;
|
||||||
|
|
||||||
const commentsRef = React.createRef();
|
const commentsRef = React.createRef();
|
||||||
const [scrollBottom, setScrollBottom] = React.useState(true);
|
|
||||||
const [viewMode, setViewMode] = React.useState(VIEW_MODE_CHAT);
|
const [viewMode, setViewMode] = React.useState(VIEW_MODE_CHAT);
|
||||||
const [performedInitialScroll, setPerformedInitialScroll] = React.useState(false);
|
const [scrollPos, setScrollPos] = React.useState(0);
|
||||||
const claimId = claim && claim.claim_id;
|
const claimId = claim && claim.claim_id;
|
||||||
const commentsLength = commentsByChronologicalOrder && commentsByChronologicalOrder.length;
|
const commentsLength = commentsByChronologicalOrder && commentsByChronologicalOrder.length;
|
||||||
const commentsToDisplay = viewMode === VIEW_MODE_CHAT ? commentsByChronologicalOrder : superChatsByTipAmount;
|
const commentsToDisplay = viewMode === VIEW_MODE_CHAT ? commentsByChronologicalOrder : superChatsByTipAmount;
|
||||||
|
|
||||||
const discussionElement = document.querySelector('.livestream__comments');
|
const discussionElement = document.querySelector('.livestream__comments');
|
||||||
const commentElement = document.querySelector('.livestream-comment');
|
|
||||||
|
|
||||||
let pinnedComment;
|
let pinnedComment;
|
||||||
const pinnedCommentIds = (claimId && pinnedCommentsById[claimId]) || [];
|
const pinnedCommentIds = (claimId && pinnedCommentsById[claimId]) || [];
|
||||||
|
@ -66,6 +63,12 @@ export default function LivestreamComments(props: Props) {
|
||||||
pinnedComment = commentsByChronologicalOrder.find((c) => c.comment_id === pinnedCommentIds[0]);
|
pinnedComment = commentsByChronologicalOrder.find((c) => c.comment_id === pinnedCommentIds[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function restoreScrollPos() {
|
||||||
|
if (discussionElement) {
|
||||||
|
discussionElement.scrollTop = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (claimId) {
|
if (claimId) {
|
||||||
doCommentList(uri, '', 1, 75);
|
doCommentList(uri, '', 1, 75);
|
||||||
|
@ -80,36 +83,39 @@ export default function LivestreamComments(props: Props) {
|
||||||
};
|
};
|
||||||
}, [claimId, uri, doCommentList, doSuperChatList, doCommentSocketConnect, doCommentSocketDisconnect]);
|
}, [claimId, uri, doCommentList, doSuperChatList, doCommentSocketConnect, doCommentSocketDisconnect]);
|
||||||
|
|
||||||
const handleScroll = React.useCallback(() => {
|
// Register scroll handler (TODO: Should throttle/debounce)
|
||||||
if (discussionElement) {
|
|
||||||
const negativeCommentHeight = commentElement && -1 * commentElement.offsetHeight;
|
|
||||||
const isAtRecent = negativeCommentHeight && discussionElement.scrollTop >= negativeCommentHeight;
|
|
||||||
|
|
||||||
setScrollBottom(isAtRecent);
|
|
||||||
}
|
|
||||||
}, [commentElement, discussionElement]);
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
if (discussionElement) {
|
function handleScroll() {
|
||||||
discussionElement.addEventListener('scroll', handleScroll);
|
if (discussionElement) {
|
||||||
|
const scrollTop = discussionElement.scrollTop;
|
||||||
if (commentsLength > 0) {
|
if (scrollTop !== scrollPos) {
|
||||||
// Only update comment scroll if the user hasn't scrolled up to view old comments
|
setScrollPos(scrollTop);
|
||||||
// If they have, do nothing
|
|
||||||
if (!performedInitialScroll) {
|
|
||||||
setTimeout(
|
|
||||||
() =>
|
|
||||||
(discussionElement.scrollTop =
|
|
||||||
discussionElement.scrollHeight - discussionElement.offsetHeight + COMMENT_SCROLL_OFFSET),
|
|
||||||
COMMENT_SCROLL_TIMEOUT
|
|
||||||
);
|
|
||||||
setPerformedInitialScroll(true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (discussionElement) {
|
||||||
|
discussionElement.addEventListener('scroll', handleScroll);
|
||||||
return () => discussionElement.removeEventListener('scroll', handleScroll);
|
return () => discussionElement.removeEventListener('scroll', handleScroll);
|
||||||
}
|
}
|
||||||
}, [commentsLength, discussionElement, handleScroll, performedInitialScroll, setPerformedInitialScroll]);
|
}, [discussionElement, scrollPos]);
|
||||||
|
|
||||||
|
// Retain scrollPos=0 when receiving new messages.
|
||||||
|
React.useEffect(() => {
|
||||||
|
if (discussionElement && commentsLength > 0) {
|
||||||
|
// Only update comment scroll if the user hasn't scrolled up to view old comments
|
||||||
|
if (scrollPos >= 0) {
|
||||||
|
// +ve scrollPos: not scrolled (Usually, there'll be a few pixels beyond 0).
|
||||||
|
// -ve scrollPos: user scrolled.
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
// Use a timer here to ensure we reset after the new comment has been rendered.
|
||||||
|
discussionElement.scrollTop = 0;
|
||||||
|
}, COMMENT_SCROLL_TIMEOUT);
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [commentsLength]); // (Just respond to 'commentsLength' updates and nothing else)
|
||||||
|
|
||||||
// sum total amounts for fiat tips and lbc tips
|
// sum total amounts for fiat tips and lbc tips
|
||||||
if (superChatsByTipAmount) {
|
if (superChatsByTipAmount) {
|
||||||
|
@ -162,13 +168,6 @@ export default function LivestreamComments(props: Props) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function scrollBack() {
|
|
||||||
if (discussionElement) {
|
|
||||||
discussionElement.scrollTop = 0;
|
|
||||||
setScrollBottom(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="card livestream__discussion">
|
<div className="card livestream__discussion">
|
||||||
<div className="card__header--between livestream-discussion__header">
|
<div className="card__header--between livestream-discussion__header">
|
||||||
|
@ -184,8 +183,7 @@ export default function LivestreamComments(props: Props) {
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setViewMode(VIEW_MODE_CHAT);
|
setViewMode(VIEW_MODE_CHAT);
|
||||||
const livestreamCommentsDiv = document.getElementsByClassName('livestream__comments')[0];
|
const livestreamCommentsDiv = document.getElementsByClassName('livestream__comments')[0];
|
||||||
const divHeight = livestreamCommentsDiv.scrollHeight;
|
livestreamCommentsDiv.scrollTop = livestreamCommentsDiv.scrollHeight;
|
||||||
livestreamCommentsDiv.scrollTop = divHeight;
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
@ -301,17 +299,17 @@ export default function LivestreamComments(props: Props) {
|
||||||
<div className="main--empty" style={{ flex: 1 }} />
|
<div className="main--empty" style={{ flex: 1 }} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{!scrollBottom && (
|
{scrollPos < 0 && (
|
||||||
<Button
|
<Button
|
||||||
button="alt"
|
button="alt"
|
||||||
className="livestream__comments-scroll__down"
|
className="livestream__comments-scroll__down"
|
||||||
label={__('Recent Comments')}
|
label={__('Recent Comments')}
|
||||||
onClick={scrollBack}
|
onClick={restoreScrollPos}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="livestream__comment-create">
|
<div className="livestream__comment-create">
|
||||||
<CommentCreate livestream bottom embed={embed} uri={uri} />
|
<CommentCreate livestream bottom embed={embed} uri={uri} onDoneReplying={restoreScrollPos} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|
Loading…
Reference in a new issue