Skip to content

Commit

Permalink
feat: Time Tooltips (#3836)
Browse files Browse the repository at this point in the history
Switch to `keepTooltipsInside` by default and simplify DOM structure around the time tooltips and progress control.

BREAKING CHANGE: removal of `keepTooltipsInside` option.
  • Loading branch information
misteroneill authored and gkatsev committed Jan 19, 2017
1 parent 49bed07 commit 1ba1f5a
Show file tree
Hide file tree
Showing 13 changed files with 576 additions and 363 deletions.
18 changes: 0 additions & 18 deletions docs/guides/components.md
Original file line number Diff line number Diff line change
Expand Up @@ -316,24 +316,6 @@ Player

## Specific Component Details

### Progress Control

The progress control has a grandchild component, the mouse time display, which shows a time tooltip that follows the mouse cursor.

By default, the progress control is sandwiched inside the control bar between the volume menu button and the remaining time display. Some skins attempt to move the it above the control bar and have it span the full width of the player. In these cases, it is less than ideal to have the tooltips leave the bounds of the player. This can be prevented by setting the `keepTooltipsInside` option on the progress control.

```js
let player = videojs('myplayer', {
controlBar: {
progressControl: {
keepTooltipsInside: true
}
}
});
```

> **Note:** This makes the tooltips use a real element instead of pseudo-elements so targeting them with CSS is different.
### Text Track Settings

The text track settings component is only available when using emulated text tracks.
158 changes: 70 additions & 88 deletions src/css/components/_progress.scss
Original file line number Diff line number Diff line change
@@ -1,17 +1,6 @@
// .vjs-progress-control / ProgressControl
//
// Let's talk pixel math!
// Start with a base font size of 10px (assuming that hasn't changed)
// No Hover:
// - Progress holder is 3px
// - Progress handle is 9px
// - Progress handle is pulled up 3px to center it.
//
// Hover:
// - Progress holder becomes 5px
// - Progress handle becomes 15px
// - Progress handle is pulled up 5px to center it
//

// This is the container for all progress bar-related components/elements.
.video-js .vjs-progress-control {
@include flex(auto);
@include display-flex(center);
Expand All @@ -22,35 +11,32 @@
display: none;
}

// Box containing play and load progresses. Also acts as seek scrubber.
.vjs-no-flex .vjs-progress-control {
width: auto;
}

// .vjs-progress-holder / SeekBar
//
// Box containing play and load progress bars. It also acts as seek scrubber.
.video-js .vjs-progress-holder {
@include flex(auto);
@include transition(all 0.2s);
height: 0.3em;
}

// We need an increased hit area on hover
// This increases the size of the progress holder so there is an increased
// hit area for clicks/touches.
.video-js .vjs-progress-control:hover .vjs-progress-holder {
font-size: 1.666666666666666666em;
}

/* If we let the font size grow as much as everything else, the current time tooltip ends up
ginormous. If you'd like to enable the current time tooltip all the time, this should be disabled
to avoid a weird hitch when you roll off the hover. */

// Also show the current time tooltip
.video-js .vjs-progress-control:hover .vjs-time-tooltip,
.video-js .vjs-progress-control:hover .vjs-mouse-display:after,
.video-js .vjs-progress-control:hover .vjs-play-progress:after {
font-family: $text-font-family;
visibility: visible;
font-size: 0.6em;
}

// Progress Bars
// .vjs-play-progress / PlayProgressBar and .vjs-load-progress / LoadProgressBar
//
// These are bars that appear within the progress control to communicate the
// amount of media that has played back and the amount of media that has
// loaded, respectively.
.video-js .vjs-progress-holder .vjs-play-progress,
.video-js .vjs-progress-holder .vjs-load-progress,
.video-js .vjs-progress-holder .vjs-tooltip-progress-bar,
.video-js .vjs-progress-holder .vjs-load-progress div {
position: absolute;
display: block;
Expand All @@ -64,86 +50,80 @@
top: 0;
}

.video-js .vjs-mouse-display {
@extend .vjs-icon-circle;

&:before {
display: none;
}
}
.video-js .vjs-play-progress {
background-color: $primary-foreground-color;
@extend .vjs-icon-circle;

// Progress handle
&:before {
font-size: 0.9em;
position: absolute;
top: -0.333333333333333em;
right: -0.5em;
font-size: 0.9em;
top: -0.333333333333333em;
z-index: 1;
}
}

// Current Time "tooltip"
// By default this is hidden and only shown when hovering over the progress control
.video-js .vjs-time-tooltip,
.video-js .vjs-mouse-display:after,
.video-js .vjs-play-progress:after {
visibility: hidden;
pointer-events: none;
position: absolute;
top: -3.4em;
right: -1.9em;
font-size: 0.9em;
color: #000;
content: attr(data-current-time);
padding: 6px 8px 8px 8px;
@include background-color-with-alpha(#fff, 0.8);
@include border-radius(0.3em);
}

.video-js .vjs-time-tooltip,
.video-js .vjs-play-progress:before,
.video-js .vjs-play-progress:after {
z-index: 1;
}

.video-js .vjs-progress-control .vjs-keep-tooltips-inside:after {
display: none;
}

.video-js .vjs-load-progress {
// For IE8 we'll lighten the color
// For IE8, we'll lighten the color
background: lighten($secondary-background-color, 25%);
// Otherwise we'll rely on stacked opacities
// Otherwise, we'll rely on stacked opacities
background: rgba($secondary-background-color, $secondary-background-transparency);
}

// there are child elements of the load progress bar that represent the
// specific time ranges that have been buffered
// There are child elements of the load progress bar that represent the
// specific time ranges that have been buffered.
.video-js .vjs-load-progress div {
// For IE8 we'll lighten the color
// For IE8, we'll lighten the color
background: lighten($secondary-background-color, 50%);
// Otherwise we'll rely on stacked opacities
// Otherwise, we'll rely on stacked opacities
background: rgba($secondary-background-color, 0.75);
}

.video-js.vjs-no-flex .vjs-progress-control {
width: auto;
}

// .vjs-time-tooltip
//
// These elements are displayed above the progress bar. They are not components
// themselves, but they are managed by the MouseTimeDisplay and PlayProgressBar
// components individually.
//
// By default, they are hidden and only shown when hovering over the progress
// control.
.video-js .vjs-time-tooltip {
@include background-color-with-alpha(#fff, 0.8);
@include border-radius(0.3em);
color: #000;
display: inline-block;
height: 2.4em;
position: relative;

// By floating the tooltips to the right, their right edge becomes aligned
// with the right edge of their parent element. However, in order to have them
// centered, they must be pulled further to the right via positioning (e.g.
// `right: -10px;`. This part is left to JavaScript.
float: right;
right: -1.9em;
}
font-family: $text-font-family;

.vjs-tooltip-progress-bar {
// The font-size should translate to a consistent 10px for time tooltips in
// all states. This is tricky because the .vjs-progress-holder element
// changes its font-size when the .vjs-progress-control is hovered.
font-size: 1em;
padding: 6px 8px 8px 8px;
pointer-events: none;
position: relative;
top: -3.4em;
visibility: hidden;
z-index: 1;
}

.video-js .vjs-progress-control:hover .vjs-time-tooltip {

// Ensure that we maintain a font-size of ~10px.
font-size: 0.6em;
visibility: visible;
}

// .vjs-mouse-display / MouseTimeDisplay
//
// This element tracks the mouse position along the progress control and
// includes a tooltip, which displays the time at that point in the media.
.video-js .vjs-progress-control .vjs-mouse-display {
display: none;
position: absolute;
Expand All @@ -152,25 +132,27 @@
background-color: #000;
z-index: 1;
}

.vjs-no-flex .vjs-progress-control .vjs-mouse-display {
z-index: 0;
}

.video-js .vjs-progress-control:hover .vjs-mouse-display {
display: block;
}
.video-js.vjs-user-inactive .vjs-progress-control .vjs-mouse-display,
.video-js.vjs-user-inactive .vjs-progress-control .vjs-mouse-display:after {

.video-js.vjs-user-inactive .vjs-progress-control .vjs-mouse-display {
visibility: hidden;
opacity: 0;
$trans: visibility 1.0s, opacity 1.0s;
@include transition($trans);
}
.video-js.vjs-user-inactive.vjs-no-flex .vjs-progress-control .vjs-mouse-display,
.video-js.vjs-user-inactive.vjs-no-flex .vjs-progress-control .vjs-mouse-display:after {

.video-js.vjs-user-inactive.vjs-no-flex .vjs-progress-control .vjs-mouse-display {
display: none;
}
.vjs-mouse-display .vjs-time-tooltip,
.video-js .vjs-progress-control .vjs-mouse-display:after {

.vjs-mouse-display .vjs-time-tooltip {
color: #fff;
@include background-color-with-alpha(#000, 0.8);
}
86 changes: 86 additions & 0 deletions src/js/component.js
Original file line number Diff line number Diff line change
Expand Up @@ -1442,6 +1442,81 @@ class Component {
return intervalId;
}

/**
* Queues up a callback to be passed to requestAnimationFrame (rAF), but
* with a few extra bonuses:
*
* - Supports browsers that do not support rAF by falling back to
* {@link Component#setTimeout}.
*
* - The callback is turned into a {@link Component~GenericCallback} (i.e.
* bound to the component).
*
* - Automatic cancellation of the rAF callback is handled if the component
* is disposed before it is called.
*
* @param {Component~GenericCallback} fn
* A function that will be bound to this component and executed just
* before the browser's next repaint.
*
* @return {number}
* Returns an rAF ID that gets used to identify the timeout. It can
* also be used in {@link Component#cancelAnimationFrame} to cancel
* the animation frame callback.
*
* @listens Component#dispose
* @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame}
*/
requestAnimationFrame(fn) {
if (this.supportsRaf_) {
fn = Fn.bind(this, fn);

const id = window.requestAnimationFrame(fn);
const disposeFn = () => this.cancelAnimationFrame(id);

disposeFn.guid = `vjs-raf-${id}`;
this.on('dispose', disposeFn);

return id;
}

// Fall back to using a timer.
return this.setTimeout(fn, 1000 / 60);
}

/**
* Cancels a queued callback passed to {@link Component#requestAnimationFrame}
* (rAF).
*
* If you queue an rAF callback via {@link Component#requestAnimationFrame},
* use this function instead of `window.cancelAnimationFrame`. If you don't,
* your dispose listener will not get cleaned up until {@link Component#dispose}!
*
* @param {number} id
* The rAF ID to clear. The return value of {@link Component#requestAnimationFrame}.
*
* @return {number}
* Returns the rAF ID that was cleared.
*
* @see [Similar to]{@link https://developer.mozilla.org/en-US/docs/Web/API/window/cancelAnimationFrame}
*/
cancelAnimationFrame(id) {
if (this.supportsRaf_) {
window.cancelAnimationFrame(id);

const disposeFn = function() {};

disposeFn.guid = `vjs-raf-${id}`;

this.off('dispose', disposeFn);

return id;
}

// Fall back to using a timer.
return this.clearTimeout(id);
}

/**
* Register a `Component` with `videojs` given the name and the component.
*
Expand Down Expand Up @@ -1540,6 +1615,17 @@ class Component {
}
}

/**
* Whether or not this component supports `requestAnimationFrame`.
*
* This is exposed primarily for testing purposes.
*
* @private
* @type {Boolean}
*/
Component.prototype.supportsRaf_ = typeof window.requestAnimationFrame === 'function' &&
typeof window.cancelAnimationFrame === 'function';

Component.registerComponent('Component', Component);

export default Component;
Loading

0 comments on commit 1ba1f5a

Please sign in to comment.