diff --git a/extensions/amp-video-docking/0.1/amp-video-docking.js b/extensions/amp-video-docking/0.1/amp-video-docking.js index 35302af94432..024f6797c8fc 100644 --- a/extensions/amp-video-docking/0.1/amp-video-docking.js +++ b/extensions/amp-video-docking/0.1/amp-video-docking.js @@ -347,6 +347,12 @@ export class VideoDocking { /** @private {boolean} */ this.isDragging_ = false; + /** @private {number} */ + this.previousDragOffsetX_ = 0; + + /** @private {number} */ + this.dragVelocityX_ = 0; + /** @private {!Array} */ this.observed_ = []; @@ -1320,6 +1326,9 @@ export class VideoDocking { const {centerX} = this.getCenter_(offsetX, offsetY); const offsetRelativeX = this.calculateRelativeX_(centerX); + this.dragVelocityX_ = offsetX - this.previousDragOffsetX_; + this.previousDragOffsetX_ = offsetX; + this.placeAt_(video, x + offsetX, y + offsetY, scale, step, transitionDurationMs, offsetRelativeX); } @@ -1471,7 +1480,100 @@ export class VideoDocking { this.getControls_().enable(); - this.snap_(offset.x, offset.y); + if (Math.abs(this.dragVelocityX_) < 40) { + this.snap_(offset.x, offset.y); + } else { + this.flickToDismiss_(this.previousDragOffsetX_, + Math.sign(this.dragVelocityX_)); + } + + this.dragVelocityX_ = 0; + this.previousDragOffsetX_ = 0; + } + + /** + * @param {number} offsetX + * @param {number} direction -1 or 1 + * @private + */ + flickToDismiss_(offsetX, direction) { + devAssert(Math.abs(direction) == 1); + + const video = this.getDockedVideo_(); + + video.pause(); + + if (this.isVisible_(video.element, 0.2)) { + this.bounceToDismiss_(video, offsetX, direction); + return; + } + + const step = 1; + const {target} = devAssert(this.currentlyDocked_); + + const {x, y, width} = this.getTargetArea_(video, target); + const {scale} = this.getDims_(video, target, step); + + const currentX = x + offsetX; + const nextX = direction == 1 ? + this.getRightEdge_() : + this.getLeftEdge_() - width; + + const transitionDurationMs = + this.calculateDismissalTransitionDurationMs_(nextX - currentX); + + this.reconcileUndocked_(); + + // Show immediately due to Chrome freeze bug when out-of-view. + video.showControls(); + + this.placeAt_(video, nextX, y, scale, /* step */ 0, transitionDurationMs) + .then(() => { + this.resetOnUndock_(video); + }); + } + + /** + * @param {!../../../src/video-interface.VideoOrBaseElementDef} video + * @param {number} offsetX + * @param {number} direction -1 or 1 + * @private + */ + bounceToDismiss_(video, offsetX, direction) { + devAssert(Math.abs(direction) == 1); + + const step = 1; + const {target} = devAssert(this.currentlyDocked_); + + const {x, y, width} = this.getTargetArea_(video, target); + const {scale} = this.getDims_(video, target, step); + + const areaWidth = this.getAreaWidth_(); + + const currentX = x + offsetX; + const nextX = direction == 1 ? + calculateRightJustifiedX(areaWidth, width, /* margin */ 0, step) : + calculateLeftJustifiedX(areaWidth, width, /* margin */ 0, step); + + const transitionDurationMs = + this.calculateDismissalTransitionDurationMs_(nextX - currentX); + + this.reconcileUndocked_(); + + this.placeAt_(video, nextX, y, scale, /* step */ 0, transitionDurationMs) + .then(() => { + this.undock_(video, /* reconciled */ true); + video.showControls(); + }); + } + + /** + * @param {number} deltaX + * @return {number} + * @private + */ + calculateDismissalTransitionDurationMs_(deltaX) { + return Math.min(300, Math.abs(deltaX) / 2); } /** @@ -1637,16 +1739,14 @@ export class VideoDocking { /** * @param {!../../../src/video-interface.VideoOrBaseElementDef} video + * @param {boolean=} opt_reconciled * @return {!Promise} * @private */ - undock_(video) { + undock_(video, opt_reconciled) { dev().info(TAG, 'undock', {video}); - this.getControls_().disable(); - const {element} = video; - const isMostlyInView = this.isVisible_(element, REVERT_TO_INLINE_RATIO); if (!isMostlyInView) { @@ -1658,10 +1758,9 @@ export class VideoDocking { video.showControls(); } - // Prevents ghosting - this.getControls_().hide(/* respectSticky */ false, /* immediately */ true); - - this.trigger_(Actions.UNDOCK); + if (!opt_reconciled) { + this.reconcileUndocked_(); + } const step = 0; @@ -1685,6 +1784,16 @@ export class VideoDocking { }); } + /** @private */ + reconcileUndocked_() { + this.getControls_().disable(); + + // Prevents ghosting + this.getControls_().hide(/* respectSticky */ false, /* immediately */ true); + + this.trigger_(Actions.UNDOCK); + } + /** * @param {!../../../src/video-interface.VideoOrBaseElementDef} video diff --git a/extensions/amp-video-docking/amp-video-docking.md b/extensions/amp-video-docking/amp-video-docking.md index b2ca7012415d..711559a57a39 100644 --- a/extensions/amp-video-docking/amp-video-docking.md +++ b/extensions/amp-video-docking/amp-video-docking.md @@ -55,6 +55,7 @@ component's visual area. If the user scrolls back, the video reverts to its orig - The video can be docked to a default corner or to a custom fixed position. - The video can be dragged and repositioned by the user on a different corner. +- The video can be flicked to be dismissed from its docked position. - Multiple videos on the same page can be docked, but only one at a time will be docked and fixed. ### Support