UX: Prevent initial jump when dragging topic timeline scroller (#23291)

Previously, when dragging the timeline scroller, we would position it purely based on the current cursor position. That means that if you started dragging it from anywhere other than the centre, the scroller will 'jump' suddenly to centre itself on the cursor.

This commit measures the offset of your cursor when the drag starts, and maintains it throughout the drag. So for example, if you start dragging at the bottom of the scroller and drag one pixel up, the scroller will only move by 1px.

This should make the UX much smoother, especially on large topics.
This commit is contained in:
David Taylor 2023-08-28 15:00:32 +01:00 committed by GitHub
parent 6f1d666323
commit 81d8c6ba6c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 43 additions and 7 deletions

View File

@ -73,7 +73,11 @@
</span> </span>
</a> </a>
</div> </div>
<div class="timeline-scrollarea" style={{this.timelineScrollareaStyle}}> <div
class="timeline-scrollarea"
style={{this.timelineScrollareaStyle}}
{{did-insert this.registerScrollarea}}
>
<div <div
class="timeline-padding" class="timeline-padding"
style={{this.beforePadding}} style={{this.beforePadding}}
@ -89,6 +93,7 @@
@didStartDrag={{this.didStartDrag}} @didStartDrag={{this.didStartDrag}}
@dragMove={{this.dragMove}} @dragMove={{this.dragMove}}
@didEndDrag={{this.didEndDrag}} @didEndDrag={{this.didEndDrag}}
{{did-insert this.registerScroller}}
/> />
<div <div
class="timeline-padding" class="timeline-padding"

View File

@ -37,6 +37,9 @@ export default class TopicTimelineScrollArea extends Component {
@tracked excerpt = ""; @tracked excerpt = "";
intersectionObserver = null; intersectionObserver = null;
scrollareaElement = null;
scrollerElement = null;
dragOffset = null;
constructor() { constructor() {
super(...arguments); super(...arguments);
@ -272,17 +275,33 @@ export default class TopicTimelineScrollArea extends Component {
@bind @bind
updatePercentage(e) { updatePercentage(e) {
// pageY for mouse and mobile const currentCursorY = e.pageY || e.touches[0].pageY;
const y = e.pageY || e.touches[0].pageY;
const area = document.querySelector(".timeline-scrollarea");
const areaTop = domUtils.offset(area).top;
this.percentage = this.clamp(parseFloat(y - areaTop) / area.offsetHeight); const desiredScrollerCentre = currentCursorY - this.dragOffset;
const areaTop = domUtils.offset(this.scrollareaElement).top;
const areaHeight = this.scrollareaElement.offsetHeight;
const scrollerHeight = this.scrollerElement.offsetHeight;
// The range of possible positions for the centre of the scroller
const scrollableTop = areaTop + scrollerHeight / 2;
const scrollableHeight = areaHeight - scrollerHeight;
this.percentage = this.clamp(
parseFloat(desiredScrollerCentre - scrollableTop) / scrollableHeight
);
this.commit(); this.commit();
} }
@bind @bind
didStartDrag() { didStartDrag(event) {
const y = event.pageY || event.touches[0].pageY;
const scrollerCentre =
domUtils.offset(this.scrollerElement).top +
this.scrollerElement.offsetHeight / 2;
this.dragOffset = y - scrollerCentre;
this.dragging = true; this.dragging = true;
} }
@ -296,6 +315,7 @@ export default class TopicTimelineScrollArea extends Component {
@bind @bind
didEndDrag() { didEndDrag() {
this.dragging = false; this.dragging = false;
this.dragOffset = null;
this.commit(); this.commit();
} }
@ -393,6 +413,16 @@ export default class TopicTimelineScrollArea extends Component {
return this.clamp(parseFloat(postIndex) / total); return this.clamp(parseFloat(postIndex) / total);
} }
} }
@action
registerScrollarea(element) {
this.scrollareaElement = element;
}
@action
registerScroller(element) {
this.scrollerElement = element;
}
} }
export function scrollareaHeight() { export function scrollareaHeight() {

View File

@ -6,6 +6,7 @@
didEndDrag=@didEndDrag didEndDrag=@didEndDrag
dragMove=@dragMove dragMove=@dragMove
}} }}
...attributes
> >
{{#if @fullscreen}} {{#if @fullscreen}}
<div class="timeline-scroller-content"> <div class="timeline-scroller-content">