Fix: Remove jittery timeline scrolling after jumping to an event (#8263)
* Fix: Remove jittery timeline scrolling after jumping to an event * Fix: Remove onUserScroll handler and merge it with onScroll * Fix: Reset scrollIntoView state earlier Co-authored-by: Janne Mareike Koschinski <jannemk@element.io>
This commit is contained in:
parent
285dc25b3e
commit
579a166113
11 changed files with 118 additions and 87 deletions
|
@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
|
|||
limitations under the License.
|
||||
*/
|
||||
|
||||
import React, { createRef, ReactNode, SyntheticEvent } from 'react';
|
||||
import React, { createRef, ReactNode } from 'react';
|
||||
import ReactDOM from "react-dom";
|
||||
import { NotificationCountType, Room, RoomEvent } from "matrix-js-sdk/src/models/room";
|
||||
import { MatrixEvent, MatrixEventEvent } from "matrix-js-sdk/src/models/event";
|
||||
|
@ -91,6 +91,9 @@ interface IProps {
|
|||
// id of an event to jump to. If not given, will go to the end of the live timeline.
|
||||
eventId?: string;
|
||||
|
||||
// whether we should scroll the event into view
|
||||
eventScrollIntoView?: boolean;
|
||||
|
||||
// where to position the event given by eventId, in pixels from the bottom of the viewport.
|
||||
// If not given, will try to put the event half way down the viewport.
|
||||
eventPixelOffset?: number;
|
||||
|
@ -124,8 +127,7 @@ interface IProps {
|
|||
// callback which is called when the panel is scrolled.
|
||||
onScroll?(event: Event): void;
|
||||
|
||||
// callback which is called when the user interacts with the room timeline
|
||||
onUserScroll?(event: SyntheticEvent): void;
|
||||
onEventScrolledIntoView?(eventId?: string): void;
|
||||
|
||||
// callback which is called when the read-up-to mark is updated.
|
||||
onReadMarkerUpdated?(): void;
|
||||
|
@ -327,9 +329,11 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
|
||||
const differentEventId = newProps.eventId != this.props.eventId;
|
||||
const differentHighlightedEventId = newProps.highlightedEventId != this.props.highlightedEventId;
|
||||
if (differentEventId || differentHighlightedEventId) {
|
||||
logger.log("TimelinePanel switching to eventId " + newProps.eventId +
|
||||
" (was " + this.props.eventId + ")");
|
||||
const differentAvoidJump = newProps.eventScrollIntoView && !this.props.eventScrollIntoView;
|
||||
if (differentEventId || differentHighlightedEventId || differentAvoidJump) {
|
||||
logger.log("TimelinePanel switching to " +
|
||||
"eventId " + newProps.eventId + " (was " + this.props.eventId + "), " +
|
||||
"scrollIntoView: " + newProps.eventScrollIntoView + " (was " + this.props.eventScrollIntoView + ")");
|
||||
return this.initTimeline(newProps);
|
||||
}
|
||||
}
|
||||
|
@ -1123,7 +1127,41 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
offsetBase = 0.5;
|
||||
}
|
||||
|
||||
return this.loadTimeline(initialEvent, pixelOffset, offsetBase);
|
||||
return this.loadTimeline(initialEvent, pixelOffset, offsetBase, props.eventScrollIntoView);
|
||||
}
|
||||
|
||||
private scrollIntoView(eventId?: string, pixelOffset?: number, offsetBase?: number): void {
|
||||
const doScroll = () => {
|
||||
if (eventId) {
|
||||
debuglog("TimelinePanel scrolling to eventId " + eventId +
|
||||
" at position " + (offsetBase * 100) + "% + " + pixelOffset);
|
||||
this.messagePanel.current.scrollToEvent(
|
||||
eventId,
|
||||
pixelOffset,
|
||||
offsetBase,
|
||||
);
|
||||
} else {
|
||||
debuglog("TimelinePanel scrolling to bottom");
|
||||
this.messagePanel.current.scrollToBottom();
|
||||
}
|
||||
};
|
||||
|
||||
debuglog("TimelinePanel scheduling scroll to event");
|
||||
this.props.onEventScrolledIntoView?.(eventId);
|
||||
// Ensure the correct scroll position pre render, if the messages have already been loaded to DOM,
|
||||
// to avoid it jumping around
|
||||
doScroll();
|
||||
|
||||
// Ensure the correct scroll position post render for correct behaviour.
|
||||
//
|
||||
// requestAnimationFrame runs our code immediately after the DOM update but before the next repaint.
|
||||
//
|
||||
// If the messages have just been loaded for the first time, this ensures we'll repeat setting the
|
||||
// correct scroll position after React has re-rendered the TimelinePanel and MessagePanel and
|
||||
// updated the DOM.
|
||||
window.requestAnimationFrame(() => {
|
||||
doScroll();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1139,8 +1177,10 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
* @param {number?} offsetBase the reference point for the pixelOffset. 0
|
||||
* means the top of the container, 1 means the bottom, and fractional
|
||||
* values mean somewhere in the middle. If omitted, it defaults to 0.
|
||||
*
|
||||
* @param {boolean?} scrollIntoView whether to scroll the event into view.
|
||||
*/
|
||||
private loadTimeline(eventId?: string, pixelOffset?: number, offsetBase?: number): void {
|
||||
private loadTimeline(eventId?: string, pixelOffset?: number, offsetBase?: number, scrollIntoView = true): void {
|
||||
this.timelineWindow = new TimelineWindow(
|
||||
MatrixClientPeg.get(), this.props.timelineSet,
|
||||
{ windowLimit: this.props.timelineCap });
|
||||
|
@ -1176,32 +1216,9 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
return;
|
||||
}
|
||||
|
||||
const doScroll = () => {
|
||||
if (eventId) {
|
||||
debuglog("TimelinePanel scrolling to eventId " + eventId);
|
||||
this.messagePanel.current.scrollToEvent(
|
||||
eventId,
|
||||
pixelOffset,
|
||||
offsetBase,
|
||||
);
|
||||
} else {
|
||||
debuglog("TimelinePanel scrolling to bottom");
|
||||
this.messagePanel.current.scrollToBottom();
|
||||
}
|
||||
};
|
||||
|
||||
// Ensure the correct scroll position pre render, if the messages have already been loaded to DOM, to
|
||||
// avoid it jumping around
|
||||
doScroll();
|
||||
|
||||
// Ensure the correct scroll position post render for correct behaviour.
|
||||
//
|
||||
// requestAnimationFrame runs our code immediately after the DOM update but before the next repaint.
|
||||
//
|
||||
// If the messages have just been loaded for the first time, this ensures we'll repeat setting the
|
||||
// correct scroll position after React has re-rendered the TimelinePanel and MessagePanel and updated
|
||||
// the DOM.
|
||||
window.requestAnimationFrame(doScroll);
|
||||
if (scrollIntoView) {
|
||||
this.scrollIntoView(eventId, pixelOffset, offsetBase);
|
||||
}
|
||||
|
||||
if (this.props.sendReadReceiptOnLoad) {
|
||||
this.sendReadReceipt();
|
||||
|
@ -1651,7 +1668,6 @@ class TimelinePanel extends React.Component<IProps, IState> {
|
|||
ourUserId={MatrixClientPeg.get().credentials.userId}
|
||||
stickyBottom={stickyBottom}
|
||||
onScroll={this.onMessageListScroll}
|
||||
onUserScroll={this.props.onUserScroll}
|
||||
onFillRequest={this.onMessageListFillRequest}
|
||||
onUnfillRequest={this.onMessageListUnfillRequest}
|
||||
isTwelveHour={this.context?.showTwelveHourTimestamps ?? this.state.isTwelveHour}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue