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 | ||||
| * @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)) | ||||
| * @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.registerTask('skin', ['sass', 'wrapcodepoints']); | ||||
|  | ||||
|   // Default task - build and test | ||||
|   grunt.registerTask('default', ['test']); | ||||
|  | ||||
|   | ||||
| @@ -41,19 +41,46 @@ Player | ||||
|     BigPlayButton | ||||
|     ControlBar | ||||
|         PlayToggle | ||||
|         FullscreenToggle | ||||
|         CurrentTimeDisplay | ||||
|         TimeDivider | ||||
|         DurationDisplay | ||||
|         RemainingTimeDisplay | ||||
|         VolumeMenuButton | ||||
|         CurrentTimeDisplay (Hidden by default) | ||||
|         TimeDivider (Hidden by default) | ||||
|         DurationDisplay (Hidden by default) | ||||
|         ProgressControl | ||||
|             SeekBar | ||||
|               LoadProgressBar | ||||
|               MouseTimeDisplay | ||||
|               PlayProgressBar | ||||
|               SeekHandle | ||||
|         VolumeControl | ||||
|             VolumeBar | ||||
|                 VolumeLevel | ||||
|                 VolumeHandle | ||||
|         MuteToggle | ||||
|         LiveDisplay (Hidden by default) | ||||
|         RemainingTimeDisplay | ||||
|         CustomControlsSpacer (No UI) | ||||
|         ChaptersButton (Hidden by default) | ||||
|         SubtitlesButton (Hidden by default) | ||||
|         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. */ | ||||
|  | ||||
| // 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 { | ||||
|   display: block; | ||||
|   font-family: VideoJS; | ||||
|   visibility: visible; | ||||
|   font-size: 0.6em; | ||||
| } | ||||
|  | ||||
| // Progress Bars | ||||
| .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; | ||||
| @@ -83,12 +86,14 @@ | ||||
|  | ||||
| // 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 { | ||||
|   display: none; | ||||
|   visibility: hidden; | ||||
|   pointer-events: none; | ||||
|   position: absolute; | ||||
|   top: -3.4em; | ||||
|   right: -1.5em; | ||||
|   right: -1.9em; | ||||
|   font-size: 0.9em; | ||||
|   color: #000; | ||||
|   content: attr(data-current-time); | ||||
| @@ -96,11 +101,17 @@ | ||||
|   @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 | ||||
|   background: lighten($secondary-background-color, 25%); | ||||
| @@ -121,6 +132,18 @@ | ||||
|   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 { | ||||
|   display: none; | ||||
|   position: absolute; | ||||
| @@ -146,6 +169,7 @@ | ||||
| .video-js.vjs-user-inactive.vjs-no-flex .vjs-progress-control .vjs-mouse-display:after { | ||||
|   display: none; | ||||
| } | ||||
| .vjs-mouse-display .vjs-time-tooltip, | ||||
| .video-js .vjs-progress-control .vjs-mouse-display:after { | ||||
|   color: #fff; | ||||
|   @include background-color-with-alpha(#000, 0.8); | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| /** | ||||
|  * @file mouse-time-display.js | ||||
|  */ | ||||
| import window from 'global/window'; | ||||
| import Component from '../../component.js'; | ||||
| import * as Dom from '../../utils/dom.js'; | ||||
| import * as Fn from '../../utils/fn.js'; | ||||
| @@ -21,6 +22,19 @@ class MouseTimeDisplay extends Component { | ||||
|   constructor(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); | ||||
|  | ||||
|     player.on('ready', () => { | ||||
| @@ -53,11 +67,50 @@ class MouseTimeDisplay extends Component { | ||||
|  | ||||
|     this.el().style.left = position + 'px'; | ||||
|     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) { | ||||
|     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); | ||||
|   | ||||
| @@ -3,6 +3,7 @@ | ||||
|  */ | ||||
| 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'; | ||||
|  | ||||
| /** | ||||
| @@ -20,6 +21,17 @@ class PlayProgressBar extends Component { | ||||
|     this.updateDataAttr(); | ||||
|     this.on(player, 'timeupdate', 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 | ||||
|  */ | ||||
| import window from 'global/window'; | ||||
| import Slider from '../../slider/slider.js'; | ||||
| import Component from '../../component.js'; | ||||
| import LoadProgressBar from './load-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 formatTime from '../../utils/format-time.js'; | ||||
| import assign from 'object.assign'; | ||||
| @@ -21,8 +23,20 @@ class SeekBar extends Slider { | ||||
|  | ||||
|   constructor(player, options){ | ||||
|     super(player, options); | ||||
|     this.on(player, 'timeupdate', this.updateARIAAttributes); | ||||
|     player.ready(Fn.bind(this, this.updateARIAAttributes)); | ||||
|     this.on(player, 'timeupdate', this.updateProgress); | ||||
|     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 | ||||
|    */ | ||||
|   updateARIAAttributes() { | ||||
|       // Allows for smooth scrubbing, when player can't keep up. | ||||
|       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) | ||||
|       this.el_.setAttribute('aria-valuetext', formatTime(time, this.player_.duration())); // human readable value of progress bar (time complete) | ||||
|   updateProgress() { | ||||
|     this.updateAriaAttributes(this.el_); | ||||
|  | ||||
|     if (this.keepTooltipsInside) { | ||||
|       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