- 1 :
/**
- 2 :
* @file slider.js
- 3 :
*/
- 4 :
import Component from '../component.js';
- 5 :
import * as Dom from '../utils/dom.js';
- 6 :
import {assign} from '../utils/obj';
- 7 :
import {IS_CHROME} from '../utils/browser.js';
- 8 :
import clamp from '../utils/clamp.js';
- 9 :
import keycode from 'keycode';
- 10 :
- 11 :
/**
- 12 :
* The base functionality for a slider. Can be vertical or horizontal.
- 13 :
* For instance the volume bar or the seek bar on a video is a slider.
- 14 :
*
- 15 :
* @extends Component
- 16 :
*/
- 17 :
class Slider extends Component {
- 18 :
- 19 :
/**
- 20 :
* Create an instance of this class
- 21 :
*
- 22 :
* @param {Player} player
- 23 :
* The `Player` that this class should be attached to.
- 24 :
*
- 25 :
* @param {Object} [options]
- 26 :
* The key/value store of player options.
- 27 :
*/
- 28 :
constructor(player, options) {
- 29 :
super(player, options);
- 30 :
- 31 :
this.handleMouseDown_ = (e) => this.handleMouseDown(e);
- 32 :
this.handleMouseUp_ = (e) => this.handleMouseUp(e);
- 33 :
this.handleKeyDown_ = (e) => this.handleKeyDown(e);
- 34 :
this.handleClick_ = (e) => this.handleClick(e);
- 35 :
this.handleMouseMove_ = (e) => this.handleMouseMove(e);
- 36 :
this.update_ = (e) => this.update(e);
- 37 :
- 38 :
// Set property names to bar to match with the child Slider class is looking for
- 39 :
this.bar = this.getChild(this.options_.barName);
- 40 :
- 41 :
// Set a horizontal or vertical class on the slider depending on the slider type
- 42 :
this.vertical(!!this.options_.vertical);
- 43 :
- 44 :
this.enable();
- 45 :
}
- 46 :
- 47 :
/**
- 48 :
* Are controls are currently enabled for this slider or not.
- 49 :
*
- 50 :
* @return {boolean}
- 51 :
* true if controls are enabled, false otherwise
- 52 :
*/
- 53 :
enabled() {
- 54 :
return this.enabled_;
- 55 :
}
- 56 :
- 57 :
/**
- 58 :
* Enable controls for this slider if they are disabled
- 59 :
*/
- 60 :
enable() {
- 61 :
if (this.enabled()) {
- 62 :
return;
- 63 :
}
- 64 :
- 65 :
this.on('mousedown', this.handleMouseDown_);
- 66 :
this.on('touchstart', this.handleMouseDown_);
- 67 :
this.on('keydown', this.handleKeyDown_);
- 68 :
this.on('click', this.handleClick_);
- 69 :
- 70 :
// TODO: deprecated, controlsvisible does not seem to be fired
- 71 :
this.on(this.player_, 'controlsvisible', this.update);
- 72 :
- 73 :
if (this.playerEvent) {
- 74 :
this.on(this.player_, this.playerEvent, this.update);
- 75 :
}
- 76 :
- 77 :
this.removeClass('disabled');
- 78 :
this.setAttribute('tabindex', 0);
- 79 :
- 80 :
this.enabled_ = true;
- 81 :
}
- 82 :
- 83 :
/**
- 84 :
* Disable controls for this slider if they are enabled
- 85 :
*/
- 86 :
disable() {
- 87 :
if (!this.enabled()) {
- 88 :
return;
- 89 :
}
- 90 :
const doc = this.bar.el_.ownerDocument;
- 91 :
- 92 :
this.off('mousedown', this.handleMouseDown_);
- 93 :
this.off('touchstart', this.handleMouseDown_);
- 94 :
this.off('keydown', this.handleKeyDown_);
- 95 :
this.off('click', this.handleClick_);
- 96 :
this.off(this.player_, 'controlsvisible', this.update_);
- 97 :
this.off(doc, 'mousemove', this.handleMouseMove_);
- 98 :
this.off(doc, 'mouseup', this.handleMouseUp_);
- 99 :
this.off(doc, 'touchmove', this.handleMouseMove_);
- 100 :
this.off(doc, 'touchend', this.handleMouseUp_);
- 101 :
this.removeAttribute('tabindex');
- 102 :
- 103 :
this.addClass('disabled');
- 104 :
- 105 :
if (this.playerEvent) {
- 106 :
this.off(this.player_, this.playerEvent, this.update);
- 107 :
}
- 108 :
this.enabled_ = false;
- 109 :
}
- 110 :
- 111 :
/**
- 112 :
* Create the `Slider`s DOM element.
- 113 :
*
- 114 :
* @param {string} type
- 115 :
* Type of element to create.
- 116 :
*
- 117 :
* @param {Object} [props={}]
- 118 :
* List of properties in Object form.
- 119 :
*
- 120 :
* @param {Object} [attributes={}]
- 121 :
* list of attributes in Object form.
- 122 :
*
- 123 :
* @return {Element}
- 124 :
* The element that gets created.
- 125 :
*/
- 126 :
createEl(type, props = {}, attributes = {}) {
- 127 :
// Add the slider element class to all sub classes
- 128 :
props.className = props.className + ' vjs-slider';
- 129 :
props = assign({
- 130 :
tabIndex: 0
- 131 :
}, props);
- 132 :
- 133 :
attributes = assign({
- 134 :
'role': 'slider',
- 135 :
'aria-valuenow': 0,
- 136 :
'aria-valuemin': 0,
- 137 :
'aria-valuemax': 100,
- 138 :
'tabIndex': 0
- 139 :
}, attributes);
- 140 :
- 141 :
return super.createEl(type, props, attributes);
- 142 :
}
- 143 :
- 144 :
/**
- 145 :
* Handle `mousedown` or `touchstart` events on the `Slider`.
- 146 :
*
- 147 :
* @param {EventTarget~Event} event
- 148 :
* `mousedown` or `touchstart` event that triggered this function
- 149 :
*
- 150 :
* @listens mousedown
- 151 :
* @listens touchstart
- 152 :
* @fires Slider#slideractive
- 153 :
*/
- 154 :
handleMouseDown(event) {
- 155 :
const doc = this.bar.el_.ownerDocument;
- 156 :
- 157 :
if (event.type === 'mousedown') {
- 158 :
event.preventDefault();
- 159 :
}
- 160 :
// Do not call preventDefault() on touchstart in Chrome
- 161 :
// to avoid console warnings. Use a 'touch-action: none' style
- 162 :
// instead to prevent unintented scrolling.
- 163 :
// https://developers.google.com/web/updates/2017/01/scrolling-intervention
- 164 :
if (event.type === 'touchstart' && !IS_CHROME) {
- 165 :
event.preventDefault();
- 166 :
}
- 167 :
Dom.blockTextSelection();
- 168 :
- 169 :
this.addClass('vjs-sliding');
- 170 :
/**
- 171 :
* Triggered when the slider is in an active state
- 172 :
*
- 173 :
* @event Slider#slideractive
- 174 :
* @type {EventTarget~Event}
- 175 :
*/
- 176 :
this.trigger('slideractive');
- 177 :
- 178 :
this.on(doc, 'mousemove', this.handleMouseMove_);
- 179 :
this.on(doc, 'mouseup', this.handleMouseUp_);
- 180 :
this.on(doc, 'touchmove', this.handleMouseMove_);
- 181 :
this.on(doc, 'touchend', this.handleMouseUp_);
- 182 :
- 183 :
this.handleMouseMove(event);
- 184 :
}
- 185 :
- 186 :
/**
- 187 :
* Handle the `mousemove`, `touchmove`, and `mousedown` events on this `Slider`.
- 188 :
* The `mousemove` and `touchmove` events will only only trigger this function during
- 189 :
* `mousedown` and `touchstart`. This is due to {@link Slider#handleMouseDown} and
- 190 :
* {@link Slider#handleMouseUp}.
- 191 :
*
- 192 :
* @param {EventTarget~Event} event
- 193 :
* `mousedown`, `mousemove`, `touchstart`, or `touchmove` event that triggered
- 194 :
* this function
- 195 :
*
- 196 :
* @listens mousemove
- 197 :
* @listens touchmove
- 198 :
*/
- 199 :
handleMouseMove(event) {}
- 200 :
- 201 :
/**
- 202 :
* Handle `mouseup` or `touchend` events on the `Slider`.
- 203 :
*
- 204 :
* @param {EventTarget~Event} event
- 205 :
* `mouseup` or `touchend` event that triggered this function.
- 206 :
*
- 207 :
* @listens touchend
- 208 :
* @listens mouseup
- 209 :
* @fires Slider#sliderinactive
- 210 :
*/
- 211 :
handleMouseUp() {
- 212 :
const doc = this.bar.el_.ownerDocument;
- 213 :
- 214 :
Dom.unblockTextSelection();
- 215 :
- 216 :
this.removeClass('vjs-sliding');
- 217 :
/**
- 218 :
* Triggered when the slider is no longer in an active state.
- 219 :
*
- 220 :
* @event Slider#sliderinactive
- 221 :
* @type {EventTarget~Event}
- 222 :
*/
- 223 :
this.trigger('sliderinactive');
- 224 :
- 225 :
this.off(doc, 'mousemove', this.handleMouseMove_);
- 226 :
this.off(doc, 'mouseup', this.handleMouseUp_);
- 227 :
this.off(doc, 'touchmove', this.handleMouseMove_);
- 228 :
this.off(doc, 'touchend', this.handleMouseUp_);
- 229 :
- 230 :
this.update();
- 231 :
}
- 232 :
- 233 :
/**
- 234 :
* Update the progress bar of the `Slider`.
- 235 :
*
- 236 :
* @return {number}
- 237 :
* The percentage of progress the progress bar represents as a
- 238 :
* number from 0 to 1.
- 239 :
*/
- 240 :
update() {
- 241 :
// In VolumeBar init we have a setTimeout for update that pops and update
- 242 :
// to the end of the execution stack. The player is destroyed before then
- 243 :
// update will cause an error
- 244 :
// If there's no bar...
- 245 :
if (!this.el_ || !this.bar) {
- 246 :
return;
- 247 :
}
- 248 :
- 249 :
// clamp progress between 0 and 1
- 250 :
// and only round to four decimal places, as we round to two below
- 251 :
const progress = this.getProgress();
- 252 :
- 253 :
if (progress === this.progress_) {
- 254 :
return progress;
- 255 :
}
- 256 :
- 257 :
this.progress_ = progress;
- 258 :
- 259 :
this.requestNamedAnimationFrame('Slider#update', () => {
- 260 :
// Set the new bar width or height
- 261 :
const sizeKey = this.vertical() ? 'height' : 'width';
- 262 :
- 263 :
// Convert to a percentage for css value
- 264 :
this.bar.el().style[sizeKey] = (progress * 100).toFixed(2) + '%';
- 265 :
});
- 266 :
- 267 :
return progress;
- 268 :
}
- 269 :
- 270 :
/**
- 271 :
* Get the percentage of the bar that should be filled
- 272 :
* but clamped and rounded.
- 273 :
*
- 274 :
* @return {number}
- 275 :
* percentage filled that the slider is
- 276 :
*/
- 277 :
getProgress() {
- 278 :
return Number(clamp(this.getPercent(), 0, 1).toFixed(4));
- 279 :
}
- 280 :
- 281 :
/**
- 282 :
* Calculate distance for slider
- 283 :
*
- 284 :
* @param {EventTarget~Event} event
- 285 :
* The event that caused this function to run.
- 286 :
*
- 287 :
* @return {number}
- 288 :
* The current position of the Slider.
- 289 :
* - position.x for vertical `Slider`s
- 290 :
* - position.y for horizontal `Slider`s
- 291 :
*/
- 292 :
calculateDistance(event) {
- 293 :
const position = Dom.getPointerPosition(this.el_, event);
- 294 :
- 295 :
if (this.vertical()) {
- 296 :
return position.y;
- 297 :
}
- 298 :
return position.x;
- 299 :
}
- 300 :
- 301 :
/**
- 302 :
* Handle a `keydown` event on the `Slider`. Watches for left, rigth, up, and down
- 303 :
* arrow keys. This function will only be called when the slider has focus. See
- 304 :
* {@link Slider#handleFocus} and {@link Slider#handleBlur}.
- 305 :
*
- 306 :
* @param {EventTarget~Event} event
- 307 :
* the `keydown` event that caused this function to run.
- 308 :
*
- 309 :
* @listens keydown
- 310 :
*/
- 311 :
handleKeyDown(event) {
- 312 :
- 313 :
// Left and Down Arrows
- 314 :
if (keycode.isEventKey(event, 'Left') || keycode.isEventKey(event, 'Down')) {
- 315 :
event.preventDefault();
- 316 :
event.stopPropagation();
- 317 :
this.stepBack();
- 318 :
- 319 :
// Up and Right Arrows
- 320 :
} else if (keycode.isEventKey(event, 'Right') || keycode.isEventKey(event, 'Up')) {
- 321 :
event.preventDefault();
- 322 :
event.stopPropagation();
- 323 :
this.stepForward();
- 324 :
} else {
- 325 :
- 326 :
// Pass keydown handling up for unsupported keys
- 327 :
super.handleKeyDown(event);
- 328 :
}
- 329 :
}
- 330 :
- 331 :
/**
- 332 :
* Listener for click events on slider, used to prevent clicks
- 333 :
* from bubbling up to parent elements like button menus.
- 334 :
*
- 335 :
* @param {Object} event
- 336 :
* Event that caused this object to run
- 337 :
*/
- 338 :
handleClick(event) {
- 339 :
event.stopPropagation();
- 340 :
event.preventDefault();
- 341 :
}
- 342 :
- 343 :
/**
- 344 :
* Get/set if slider is horizontal for vertical
- 345 :
*
- 346 :
* @param {boolean} [bool]
- 347 :
* - true if slider is vertical,
- 348 :
* - false is horizontal
- 349 :
*
- 350 :
* @return {boolean}
- 351 :
* - true if slider is vertical, and getting
- 352 :
* - false if the slider is horizontal, and getting
- 353 :
*/
- 354 :
vertical(bool) {
- 355 :
if (bool === undefined) {
- 356 :
return this.vertical_ || false;
- 357 :
}
- 358 :
- 359 :
this.vertical_ = !!bool;
- 360 :
- 361 :
if (this.vertical_) {
- 362 :
this.addClass('vjs-slider-vertical');
- 363 :
} else {
- 364 :
this.addClass('vjs-slider-horizontal');
- 365 :
}
- 366 :
}
- 367 :
}
- 368 :
- 369 :
Component.registerComponent('Slider', Slider);
- 370 :
export default Slider;