Skip to content

Commit

Permalink
✨Flick to dismiss docked video (#20906)
Browse files Browse the repository at this point in the history
* Tracks dragging velocity.
* On drag end, video is dismissed if velocity is over a certain threshold by either:
  * sliding out, if the component area is out of view
  * or bouncing into position, if the component area is in view

Videos docked to slot elements cannot be dragged, so this feature won't work in this case. An upcoming PR will introduce dragging for slot elements so they can be flicked-to-dismiss.
  • Loading branch information
alanorozco committed Feb 20, 2019
1 parent 3f3e6dd commit 798f38d
Show file tree
Hide file tree
Showing 2 changed files with 119 additions and 9 deletions.
127 changes: 118 additions & 9 deletions extensions/amp-video-docking/0.1/amp-video-docking.js
Expand Up @@ -347,6 +347,12 @@ export class VideoDocking {
/** @private {boolean} */
this.isDragging_ = false;

/** @private {number} */
this.previousDragOffsetX_ = 0;

/** @private {number} */
this.dragVelocityX_ = 0;

/** @private {!Array<!../../../src/video-interface.VideoOrBaseElementDef>} */
this.observed_ = [];

Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
}

/**
Expand Down Expand Up @@ -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) {
Expand All @@ -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;

Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions extensions/amp-video-docking/amp-video-docking.md
Expand Up @@ -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.

### <a id="support"></a> Support
Expand Down

0 comments on commit 798f38d

Please sign in to comment.