mirror of
				https://github.com/videojs/video.js.git
				synced 2025-10-31 00:08:01 +02:00 
			
		
		
		
	@gkatsev added an option to keep the tooltips inside the player bounds. closes #3149
This commit is contained in:
		| @@ -7,6 +7,7 @@ CHANGELOG | |||||||
| * @kamilbrenk Added lang | * @kamilbrenk Added lang | ||||||
| * @arius28 added greek translation file (el.json) ([view](https://github.com/videojs/video.js/pull/3185)) | * @arius28 added greek translation file (el.json) ([view](https://github.com/videojs/video.js/pull/3185)) | ||||||
| * @ricardosiri68 changed the relative sass paths ([view](https://github.com/videojs/video.js/pull/3147)) | * @ricardosiri68 changed the relative sass paths ([view](https://github.com/videojs/video.js/pull/3147)) | ||||||
|  | * @gkatsev added an option to keep the tooltips inside the player bounds ([view](https://github.com/videojs/video.js/pull/3149)) | ||||||
|  |  | ||||||
| -------------------- | -------------------- | ||||||
|  |  | ||||||
|   | |||||||
| @@ -500,6 +500,8 @@ module.exports = function(grunt) { | |||||||
|     grunt.file.write(cssPath, css.replace(/(\\f\w+);/g, "'$1';")); |     grunt.file.write(cssPath, css.replace(/(\\f\w+);/g, "'$1';")); | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|  |   grunt.registerTask('skin', ['sass', 'wrapcodepoints']); | ||||||
|  |  | ||||||
|   // Default task - build and test |   // Default task - build and test | ||||||
|   grunt.registerTask('default', ['test']); |   grunt.registerTask('default', ['test']); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -41,19 +41,46 @@ Player | |||||||
|     BigPlayButton |     BigPlayButton | ||||||
|     ControlBar |     ControlBar | ||||||
|         PlayToggle |         PlayToggle | ||||||
|         FullscreenToggle |         VolumeMenuButton | ||||||
|         CurrentTimeDisplay |         CurrentTimeDisplay (Hidden by default) | ||||||
|         TimeDivider |         TimeDivider (Hidden by default) | ||||||
|         DurationDisplay |         DurationDisplay (Hidden by default) | ||||||
|         RemainingTimeDisplay |  | ||||||
|         ProgressControl |         ProgressControl | ||||||
|             SeekBar |             SeekBar | ||||||
|               LoadProgressBar |               LoadProgressBar | ||||||
|  |               MouseTimeDisplay | ||||||
|               PlayProgressBar |               PlayProgressBar | ||||||
|               SeekHandle |         LiveDisplay (Hidden by default) | ||||||
|         VolumeControl |         RemainingTimeDisplay | ||||||
|             VolumeBar |         CustomControlsSpacer (No UI) | ||||||
|                 VolumeLevel |         ChaptersButton (Hidden by default) | ||||||
|                 VolumeHandle |         SubtitlesButton (Hidden by default) | ||||||
|         MuteToggle |         CaptionsButton (Hidden by default) | ||||||
|  |         FullscreenToggle | ||||||
|  |     ErrorDisplay | ||||||
|  |     TextTrackSettings | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | ## Progress Control | ||||||
|  | The progress control is made up of the SeekBar. The seekbar contains the load progress bar | ||||||
|  | and the play progress bar. In addition, it contains the Mouse Time Display which | ||||||
|  | is used to display the time tooltip that follows the mouse cursor. | ||||||
|  | The play progress bar also has a time tooltip that show the current time. | ||||||
|  |  | ||||||
|  | By default, the progress control is sandwiched between the volume menu button and | ||||||
|  | the remaining time display inside the control bar, but in some cases, a skin would | ||||||
|  | want to move the progress control above the control bar and have it span the full | ||||||
|  | width of the player, in those cases, it is less than ideal to have the tooltips | ||||||
|  | get cut off or leave the bounds of the player. This can be prevented by setting the | ||||||
|  | `keepTooltipsInside` option on the progress control. This also makes the tooltips use  | ||||||
|  | a real element instead of pseudo elements so targetting them with css will be different. | ||||||
|  |  | ||||||
|  | ```js | ||||||
|  | let player = videojs('myplayer', { | ||||||
|  |   controlBar: { | ||||||
|  |     progressControl: { | ||||||
|  |       keepTooltipsInside: true | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | }); | ||||||
| ``` | ``` | ||||||
|   | |||||||
| @@ -39,15 +39,18 @@ | |||||||
|  to avoid a weird hitch when you roll off the hover. */ |  to avoid a weird hitch when you roll off the hover. */ | ||||||
|  |  | ||||||
| // Also show the current time tooltip | // 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-mouse-display:after, | ||||||
| .video-js .vjs-progress-control:hover .vjs-play-progress:after { | .video-js .vjs-progress-control:hover .vjs-play-progress:after { | ||||||
|   display: block; |   font-family: VideoJS; | ||||||
|  |   visibility: visible; | ||||||
|   font-size: 0.6em; |   font-size: 0.6em; | ||||||
| } | } | ||||||
|  |  | ||||||
| // Progress Bars | // Progress Bars | ||||||
| .video-js .vjs-progress-holder .vjs-play-progress, | .video-js .vjs-progress-holder .vjs-play-progress, | ||||||
| .video-js .vjs-progress-holder .vjs-load-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 { | .video-js .vjs-progress-holder .vjs-load-progress div { | ||||||
|   position: absolute; |   position: absolute; | ||||||
|   display: block; |   display: block; | ||||||
| @@ -83,12 +86,14 @@ | |||||||
|  |  | ||||||
| // Current Time "tooltip" | // Current Time "tooltip" | ||||||
| // By default this is hidden and only shown when hovering over the progress control | // 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-mouse-display:after, | ||||||
| .video-js .vjs-play-progress:after { | .video-js .vjs-play-progress:after { | ||||||
|   display: none; |   visibility: hidden; | ||||||
|  |   pointer-events: none; | ||||||
|   position: absolute; |   position: absolute; | ||||||
|   top: -3.4em; |   top: -3.4em; | ||||||
|   right: -1.5em; |   right: -1.9em; | ||||||
|   font-size: 0.9em; |   font-size: 0.9em; | ||||||
|   color: #000; |   color: #000; | ||||||
|   content: attr(data-current-time); |   content: attr(data-current-time); | ||||||
| @@ -96,11 +101,17 @@ | |||||||
|   @include background-color-with-alpha(#fff, 0.8); |   @include background-color-with-alpha(#fff, 0.8); | ||||||
|   @include border-radius(0.3em); |   @include border-radius(0.3em); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | .video-js .vjs-time-tooltip, | ||||||
| .video-js .vjs-play-progress:before, | .video-js .vjs-play-progress:before, | ||||||
| .video-js .vjs-play-progress:after { | .video-js .vjs-play-progress:after { | ||||||
|   z-index: 1; |   z-index: 1; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | .video-js .vjs-progress-control .vjs-keep-tooltips-inside:after { | ||||||
|  |   display: none; | ||||||
|  | } | ||||||
|  |  | ||||||
| .video-js .vjs-load-progress { | .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%); |   background: lighten($secondary-background-color, 25%); | ||||||
| @@ -121,6 +132,18 @@ | |||||||
|   width: auto; |   width: auto; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | .video-js .vjs-time-tooltip { | ||||||
|  |   display: inline-block; | ||||||
|  |   height: 2.4em; | ||||||
|  |   position: relative; | ||||||
|  |   float: right; | ||||||
|  |   right: -1.9em; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .vjs-tooltip-progress-bar { | ||||||
|  |   visibility: hidden; | ||||||
|  | } | ||||||
|  |  | ||||||
| .video-js .vjs-progress-control .vjs-mouse-display { | .video-js .vjs-progress-control .vjs-mouse-display { | ||||||
|   display: none; |   display: none; | ||||||
|   position: absolute; |   position: absolute; | ||||||
| @@ -146,6 +169,7 @@ | |||||||
| .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:after { | ||||||
|   display: none; |   display: none; | ||||||
| } | } | ||||||
|  | .vjs-mouse-display .vjs-time-tooltip, | ||||||
| .video-js .vjs-progress-control .vjs-mouse-display:after { | .video-js .vjs-progress-control .vjs-mouse-display:after { | ||||||
|   color: #fff; |   color: #fff; | ||||||
|   @include background-color-with-alpha(#000, 0.8); |   @include background-color-with-alpha(#000, 0.8); | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| /** | /** | ||||||
|  * @file mouse-time-display.js |  * @file mouse-time-display.js | ||||||
|  */ |  */ | ||||||
|  | import window from 'global/window'; | ||||||
| import Component from '../../component.js'; | import Component from '../../component.js'; | ||||||
| import * as Dom from '../../utils/dom.js'; | import * as Dom from '../../utils/dom.js'; | ||||||
| import * as Fn from '../../utils/fn.js'; | import * as Fn from '../../utils/fn.js'; | ||||||
| @@ -21,6 +22,19 @@ class MouseTimeDisplay extends Component { | |||||||
|   constructor(player, options) { |   constructor(player, options) { | ||||||
|     super(player, options); |     super(player, options); | ||||||
|  |  | ||||||
|  |     if (options.playerOptions && | ||||||
|  |         options.playerOptions.controlBar && | ||||||
|  |         options.playerOptions.controlBar.progressControl && | ||||||
|  |         options.playerOptions.controlBar.progressControl.keepTooltipsInside) { | ||||||
|  |       this.keepTooltipsInside = options.playerOptions.controlBar.progressControl.keepTooltipsInside; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (this.keepTooltipsInside) { | ||||||
|  |       this.tooltip = Dom.createEl('div', {className: 'vjs-time-tooltip'}); | ||||||
|  |       this.el().appendChild(this.tooltip); | ||||||
|  |       this.addClass('vjs-keep-tooltips-inside'); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     this.update(0, 0); |     this.update(0, 0); | ||||||
|  |  | ||||||
|     player.on('ready', () => { |     player.on('ready', () => { | ||||||
| @@ -53,11 +67,50 @@ class MouseTimeDisplay extends Component { | |||||||
|  |  | ||||||
|     this.el().style.left = position + 'px'; |     this.el().style.left = position + 'px'; | ||||||
|     this.el().setAttribute('data-current-time', time); |     this.el().setAttribute('data-current-time', time); | ||||||
|  |  | ||||||
|  |     if (this.keepTooltipsInside) { | ||||||
|  |       let clampedPosition = this.clampPosition_(position); | ||||||
|  |       let difference = position - clampedPosition + 1; | ||||||
|  |       let tooltipWidth = parseFloat(window.getComputedStyle(this.tooltip).width); | ||||||
|  |       let tooltipWidthHalf = tooltipWidth / 2; | ||||||
|  |  | ||||||
|  |       this.tooltip.innerHTML = time; | ||||||
|  |       this.tooltip.style.right = `-${tooltipWidthHalf - difference}px`; | ||||||
|  |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   calculateDistance(event) { |   calculateDistance(event) { | ||||||
|     return Dom.getPointerPosition(this.el().parentNode, event).x; |     return Dom.getPointerPosition(this.el().parentNode, event).x; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * This takes in a horizontal position for the bar and returns a clamped position. | ||||||
|  |    * Clamped position means that it will keep the position greater than half the width | ||||||
|  |    * of the tooltip and smaller than the player width minus half the width o the tooltip. | ||||||
|  |    * It will only clamp the position if `keepTooltipsInside` option is set. | ||||||
|  |    * | ||||||
|  |    * @param {Number} position the position the bar wants to be | ||||||
|  |    * @return {Number} newPosition the (potentially) clamped position | ||||||
|  |    * @method clampPosition_ | ||||||
|  |    */ | ||||||
|  |   clampPosition_(position) { | ||||||
|  |     if (!this.keepTooltipsInside) { | ||||||
|  |       return position; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     let playerWidth = parseFloat(window.getComputedStyle(this.player().el()).width); | ||||||
|  |     let tooltipWidth = parseFloat(window.getComputedStyle(this.tooltip).width); | ||||||
|  |     let tooltipWidthHalf = tooltipWidth / 2; | ||||||
|  |     let actualPosition = position; | ||||||
|  |  | ||||||
|  |     if (position < tooltipWidthHalf) { | ||||||
|  |       actualPosition = Math.ceil(tooltipWidthHalf); | ||||||
|  |     } else if (position > (playerWidth - tooltipWidthHalf)) { | ||||||
|  |       actualPosition = Math.floor(playerWidth - tooltipWidthHalf); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     return actualPosition; | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| Component.registerComponent('MouseTimeDisplay', MouseTimeDisplay); | Component.registerComponent('MouseTimeDisplay', MouseTimeDisplay); | ||||||
|   | |||||||
| @@ -3,6 +3,7 @@ | |||||||
|  */ |  */ | ||||||
| import Component from '../../component.js'; | import Component from '../../component.js'; | ||||||
| import * as Fn from '../../utils/fn.js'; | import * as Fn from '../../utils/fn.js'; | ||||||
|  | import * as Dom from '../../utils/dom.js'; | ||||||
| import formatTime from '../../utils/format-time.js'; | import formatTime from '../../utils/format-time.js'; | ||||||
|  |  | ||||||
| /** | /** | ||||||
| @@ -20,6 +21,17 @@ class PlayProgressBar extends Component { | |||||||
|     this.updateDataAttr(); |     this.updateDataAttr(); | ||||||
|     this.on(player, 'timeupdate', this.updateDataAttr); |     this.on(player, 'timeupdate', this.updateDataAttr); | ||||||
|     player.ready(Fn.bind(this, this.updateDataAttr)); |     player.ready(Fn.bind(this, this.updateDataAttr)); | ||||||
|  |  | ||||||
|  |     if (options.playerOptions && | ||||||
|  |         options.playerOptions.controlBar && | ||||||
|  |         options.playerOptions.controlBar.progressControl && | ||||||
|  |         options.playerOptions.controlBar.progressControl.keepTooltipsInside) { | ||||||
|  |       this.keepTooltipsInside = options.playerOptions.controlBar.progressControl.keepTooltipsInside; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (this.keepTooltipsInside) { | ||||||
|  |       this.addClass('vjs-keep-tooltips-inside'); | ||||||
|  |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
|   | |||||||
| @@ -1,10 +1,12 @@ | |||||||
| /** | /** | ||||||
|  * @file seek-bar.js |  * @file seek-bar.js | ||||||
|  */ |  */ | ||||||
|  | import window from 'global/window'; | ||||||
| import Slider from '../../slider/slider.js'; | import Slider from '../../slider/slider.js'; | ||||||
| import Component from '../../component.js'; | import Component from '../../component.js'; | ||||||
| import LoadProgressBar from './load-progress-bar.js'; | import LoadProgressBar from './load-progress-bar.js'; | ||||||
| import PlayProgressBar from './play-progress-bar.js'; | import PlayProgressBar from './play-progress-bar.js'; | ||||||
|  | import TooltipProgressBar from './tooltip-progress-bar.js'; | ||||||
| import * as Fn from '../../utils/fn.js'; | import * as Fn from '../../utils/fn.js'; | ||||||
| import formatTime from '../../utils/format-time.js'; | import formatTime from '../../utils/format-time.js'; | ||||||
| import assign from 'object.assign'; | import assign from 'object.assign'; | ||||||
| @@ -21,8 +23,20 @@ class SeekBar extends Slider { | |||||||
|  |  | ||||||
|   constructor(player, options){ |   constructor(player, options){ | ||||||
|     super(player, options); |     super(player, options); | ||||||
|     this.on(player, 'timeupdate', this.updateARIAAttributes); |     this.on(player, 'timeupdate', this.updateProgress); | ||||||
|     player.ready(Fn.bind(this, this.updateARIAAttributes)); |     this.on(player, 'ended', this.updateProgress); | ||||||
|  |     player.ready(Fn.bind(this, this.updateProgress)); | ||||||
|  |  | ||||||
|  |     if (options.playerOptions && | ||||||
|  |         options.playerOptions.controlBar && | ||||||
|  |         options.playerOptions.controlBar.progressControl && | ||||||
|  |         options.playerOptions.controlBar.progressControl.keepTooltipsInside) { | ||||||
|  |       this.keepTooltipsInside = options.playerOptions.controlBar.progressControl.keepTooltipsInside; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (this.keepTooltipsInside) { | ||||||
|  |       this.tooltipProgressBar = this.addChild('TooltipProgressBar'); | ||||||
|  |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
| @@ -44,11 +58,27 @@ class SeekBar extends Slider { | |||||||
|    * |    * | ||||||
|    * @method updateARIAAttributes |    * @method updateARIAAttributes | ||||||
|    */ |    */ | ||||||
|   updateARIAAttributes() { |   updateProgress() { | ||||||
|       // Allows for smooth scrubbing, when player can't keep up. |     this.updateAriaAttributes(this.el_); | ||||||
|       let time = (this.player_.scrubbing()) ? this.player_.getCache().currentTime : this.player_.currentTime(); |  | ||||||
|       this.el_.setAttribute('aria-valuenow', (this.getPercent() * 100).toFixed(2)); // machine readable value of progress bar (percentage complete) |     if (this.keepTooltipsInside) { | ||||||
|       this.el_.setAttribute('aria-valuetext', formatTime(time, this.player_.duration())); // human readable value of progress bar (time complete) |       this.updateAriaAttributes(this.tooltipProgressBar.el_); | ||||||
|  |       this.tooltipProgressBar.el_.style.width = this.bar.el_.style.width; | ||||||
|  |  | ||||||
|  |       let playerWidth = parseFloat(window.getComputedStyle(this.player().el()).width); | ||||||
|  |       let tooltipWidth = parseFloat(window.getComputedStyle(this.tooltipProgressBar.tooltip).width); | ||||||
|  |       let tooltipStyle = this.tooltipProgressBar.el().style; | ||||||
|  |       tooltipStyle.maxWidth = Math.floor(playerWidth - (tooltipWidth / 2)) + 'px'; | ||||||
|  |       tooltipStyle.minWidth = Math.ceil(tooltipWidth / 2) + 'px'; | ||||||
|  |       tooltipStyle.right = `-${tooltipWidth / 2}px`; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   updateAriaAttributes(el) { | ||||||
|  |     // Allows for smooth scrubbing, when player can't keep up. | ||||||
|  |     let time = (this.player_.scrubbing()) ? this.player_.getCache().currentTime : this.player_.currentTime(); | ||||||
|  |     el.setAttribute('aria-valuenow', (this.getPercent() * 100).toFixed(2)); // machine readable value of progress bar (percentage complete) | ||||||
|  |     el.setAttribute('aria-valuetext', formatTime(time, this.player_.duration())); // human readable value of progress bar (time complete) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /** |   /** | ||||||
|   | |||||||
							
								
								
									
										54
									
								
								src/js/control-bar/progress-control/tooltip-progress-bar.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/js/control-bar/progress-control/tooltip-progress-bar.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,54 @@ | |||||||
|  | /** | ||||||
|  |  * @file play-progress-bar.js | ||||||
|  |  */ | ||||||
|  | import Component from '../../component.js'; | ||||||
|  | import * as Fn from '../../utils/fn.js'; | ||||||
|  | import * as Dom from '../../utils/dom.js'; | ||||||
|  | import formatTime from '../../utils/format-time.js'; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Shows play progress | ||||||
|  |  * | ||||||
|  |  * @param {Player|Object} player | ||||||
|  |  * @param {Object=} options | ||||||
|  |  * @extends Component | ||||||
|  |  * @class PlayProgressBar | ||||||
|  |  */ | ||||||
|  | class TooltipProgressBar extends Component { | ||||||
|  |  | ||||||
|  |   constructor(player, options){ | ||||||
|  |     super(player, options); | ||||||
|  |     this.updateDataAttr(); | ||||||
|  |     this.on(player, 'timeupdate', this.updateDataAttr); | ||||||
|  |     player.ready(Fn.bind(this, this.updateDataAttr)); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   /** | ||||||
|  |    * Create the component's DOM element | ||||||
|  |    * | ||||||
|  |    * @return {Element} | ||||||
|  |    * @method createEl | ||||||
|  |    */ | ||||||
|  |   createEl() { | ||||||
|  |     let el = super.createEl('div', { | ||||||
|  |       className: 'vjs-tooltip-progress-bar vjs-slider-bar', | ||||||
|  |       innerHTML: `<div class="vjs-time-tooltip"></div> | ||||||
|  |         <span class="vjs-control-text"><span>${this.localize('Progress')}</span>: 0%</span>` | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     this.tooltip = el.querySelector('.vjs-time-tooltip'); | ||||||
|  |  | ||||||
|  |     return el; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   updateDataAttr() { | ||||||
|  |     let time = (this.player_.scrubbing()) ? this.player_.getCache().currentTime : this.player_.currentTime(); | ||||||
|  |     let formattedTime = formatTime(time, this.player_.duration()); | ||||||
|  |     this.el_.setAttribute('data-current-time', formattedTime); | ||||||
|  |     this.tooltip.innerHTML = formattedTime; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Component.registerComponent('TooltipProgressBar', TooltipProgressBar); | ||||||
|  | export default TooltipProgressBar; | ||||||
		Reference in New Issue
	
	Block a user