From 438e42583859eed0d2e9fd1a00b9cdc32a71f9b4 Mon Sep 17 00:00:00 2001 From: Sam Potts Date: Sun, 18 Apr 2021 16:58:44 +1000 Subject: [PATCH] fix: aspect ratio improvements (#2171) - Use CSS aspect-ratio (retain fallback for legacy browsers) - Round aspect ratios (fixes YouTube black border issue) --- demo/src/sass/settings/layout.scss | 2 +- src/js/plugins/vimeo.js | 4 +-- src/js/plugins/youtube.js | 4 +-- src/js/plyr.js | 6 ++-- src/js/utils/style.js | 57 +++++++++++++++++++++++++++--- src/sass/types/video.scss | 10 ++++-- 6 files changed, 67 insertions(+), 16 deletions(-) diff --git a/demo/src/sass/settings/layout.scss b/demo/src/sass/settings/layout.scss index 77185344..820423ca 100644 --- a/demo/src/sass/settings/layout.scss +++ b/demo/src/sass/settings/layout.scss @@ -2,4 +2,4 @@ // Layout // ========================================================================== -$container-max-width: 1260px; +$container-max-width: 1240px; diff --git a/src/js/plugins/vimeo.js b/src/js/plugins/vimeo.js index ea001441..10246c66 100644 --- a/src/js/plugins/vimeo.js +++ b/src/js/plugins/vimeo.js @@ -11,7 +11,7 @@ import fetch from '../utils/fetch'; import is from '../utils/is'; import loadScript from '../utils/load-script'; import { format, stripHTML } from '../utils/strings'; -import { setAspectRatio } from '../utils/style'; +import { roundAspectRatio, setAspectRatio } from '../utils/style'; import { buildUrlParams } from '../utils/urls'; // Parse Vimeo ID from URL @@ -294,7 +294,7 @@ const vimeo = { // Set aspect ratio based on video size Promise.all([player.embed.getVideoWidth(), player.embed.getVideoHeight()]).then((dimensions) => { const [width, height] = dimensions; - player.embed.ratio = [width, height]; + player.embed.ratio = roundAspectRatio(width, height); setAspectRatio.call(this); }); diff --git a/src/js/plugins/youtube.js b/src/js/plugins/youtube.js index db5781e6..19d2f1a5 100644 --- a/src/js/plugins/youtube.js +++ b/src/js/plugins/youtube.js @@ -11,7 +11,7 @@ import loadImage from '../utils/load-image'; import loadScript from '../utils/load-script'; import { extend } from '../utils/objects'; import { format, generateId } from '../utils/strings'; -import { setAspectRatio } from '../utils/style'; +import { roundAspectRatio, setAspectRatio } from '../utils/style'; // Parse YouTube ID from URL function parseId(url) { @@ -90,7 +90,7 @@ const youtube = { ui.setTitle.call(this); // Set aspect ratio - this.embed.ratio = [width, height]; + this.embed.ratio = roundAspectRatio(width, height); } setAspectRatio.call(this); diff --git a/src/js/plyr.js b/src/js/plyr.js index 6572e473..59381e11 100644 --- a/src/js/plyr.js +++ b/src/js/plyr.js @@ -29,7 +29,7 @@ import loadSprite from './utils/load-sprite'; import { clamp } from './utils/numbers'; import { cloneDeep, extend } from './utils/objects'; import { silencePromise } from './utils/promise'; -import { getAspectRatio, reduceAspectRatio, setAspectRatio, validateRatio } from './utils/style'; +import { getAspectRatio, reduceAspectRatio, setAspectRatio, validateAspectRatio } from './utils/style'; import { parseUrl } from './utils/urls'; // Private properties @@ -916,12 +916,12 @@ class Plyr { return; } - if (!is.string(input) || !validateRatio(input)) { + if (!is.string(input) || !validateAspectRatio(input)) { this.debug.error(`Invalid aspect ratio specified (${input})`); return; } - this.config.ratio = input; + this.config.ratio = reduceAspectRatio(input); setAspectRatio.call(this); } diff --git a/src/js/utils/style.js b/src/js/utils/style.js index f02b0ba5..a9659cf4 100644 --- a/src/js/utils/style.js +++ b/src/js/utils/style.js @@ -2,9 +2,30 @@ // Style utils // ========================================================================== +import { closest } from './arrays'; import is from './is'; -export function validateRatio(input) { +// Standard/common aspect ratios +const standardRatios = [ + [1, 1], + [4, 3], + [3, 4], + [5, 4], + [4, 5], + [3, 2], + [2, 3], + [16, 10], + [10, 16], + [16, 9], + [9, 16], + [21, 9], + [9, 21], + [32, 9], + [9, 32], +].reduce((out, [x, y]) => ({ ...out, [x / y]: [x, y] }), {}); + +// Validate an aspect ratio +export function validateAspectRatio(input) { if (!is.array(input) && (!is.string(input) || !input.includes(':'))) { return false; } @@ -14,6 +35,7 @@ export function validateRatio(input) { return ratio.map(Number).every(is.number); } +// Reduce an aspect ratio to it's lowest form export function reduceAspectRatio(ratio) { if (!is.array(ratio) || !ratio.every(is.number)) { return null; @@ -26,8 +48,9 @@ export function reduceAspectRatio(ratio) { return [width / divider, height / divider]; } +// Calculate an aspect ratio export function getAspectRatio(input) { - const parse = (ratio) => (validateRatio(ratio) ? ratio.split(':').map(Number) : null); + const parse = (ratio) => (validateAspectRatio(ratio) ? ratio.split(':').map(Number) : null); // Try provided ratio let ratio = parse(input); @@ -58,10 +81,20 @@ export function setAspectRatio(input) { const { wrapper } = this.elements; const ratio = getAspectRatio.call(this, input); - const [w, h] = is.array(ratio) ? ratio : [0, 0]; - const padding = (100 / w) * h; - wrapper.style.paddingBottom = `${padding}%`; + if (!is.array(ratio)) { + return {}; + } + + const [x, y] = ratio; + const useNative = window.CSS?.supports(`aspect-ratio: ${x} / ${y}`) ?? false; + const padding = (100 / x) * y; + + if (useNative) { + wrapper.style.aspectRatio = `${x}/${y}`; + } else { + wrapper.style.paddingBottom = `${padding}%`; + } // For Vimeo we have an extra
to hide the standard controls and UI if (this.isVimeo && !this.config.vimeo.premium && this.supported.ui) { @@ -80,4 +113,18 @@ export function setAspectRatio(input) { return { padding, ratio }; } +// Round an aspect ratio to closest standard ratio +export function roundAspectRatio(x, y, tolerance = 0.05) { + const ratio = x / y; + const closestRatio = closest(Object.keys(standardRatios), ratio); + + // Check match is within tolerance + if (Math.abs(closestRatio - ratio) <= tolerance) { + return standardRatios[closestRatio]; + } + + // No match + return [x, y]; +} + export default { setAspectRatio }; diff --git a/src/sass/types/video.scss b/src/sass/types/video.scss index 79b3e158..fe7d8b37 100644 --- a/src/sass/types/video.scss +++ b/src/sass/types/video.scss @@ -26,9 +26,13 @@ $embed-padding: ((100 / 16) * 9); .plyr__video-embed, .plyr__video-wrapper--fixed-ratio { - height: 0; - padding-bottom: to-percentage($embed-padding); - position: relative; + @supports not (aspect-ratio: 16 / 9) { + height: 0; + padding-bottom: to-percentage($embed-padding); + position: relative; + } + + aspect-ratio: 16 / 9; } .plyr__video-embed iframe,