fix: aspect ratio improvements (#2171)
- Use CSS aspect-ratio (retain fallback for legacy browsers) - Round aspect ratios (fixes YouTube black border issue)
This commit is contained in:
		@ -2,4 +2,4 @@
 | 
				
			|||||||
// Layout
 | 
					// Layout
 | 
				
			||||||
// ==========================================================================
 | 
					// ==========================================================================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
$container-max-width: 1260px;
 | 
					$container-max-width: 1240px;
 | 
				
			||||||
 | 
				
			|||||||
@ -11,7 +11,7 @@ import fetch from '../utils/fetch';
 | 
				
			|||||||
import is from '../utils/is';
 | 
					import is from '../utils/is';
 | 
				
			||||||
import loadScript from '../utils/load-script';
 | 
					import loadScript from '../utils/load-script';
 | 
				
			||||||
import { format, stripHTML } from '../utils/strings';
 | 
					import { format, stripHTML } from '../utils/strings';
 | 
				
			||||||
import { setAspectRatio } from '../utils/style';
 | 
					import { roundAspectRatio, setAspectRatio } from '../utils/style';
 | 
				
			||||||
import { buildUrlParams } from '../utils/urls';
 | 
					import { buildUrlParams } from '../utils/urls';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Parse Vimeo ID from URL
 | 
					// Parse Vimeo ID from URL
 | 
				
			||||||
@ -294,7 +294,7 @@ const vimeo = {
 | 
				
			|||||||
    // Set aspect ratio based on video size
 | 
					    // Set aspect ratio based on video size
 | 
				
			||||||
    Promise.all([player.embed.getVideoWidth(), player.embed.getVideoHeight()]).then((dimensions) => {
 | 
					    Promise.all([player.embed.getVideoWidth(), player.embed.getVideoHeight()]).then((dimensions) => {
 | 
				
			||||||
      const [width, height] = dimensions;
 | 
					      const [width, height] = dimensions;
 | 
				
			||||||
      player.embed.ratio = [width, height];
 | 
					      player.embed.ratio = roundAspectRatio(width, height);
 | 
				
			||||||
      setAspectRatio.call(this);
 | 
					      setAspectRatio.call(this);
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -11,7 +11,7 @@ import loadImage from '../utils/load-image';
 | 
				
			|||||||
import loadScript from '../utils/load-script';
 | 
					import loadScript from '../utils/load-script';
 | 
				
			||||||
import { extend } from '../utils/objects';
 | 
					import { extend } from '../utils/objects';
 | 
				
			||||||
import { format, generateId } from '../utils/strings';
 | 
					import { format, generateId } from '../utils/strings';
 | 
				
			||||||
import { setAspectRatio } from '../utils/style';
 | 
					import { roundAspectRatio, setAspectRatio } from '../utils/style';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Parse YouTube ID from URL
 | 
					// Parse YouTube ID from URL
 | 
				
			||||||
function parseId(url) {
 | 
					function parseId(url) {
 | 
				
			||||||
@ -90,7 +90,7 @@ const youtube = {
 | 
				
			|||||||
          ui.setTitle.call(this);
 | 
					          ui.setTitle.call(this);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          // Set aspect ratio
 | 
					          // Set aspect ratio
 | 
				
			||||||
          this.embed.ratio = [width, height];
 | 
					          this.embed.ratio = roundAspectRatio(width, height);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        setAspectRatio.call(this);
 | 
					        setAspectRatio.call(this);
 | 
				
			||||||
 | 
				
			|||||||
@ -29,7 +29,7 @@ import loadSprite from './utils/load-sprite';
 | 
				
			|||||||
import { clamp } from './utils/numbers';
 | 
					import { clamp } from './utils/numbers';
 | 
				
			||||||
import { cloneDeep, extend } from './utils/objects';
 | 
					import { cloneDeep, extend } from './utils/objects';
 | 
				
			||||||
import { silencePromise } from './utils/promise';
 | 
					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';
 | 
					import { parseUrl } from './utils/urls';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Private properties
 | 
					// Private properties
 | 
				
			||||||
@ -916,12 +916,12 @@ class Plyr {
 | 
				
			|||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (!is.string(input) || !validateRatio(input)) {
 | 
					    if (!is.string(input) || !validateAspectRatio(input)) {
 | 
				
			||||||
      this.debug.error(`Invalid aspect ratio specified (${input})`);
 | 
					      this.debug.error(`Invalid aspect ratio specified (${input})`);
 | 
				
			||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    this.config.ratio = input;
 | 
					    this.config.ratio = reduceAspectRatio(input);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    setAspectRatio.call(this);
 | 
					    setAspectRatio.call(this);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
				
			|||||||
@ -2,9 +2,30 @@
 | 
				
			|||||||
// Style utils
 | 
					// Style utils
 | 
				
			||||||
// ==========================================================================
 | 
					// ==========================================================================
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import { closest } from './arrays';
 | 
				
			||||||
import is from './is';
 | 
					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(':'))) {
 | 
					  if (!is.array(input) && (!is.string(input) || !input.includes(':'))) {
 | 
				
			||||||
    return false;
 | 
					    return false;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@ -14,6 +35,7 @@ export function validateRatio(input) {
 | 
				
			|||||||
  return ratio.map(Number).every(is.number);
 | 
					  return ratio.map(Number).every(is.number);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Reduce an aspect ratio to it's lowest form
 | 
				
			||||||
export function reduceAspectRatio(ratio) {
 | 
					export function reduceAspectRatio(ratio) {
 | 
				
			||||||
  if (!is.array(ratio) || !ratio.every(is.number)) {
 | 
					  if (!is.array(ratio) || !ratio.every(is.number)) {
 | 
				
			||||||
    return null;
 | 
					    return null;
 | 
				
			||||||
@ -26,8 +48,9 @@ export function reduceAspectRatio(ratio) {
 | 
				
			|||||||
  return [width / divider, height / divider];
 | 
					  return [width / divider, height / divider];
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Calculate an aspect ratio
 | 
				
			||||||
export function getAspectRatio(input) {
 | 
					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
 | 
					  // Try provided ratio
 | 
				
			||||||
  let ratio = parse(input);
 | 
					  let ratio = parse(input);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -58,10 +81,20 @@ export function setAspectRatio(input) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  const { wrapper } = this.elements;
 | 
					  const { wrapper } = this.elements;
 | 
				
			||||||
  const ratio = getAspectRatio.call(this, input);
 | 
					  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 <div> to hide the standard controls and UI
 | 
					  // For Vimeo we have an extra <div> to hide the standard controls and UI
 | 
				
			||||||
  if (this.isVimeo && !this.config.vimeo.premium && this.supported.ui) {
 | 
					  if (this.isVimeo && !this.config.vimeo.premium && this.supported.ui) {
 | 
				
			||||||
@ -80,4 +113,18 @@ export function setAspectRatio(input) {
 | 
				
			|||||||
  return { padding, ratio };
 | 
					  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 };
 | 
					export default { setAspectRatio };
 | 
				
			||||||
 | 
				
			|||||||
@ -26,9 +26,13 @@ $embed-padding: ((100 / 16) * 9);
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
.plyr__video-embed,
 | 
					.plyr__video-embed,
 | 
				
			||||||
.plyr__video-wrapper--fixed-ratio {
 | 
					.plyr__video-wrapper--fixed-ratio {
 | 
				
			||||||
  height: 0;
 | 
					  @supports not (aspect-ratio: 16 / 9) {
 | 
				
			||||||
  padding-bottom: to-percentage($embed-padding);
 | 
					    height: 0;
 | 
				
			||||||
  position: relative;
 | 
					    padding-bottom: to-percentage($embed-padding);
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  aspect-ratio: 16 / 9;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.plyr__video-embed iframe,
 | 
					.plyr__video-embed iframe,
 | 
				
			||||||
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user