1
0
mirror of https://github.com/videojs/video.js.git synced 2024-12-27 02:43:45 +02:00

feat: Add responsive option, which enables breakpoints support. (#5496)

Follow-up for #5471

This makes the breakpoints option and `breakpoints()` method clearer and introduces the responsive option and `responsive()` method, which will turn on the breakpoints.

The return value of `currentBreakpoint()` was simplified to only ever return a string (empty if none).

Also, added convenience methods: `responsive()`, `getBreakpointClass()`, and `currentBreakpointClass()`.
This commit is contained in:
Pat O'Neill 2018-10-11 17:03:33 -04:00 committed by Gary Katsevman
parent 159483e050
commit 6df3ac78d4
5 changed files with 257 additions and 65 deletions

View File

@ -2,10 +2,12 @@
Video.js generally lays out the player to the dimensions that are set as attributes or via CSS, like other DOM elements. However, we provide a few ways to make the player be more fluid.
## Fluid mode
## Fluid Mode
Video.js has a fluid mode that keeps the player sized to a particular aspect ratio.
By default, fluid mode will use the intrinsic size of the video once loaded but you can change it with classes or with the `aspectRatio` option.
Enabling fluid mode will disable fill mode. If both are enabled, fluid mode takes precedence.
You can enable fluid in a few ways:
@ -19,10 +21,12 @@ You can enable fluid in a few ways:
### Classes
There are three classes associated with fluid mode, `vjs-fluid`, `vjs-16-9`, and `vjs-4-3`.
`vjs-fluid` turns on the general fluid mode which will wait for the video to load to calculate the aspect ratio of the video.
Alternatively, because 16:9 and 4:3 aspect ratios are so common, we provided them as classes by default for you to use if you know that your videos are 16:9 or 4:3.
### Enabling fluid mode
### Enabling Fluid Mode
You can pass in the `fluid` option to the player or call `player.fluid(true)`. This will enable the generic fluid mode.
@ -38,7 +42,7 @@ var player = videojs('vid2');
player.fluid(true);
```
#### Setting Aspect Ratio
### Setting Aspect Ratio
You can specify an aspect ratio for us to use if you don't want to use the intrinsic values from the video element or if you have a specific ratio in mind. It works as either a method call or an option to the player.
@ -58,7 +62,7 @@ var player = videojs('vid2');
player.aspectRatio('1:1');
```
### Disabling fluid mode
### Disabling Fluid Mode
You can disable fluid mode by remove the associated classes or by calling passing in `false` to the method.
@ -66,17 +70,17 @@ You can disable fluid mode by remove the associated classes or by calling passin
player.fluid(false);
```
## Fill mode
## Fill Mode
Fill mode will make the player fit and fill out its container. This is often useful if you have a responsive website and already have a container for Video.js that resizes properly to your design. It can be set either via a class or an option.
If fill is enabled, it'll turn off fluid mode. If the player is configured with both fluid and fill options, fluid mode takes precedence.
## Class
### Class
There's just one class for this one: `vjs-fill`. When available, Video.js will enter fill mode.
## Enabling fill mode
### Enabling Fill Mode
You can pass in the `fill` option to the player or call `player.fill(true)`. This will enable fill mode.
@ -92,10 +96,90 @@ var player = videojs('vid2');
player.fill(true);
```
## Disabling fill mode
### Disabling Fill Mode
You can disable fill mode by removing the associated class or by passing `false` in to the method.
```js
player.fill(false);
```
## Responsive Mode
Responsive mode will make the player's UI customize itself based on the size of the player. This is useful if you have embeds of varying sizes - or if you want a fluid/fill player to adjust its UI based on its size.
Responsive mode is independent of fluid mode or fill mode - it only deals with the arrangements of the UI within the player, not with the size of the player. However, it is often useful to use responsive mode in conjunction with either fluid mode or fill mode!
### Class
A player in responsive mode will add and remove classes based on its size breakpoints. The default breakpoints, classes, and sizes are outlined below:
Name | Class | Min. Width | Max. Width
---------|----------------------|------------|-----------
`tiny` | `vjs-layout-tiny` | 0 | 210
`xsmall` | `vjs-layout-x-small` | 211 | 320
`small` | `vjs-layout-small` | 321 | 425
`medium` | `vjs-layout-medium` | 426 | 768
`large` | `vjs-layout-large` | 769 | 1440
`xlarge` | `vjs-layout-x-large` | 1441 | 2560
`huge` | `vjs-layout-huge` | 2561 | Infinity
### Enabling Responsive Mode
You can enable responsive mode by passing the `responsive` option or by calling `player.responsive(true)`.
```js
var player = videojs('vid1', {
responsive: true
});
```
```js
var player = videojs('vid2');
player.responsive(true);
```
### Disabling Responsive Mode
You can disable responsive mode by passing `false` to the method.
```js
player.responsive(false);
```
### Customizing Breakpoints
The default breakpoints can be customized by passing the `breakpoints` option or by calling `player.breakpoints({...})`.
```js
var player = videojs('vid1', {
breakpoints: {
medium: 500
}
});
```
```js
var player = videojs('vid2');
player.breakpoints({
medium: 500
});
```
The breakpoints object should have keys matching the **Name** from the table above and values matching the **Max. Width** from the table above. The **Min. Width** is calculated by adding one to the previous breakpoint's **Max. Width**.
Anytime breakpoints are customized, previous customizations are discarded.
### Restoring Default Breakpoints
The default breakpoints can be restored by calling `player.breakpoints(true)`.
```js
var player = videojs('vid1');
player.breakpoints(true);
```
This is only useful if breakpoints had previously been customized.

View File

@ -165,11 +165,11 @@ Prevents the player from running the autoSetup for media elements with `data-set
### `breakpoints`
> Type: `boolean|Array`, Default: `false`
> Type: `Object`
Set layout breakpoints that will configure how class names are toggled on the player to adjust the UI based on the player's dimensions.
When used with the [`responsive` option](#responsive), sets breakpoints that will configure how class names are toggled on the player to adjust the UI based on the player's dimensions.
By default, no breakpoints are supported, but passing `true` will set some sensible default breakpoints:
By default, the breakpoints are:
Class Name | Width Range
---------------------|------------
@ -203,7 +203,7 @@ When the player's size changes, the merged breakpoints will be inspected in the
That breakpoint's associated class name will be added as a class to the player. The previous breakpoint's class will be removed.
See the file `sandbox/responsive.html.example` for an example of a fluid/responsive player using the default breakpoints.
See the file `sandbox/responsive.html.example` for an example of a responsive player using the default breakpoints.
### `children`
@ -303,6 +303,14 @@ Although, since the `plugins` option is an object, the order of initialization i
See [the plugins guide][plugins] for more information on Video.js plugins.
### `responsive`
> Type: `boolean`, Default: `false`
Setting this option to `true` will cause the player to customize itself based on responsive breakpoints (see: [`breakpoints` option](#breakpoints)).
When this option is `false` (the default), responsive breakpoints will be ignored.
### `sources`
> Type: `Array`

View File

@ -132,7 +132,7 @@
<script>
var vid = document.querySelector('video-js');
var player = videojs(vid, {breakpoints: true});
var player = videojs(vid, {responsive: true});
var tbody = document.querySelector('table tbody');
player.on('playerresize', function() {

View File

@ -22,7 +22,7 @@ import * as stylesheet from './utils/stylesheet.js';
import FullscreenApi from './fullscreen-api.js';
import MediaError from './media-error.js';
import safeParseTuple from 'safe-json-parse/tuple';
import {assign, isObject} from './utils/obj';
import {assign} from './utils/obj';
import mergeOptions from './utils/merge-options.js';
import {silencePromise} from './utils/promise';
import textTrackConverter from './tracks/text-track-list-converter.js';
@ -522,7 +522,8 @@ class Player extends Component {
this.on('fullscreenchange', this.handleFullscreenChange_);
this.on('stageclick', this.handleStageClick_);
this.setBreakpoints(this.options_.breakpoints);
this.breakpoints(this.options_.breakpoints);
this.responsive(this.options_.responsive);
this.changingSrc_ = false;
this.playWaitingForReady_ = false;
@ -3713,12 +3714,12 @@ class Player extends Component {
}
/**
* Change layout breakpoint classes when the player resizes.
* Change breakpoint classes when the player resizes.
*
* @private
*/
updateCurrentBreakpoint_() {
if (!this.breakpoints_) {
if (!this.responsive()) {
return;
}
@ -3749,59 +3750,142 @@ class Player extends Component {
}
/**
* Set layout breakpoints on the player.
* Removes the current breakpoint.
*
* @param {boolean|Object} breakpoints
* A boolean indicating whether breakpoints should or should not be
* used - or an object describing custom max widths for the supported
* breakpoints.
* @private
*/
setBreakpoints(breakpoints) {
const hadBps = Boolean(this.breakpoints_);
removeCurrentBreakpoint_() {
const className = this.currentBreakpointClass();
if (!breakpoints) {
this.breakpoint_ = null;
this.breakpoints_ = null;
this.off('playerresize', this.updateCurrentBreakpoint_);
this.breakpoint_ = '';
if (className) {
this.removeClass(className);
}
}
/**
* Get or set breakpoints on the player.
*
* Calling this method with an object or `true` will remove any previous
* custom breakpoints and start from the defaults again.
*
* @param {Object|boolean} [breakpoints]
* If an object is given, it can be used to provide custom
* breakpoints. If `true` is given, will set default breakpoints.
* If this argument is not given, will simply return the current
* breakpoints.
*
* @param {number} [breakpoints.tiny]
* The maximum width for the "vjs-layout-tiny" class.
*
* @param {number} [breakpoints.xsmall]
* The maximum width for the "vjs-layout-x-small" class.
*
* @param {number} [breakpoints.small]
* The maximum width for the "vjs-layout-small" class.
*
* @param {number} [breakpoints.medium]
* The maximum width for the "vjs-layout-medium" class.
*
* @param {number} [breakpoints.large]
* The maximum width for the "vjs-layout-large" class.
*
* @param {number} [breakpoints.xlarge]
* The maximum width for the "vjs-layout-x-large" class.
*
* @param {number} [breakpoints.huge]
* The maximum width for the "vjs-layout-huge" class.
*
* @return {Object}
* An object mapping breakpoint names to maximum width values.
*/
breakpoints(breakpoints) {
// Used as a getter.
if (breakpoints === undefined) {
return assign(this.breakpoints_);
}
this.breakpoint_ = '';
this.breakpoints_ = assign({}, DEFAULT_BREAKPOINTS, breakpoints);
// When breakpoint definitions change, we need to update the currently
// selected breakpoint.
this.updateCurrentBreakpoint_();
// Clone the breakpoints before returning.
return assign(this.breakpoints_);
}
/**
* Get or set a flag indicating whether or not this player should adjust
* its UI based on its dimensions.
*
* @param {boolean} value
* Should be `true` if the player should adjust its UI based on its
* dimensions; otherwise, should be `false`.
*
* @return {boolean}
* Will be `true` if this player should adjust its UI based on its
* dimensions; otherwise, will be `false`.
*/
responsive(value) {
// Used as a getter.
if (value === undefined) {
return this.responsive_;
}
value = Boolean(value);
const current = this.responsive_;
// Nothing changed.
if (value === current) {
return;
}
this.breakpoints_ = assign({}, DEFAULT_BREAKPOINTS);
// The value actually changed, set it.
this.responsive_ = value;
if (isObject(breakpoints)) {
assign(this.breakpoints_, breakpoints);
}
this.updateCurrentBreakpoint_();
// If we were not previously supporting breakpoints, add a listener.
if (!hadBps) {
// Start listening for breakpoints and set the initial breakpoint if the
// player is now responsive.
if (value) {
this.on('playerresize', this.updateCurrentBreakpoint_);
this.updateCurrentBreakpoint_();
// Stop listening for breakpoints if the player is no longer responsive.
} else {
this.off('playerresize', this.updateCurrentBreakpoint_);
this.removeCurrentBreakpoint_();
}
return value;
}
/**
* Get current layout breakpoints for the player.
* Get current breakpoint name, if any.
*
* @return {Object}
* An object describing the current layout breakpoints and their
* max widths.
*/
getBreakpoints() {
return this.breakpoints_ && assign(this.breakpoints_);
}
/**
* Get current layout breakpoint name, if any.
*
* @return {string|null}
* If there is currently a layout breakpoint set, returns a the key
* from the breakpoints object matching it. Otherwise, returns `null`.
* @return {string}
* If there is currently a breakpoint set, returns a the key from the
* breakpoints object matching it. Otherwise, returns an empty string.
*/
currentBreakpoint() {
return this.breakpoint_;
}
/**
* Get the current breakpoint class name.
*
* @return {string}
* The matching class name (e.g. `"vjs-layout-tiny"` or
* `"vjs-layout-large"`) for the current breakpoint. Empty string if
* there is no current breakpoint.
*/
currentBreakpointClass() {
return BREAKPOINT_CLASSES[this.breakpoint_] || '';
}
/**
* Gets tag settings
*
@ -4003,7 +4087,8 @@ Player.prototype.options_ = {
// Default message to show when a video cannot be played.
notSupportedMessage: 'No compatible source was found for this media.',
breakpoints: false
breakpoints: {},
responsive: false
};
[

View File

@ -27,66 +27,81 @@ QUnit.module('Player: Breakpoints', {
});
QUnit.test('breakpoints are disabled by default', function(assert) {
assert.strictEqual(this.player.getBreakpoints(), null, 'no breakpoints defined');
assert.strictEqual(this.player.currentBreakpoint(), null, 'no current breakpoint set');
assert.deepEqual(this.player.breakpoints(), getExpectedBreakpoints(), 'default breakpoints defined');
assert.notOk(this.player.responsive(), 'player is not responsive');
assert.strictEqual(this.player.currentBreakpoint(), '', 'no current breakpoint set');
assert.strictEqual(this.player.currentBreakpointClass(), '', 'no current breakpoint set');
});
QUnit.test('setting default breakpoints', function(assert) {
this.player.setBreakpoints(true);
assert.deepEqual(this.player.getBreakpoints(), getExpectedBreakpoints(), 'breakpoints defined');
QUnit.test('enabling responsive mode', function(assert) {
this.player.responsive(true);
assert.ok(this.player.responsive(), 'player is responsive');
// Player should be 300x150 by default.
assert.strictEqual(this.player.currentBreakpoint(), 'xsmall', 'current breakpoint set');
assert.strictEqual(this.player.currentBreakpointClass(), 'vjs-layout-x-small', 'current breakpoint set');
});
QUnit.test('setting custom breakpoints', function(assert) {
this.player.setBreakpoints({tiny: 300});
assert.deepEqual(this.player.getBreakpoints(), getExpectedBreakpoints({tiny: 300}), 'breakpoints defined');
QUnit.test('setting custom breakpoints and enabling responsive mode', function(assert) {
this.player.breakpoints({tiny: 300});
this.player.responsive(true);
assert.deepEqual(this.player.breakpoints(), getExpectedBreakpoints({tiny: 300}), 'breakpoints defined');
// Player should be 300x150 by default.
assert.strictEqual(this.player.currentBreakpoint(), 'tiny', 'current breakpoint set');
assert.strictEqual(this.player.currentBreakpointClass(), 'vjs-layout-tiny', 'current breakpoint set');
});
QUnit.test('setting breakpoints via option', function(assert) {
const player = TestHelpers.makePlayer({breakpoints: {tiny: 300}});
QUnit.test('setting breakpoints/responsive via option', function(assert) {
const player = TestHelpers.makePlayer({breakpoints: {tiny: 300}, responsive: true});
assert.deepEqual(player.getBreakpoints(), getExpectedBreakpoints({tiny: 300}), 'breakpoints defined');
assert.deepEqual(player.breakpoints(), getExpectedBreakpoints({tiny: 300}), 'breakpoints defined');
// Player should be 300x150 by default.
assert.strictEqual(player.currentBreakpoint(), 'tiny', 'current breakpoint set');
assert.strictEqual(player.currentBreakpointClass(), 'vjs-layout-tiny', 'current breakpoint set');
});
QUnit.test('changing the player size triggers breakpoints', function(assert) {
let currentWidth;
this.player.setBreakpoints(true);
this.player.responsive(true);
this.player.currentWidth = () => currentWidth;
currentWidth = 200;
this.player.trigger('playerresize');
assert.strictEqual(this.player.currentBreakpoint(), 'tiny', 'current breakpoint is correct');
assert.strictEqual(this.player.currentBreakpointClass(), 'vjs-layout-tiny', 'current breakpoint set');
currentWidth = 300;
this.player.trigger('playerresize');
assert.strictEqual(this.player.currentBreakpoint(), 'xsmall', 'current breakpoint is correct');
assert.strictEqual(this.player.currentBreakpointClass(), 'vjs-layout-x-small', 'current breakpoint set');
currentWidth = 400;
this.player.trigger('playerresize');
assert.strictEqual(this.player.currentBreakpoint(), 'small', 'current breakpoint is correct');
assert.strictEqual(this.player.currentBreakpointClass(), 'vjs-layout-small', 'current breakpoint set');
currentWidth = 600;
this.player.trigger('playerresize');
assert.strictEqual(this.player.currentBreakpoint(), 'medium', 'current breakpoint is correct');
assert.strictEqual(this.player.currentBreakpointClass(), 'vjs-layout-medium', 'current breakpoint set');
currentWidth = 900;
this.player.trigger('playerresize');
assert.strictEqual(this.player.currentBreakpoint(), 'large', 'current breakpoint is correct');
assert.strictEqual(this.player.currentBreakpointClass(), 'vjs-layout-large', 'current breakpoint set');
currentWidth = 1600;
this.player.trigger('playerresize');
assert.strictEqual(this.player.currentBreakpoint(), 'xlarge', 'current breakpoint is correct');
assert.strictEqual(this.player.currentBreakpointClass(), 'vjs-layout-x-large', 'current breakpoint set');
currentWidth = 3000;
this.player.trigger('playerresize');
assert.strictEqual(this.player.currentBreakpoint(), 'huge', 'current breakpoint is correct');
assert.strictEqual(this.player.currentBreakpointClass(), 'vjs-layout-huge', 'current breakpoint set');
});