1
0
mirror of https://github.com/videojs/video.js.git synced 2024-12-02 09:11:54 +02:00

feat: Add a mouse volume tooltip (#6824)

This commit is contained in:
Grzegorz Blaszczyk 2021-03-23 23:02:07 +01:00 committed by GitHub
parent 239c9a1552
commit b2edfd24ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 328 additions and 0 deletions

View File

@ -138,6 +138,7 @@
&:before {
position: absolute;
font-size: 0.9em; // Doing this to match the handle on play progress.
z-index: 1;
}
}
@ -148,6 +149,7 @@
&:before {
top: -0.5em;
left: -0.3em;
z-index: 1;
}
}
.vjs-slider-horizontal .vjs-volume-level {
@ -184,3 +186,83 @@
.video-js .vjs-volume-horizontal .vjs-menu {
left: -2em;
}
// .vjs-volume-tooltip
//
// These elements are displayed above the volume bar.
//
// By default, they are hidden and only shown when hovering over the volume
// control.
.video-js .vjs-volume-tooltip {
@include background-color-with-alpha(#fff, 0.8);
@include border-radius(0.3em);
color: #000;
float: right;
font-family: $text-font-family;
font-size: 1em;
padding: 6px 8px 8px 8px;
pointer-events: none;
position: absolute;
top: -3.4em;
visibility: hidden;
z-index: 1;
}
.video-js .vjs-volume-control:hover .vjs-volume-tooltip,
.video-js .vjs-volume-control:hover .vjs-progress-holder:focus .vjs-volume-tooltip {
display: block;
font-size: 1em;
visibility: visible;
}
.video-js .vjs-volume-vertical:hover .vjs-volume-tooltip,
.video-js .vjs-volume-vertical:hover .vjs-progress-holder:focus .vjs-volume-tooltip {
left: 1em;
top: -12px;
}
.video-js .vjs-volume-control.disabled:hover .vjs-volume-tooltip {
font-size: 1em;
}
// .vjs-mouse-display / MouseVolumeLevelDisplay
//
// This element tracks the mouse position along the volume control and
// includes a tooltip, which displays the volume level.
.video-js .vjs-volume-control .vjs-mouse-display {
display: none;
position: absolute;
width: 100%;
height: 1px;
background-color: #000;
z-index: 1;
}
.video-js .vjs-volume-horizontal .vjs-mouse-display {
width: 1px;
height: 100%;
}
.vjs-no-flex .vjs-volume-control .vjs-mouse-display {
z-index: 0;
}
.video-js .vjs-volume-control:hover .vjs-mouse-display {
display: block;
}
.video-js.vjs-user-inactive .vjs-volume-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-volume-control .vjs-mouse-display {
display: none;
}
.vjs-mouse-display .vjs-volume-tooltip {
color: #fff;
@include background-color-with-alpha(#000, 0.8);
}

View File

@ -0,0 +1,87 @@
/**
* @file mouse-volume-level-display.js
*/
import Component from '../../component.js';
import * as Fn from '../../utils/fn.js';
import './volume-level-tooltip';
/**
* The {@link MouseVolumeLevelDisplay} component tracks mouse movement over the
* {@link VolumeControl}. It displays an indicator and a {@link VolumeLevelTooltip}
* indicating the volume level which is represented by a given point in the
* {@link VolumeBar}.
*
* @extends Component
*/
class MouseVolumeLevelDisplay extends Component {
/**
* Creates an instance of this class.
*
* @param {Player} player
* The {@link Player} that this class should be attached to.
*
* @param {Object} [options]
* The key/value store of player options.
*/
constructor(player, options) {
super(player, options);
this.update = Fn.throttle(Fn.bind(this, this.update), Fn.UPDATE_REFRESH_INTERVAL);
}
/**
* Create the DOM element for this class.
*
* @return {Element}
* The element that was created.
*/
createEl() {
return super.createEl('div', {
className: 'vjs-mouse-display'
});
}
/**
* Enquires updates to its own DOM as well as the DOM of its
* {@link VolumeLevelTooltip} child.
*
* @param {Object} rangeBarRect
* The `ClientRect` for the {@link VolumeBar} element.
*
* @param {number} rangeBarPoint
* A number from 0 to 1, representing a horizontal/vertical reference point
* from the left edge of the {@link VolumeBar}
*
* @param {boolean} vertical
* Referees to the Volume control position
* in the control bar{@link VolumeControl}
*
*/
update(rangeBarRect, rangeBarPoint, vertical) {
const volume = 100 * rangeBarPoint;
this.getChild('volumeLevelTooltip').updateVolume(rangeBarRect, rangeBarPoint, vertical, volume, () => {
if (vertical) {
this.el_.style.bottom = `${rangeBarRect.height * rangeBarPoint}px`;
} else {
this.el_.style.left = `${rangeBarRect.width * rangeBarPoint}px`;
}
});
}
}
/**
* Default options for `MouseVolumeLevelDisplay`
*
* @type {Object}
* @private
*/
MouseVolumeLevelDisplay.prototype.options_ = {
children: [
'volumeLevelTooltip'
]
};
Component.registerComponent('MouseVolumeLevelDisplay', MouseVolumeLevelDisplay);
export default MouseVolumeLevelDisplay;

View File

@ -4,9 +4,12 @@
import Slider from '../../slider/slider.js';
import Component from '../../component.js';
import * as Dom from '../../utils/dom.js';
import clamp from '../../utils/clamp.js';
import {IS_IOS, IS_ANDROID} from '../../utils/browser.js';
// Required children
import './volume-level.js';
import './mouse-volume-level-display.js';
/**
* The bar that contains the volume level and can be clicked on to adjust the level
@ -71,6 +74,23 @@ class VolumeBar extends Slider {
* @listens mousemove
*/
handleMouseMove(event) {
const mouseVolumeLevelDisplay = this.getChild('mouseVolumeLevelDisplay');
if (mouseVolumeLevelDisplay) {
const volumeBarEl = this.el();
const volumeBarRect = Dom.getBoundingClientRect(volumeBarEl);
const vertical = this.vertical();
let volumeBarPoint = Dom.getPointerPosition(volumeBarEl, event);
volumeBarPoint = vertical ? volumeBarPoint.y : volumeBarPoint.x;
// The default skin has a gap on either side of the `VolumeBar`. This means
// that it's possible to trigger this behavior outside the boundaries of
// the `VolumeBar`. This ensures we stay within it at all times.
volumeBarPoint = clamp(volumeBarPoint, 0, 1);
mouseVolumeLevelDisplay.update(volumeBarRect, volumeBarPoint, vertical);
}
if (!Dom.isSingleLeftClick(event)) {
return;
}
@ -174,6 +194,11 @@ VolumeBar.prototype.options_ = {
barName: 'volumeLevel'
};
// MouseVolumeLevelDisplay tooltip should not be added to a player on mobile devices
if (!IS_IOS && !IS_ANDROID) {
VolumeBar.prototype.options_.children.splice(0, 0, 'mouseVolumeLevelDisplay');
}
/**
* Call the update event for this Slider when this event happens on the player.
*

View File

@ -44,6 +44,7 @@ class VolumeControl extends Component {
this.on('mousedown', this.handleMouseDown);
this.on('touchstart', this.handleMouseDown);
this.on('mousemove', this.handleMouseMove);
// while the slider is active (the mouse has been pressed down and
// is dragging) or in focus we do not want to hide the VolumeBar

View File

@ -0,0 +1,133 @@
/**
* @file volume-level-tooltip.js
*/
import Component from '../../component';
import * as Dom from '../../utils/dom.js';
import * as Fn from '../../utils/fn.js';
/**
* Volume level tooltips display a volume above or side by side the volume bar.
*
* @extends Component
*/
class VolumeLevelTooltip extends Component {
/**
* Creates an instance of this class.
*
* @param {Player} player
* The {@link Player} that this class should be attached to.
*
* @param {Object} [options]
* The key/value store of player options.
*/
constructor(player, options) {
super(player, options);
this.update = Fn.throttle(Fn.bind(this, this.update), Fn.UPDATE_REFRESH_INTERVAL);
}
/**
* Create the volume tooltip DOM element
*
* @return {Element}
* The element that was created.
*/
createEl() {
return super.createEl('div', {
className: 'vjs-volume-tooltip'
}, {
'aria-hidden': 'true'
});
}
/**
* Updates the position of the tooltip relative to the `VolumeBar` and
* its content text.
*
* @param {Object} rangeBarRect
* The `ClientRect` for the {@link VolumeBar} element.
*
* @param {number} rangeBarPoint
* A number from 0 to 1, representing a horizontal/vertical reference point
* from the left edge of the {@link VolumeBar}
*
* @param {boolean} vertical
* Referees to the Volume control position
* in the control bar{@link VolumeControl}
*
*/
update(rangeBarRect, rangeBarPoint, vertical, content) {
if (!vertical) {
const tooltipRect = Dom.getBoundingClientRect(this.el_);
const playerRect = Dom.getBoundingClientRect(this.player_.el());
const volumeBarPointPx = rangeBarRect.width * rangeBarPoint;
if (!playerRect || !tooltipRect) {
return;
}
const spaceLeftOfPoint = (rangeBarRect.left - playerRect.left) + volumeBarPointPx;
const spaceRightOfPoint = (rangeBarRect.width - volumeBarPointPx) +
(playerRect.right - rangeBarRect.right);
let pullTooltipBy = tooltipRect.width / 2;
if (spaceLeftOfPoint < pullTooltipBy) {
pullTooltipBy += pullTooltipBy - spaceLeftOfPoint;
} else if (spaceRightOfPoint < pullTooltipBy) {
pullTooltipBy = spaceRightOfPoint;
}
if (pullTooltipBy < 0) {
pullTooltipBy = 0;
} else if (pullTooltipBy > tooltipRect.width) {
pullTooltipBy = tooltipRect.width;
}
this.el_.style.right = `-${pullTooltipBy}px`;
}
this.write(`${content}%`);
}
/**
* Write the volume to the tooltip DOM element.
*
* @param {string} content
* The formatted volume for the tooltip.
*/
write(content) {
Dom.textContent(this.el_, content);
}
/**
* Updates the position of the volume tooltip relative to the `VolumeBar`.
*
* @param {Object} rangeBarRect
* The `ClientRect` for the {@link VolumeBar} element.
*
* @param {number} rangeBarPoint
* A number from 0 to 1, representing a horizontal/vertical reference point
* from the left edge of the {@link VolumeBar}
*
* @param {boolean} vertical
* Referees to the Volume control position
* in the control bar{@link VolumeControl}
*
* @param {number} volume
* The volume level to update the tooltip to
*
* @param {Function} cb
* A function that will be called during the request animation frame
* for tooltips that need to do additional animations from the default
*/
updateVolume(rangeBarRect, rangeBarPoint, vertical, volume, cb) {
this.requestNamedAnimationFrame('VolumeLevelTooltip#updateVolume', () => {
this.update(rangeBarRect, rangeBarPoint, vertical, volume.toFixed(0));
if (cb) {
cb();
}
});
}
}
Component.registerComponent('VolumeLevelTooltip', VolumeLevelTooltip);
export default VolumeLevelTooltip;