From fefcd7f9044176c48005754733d94b571a5b1120 Mon Sep 17 00:00:00 2001 From: felix-hoc Date: Wed, 18 Dec 2024 16:43:03 +0100 Subject: [PATCH] Limit thumbnail preview to UI bounds --- src/ts/components/seekbar.ts | 23 ++++++++++++++++++++--- src/ts/components/seekbarlabel.ts | 28 ++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 3 deletions(-) diff --git a/src/ts/components/seekbar.ts b/src/ts/components/seekbar.ts index 8227e0a34..203387653 100644 --- a/src/ts/components/seekbar.ts +++ b/src/ts/components/seekbar.ts @@ -114,7 +114,10 @@ export class SeekBar extends Component { private seekBarMarkersContainer: DOM; private timelineMarkersHandler: TimelineMarkersHandler; + private uiBoundingRect: DOMRect; + private player: PlayerAPI; + private uiManager: UIInstanceManager; protected seekBarType: SeekBarType; @@ -220,6 +223,7 @@ export class SeekBar extends Component { super.configure(player, uimanager); this.player = player; + this.uiManager = uimanager; // Apply scaling transform to the backdrop bar to have all bars rendered similarly // (the call must be up here to be executed for the volume slider as well) @@ -453,6 +457,7 @@ export class SeekBar extends Component { // is positioned absolutely and must therefore be updated when the size of the seekbar changes. player.on(player.exports.PlayerEvent.PlayerResized, () => { this.refreshPlaybackPosition(); + this.uiBoundingRect = this.uiManager.getUI().getDomElement().get(0).getBoundingClientRect(); }); // Additionally, when this code is called, the seekbar is not part of the UI yet and therefore does not have a size, // resulting in a wrong initial position of the marker. Refreshing it once the UI is configured solved this issue. @@ -1020,6 +1025,20 @@ export class SeekBar extends Component { this.seekBarEvents.onSeek.dispatch(this); } + private updateLabelPosition = (seekPositionPercentage: number) => { + const labelDomElement = this.label.getDomElement(); + labelDomElement.css({ + 'left': seekPositionPercentage + '%', + 'transform': null, + }); + + // TODO: Re-test `requestAnimationFrame` to prevent forced synchronous layout calculation + if (!this.uiBoundingRect) { + this.uiBoundingRect = this.uiManager.getUI().getDomElement().get(0).getBoundingClientRect(); + } + this.label.ensureWithinBounds(this.uiBoundingRect); + }; + protected onSeekPreviewEvent(percentage: number, scrubbing: boolean) { let snappedMarker = this.timelineMarkersHandler && this.timelineMarkersHandler.getMarkerAtPosition(percentage); @@ -1043,9 +1062,7 @@ export class SeekBar extends Component { } if (this.label) { - this.label.getDomElement().css({ - 'left': seekPositionPercentage + '%', - }); + this.updateLabelPosition(seekPositionPercentage); } this.seekBarEvents.onSeekPreview.dispatch(this, { diff --git a/src/ts/components/seekbarlabel.ts b/src/ts/components/seekbarlabel.ts index 4673810c0..8c652bdb2 100644 --- a/src/ts/components/seekbarlabel.ts +++ b/src/ts/components/seekbarlabel.ts @@ -136,6 +136,34 @@ export class SeekBarLabel extends Container { } }; + public ensureWithinBounds = (bounds: DOMRect) => { + // TODO move into CSS + const overflowMargin = 8; + + const labelBounding = this.container.getDomElement().get(0).getBoundingClientRect(); + + let preventOverflowOffset = 0; + if (labelBounding.right + overflowMargin > bounds.right) { + preventOverflowOffset = labelBounding.right - bounds.right + overflowMargin; + } else if (labelBounding.left - overflowMargin < bounds.left) { + preventOverflowOffset = labelBounding.left - bounds.left - overflowMargin; + } + + if (preventOverflowOffset !== 0) { + this.getDomElement().css({ + transform: `translateX(${-preventOverflowOffset}px)`, + }); + + this.caret.getDomElement().css({ + transform: `translateX(${preventOverflowOffset}px)`, + }); + } else { + this.caret.getDomElement().css({ + transform: `translateX(${0}px)`, + }); + } + } + /** * Sets arbitrary text on the label. * @param text the text to show on the label