304 lines
8.9 KiB
JavaScript
304 lines
8.9 KiB
JavaScript
|
/*----------------------------------------
|
||
|
* objectFitPolyfill 2.3.4
|
||
|
*
|
||
|
* Made by Constance Chen
|
||
|
* Released under the ISC license
|
||
|
*
|
||
|
* https://github.com/constancecchen/object-fit-polyfill
|
||
|
*--------------------------------------*/
|
||
|
|
||
|
(function() {
|
||
|
'use strict';
|
||
|
|
||
|
// if the page is being rendered on the server, don't continue
|
||
|
if (typeof window === 'undefined') return;
|
||
|
|
||
|
// Workaround for Edge 16-18, which only implemented object-fit for <img> tags
|
||
|
var edgeMatch = window.navigator.userAgent.match(/Edge\/(\d{2})\./);
|
||
|
var edgeVersion = edgeMatch ? parseInt(edgeMatch[1], 10) : null;
|
||
|
var edgePartialSupport = edgeVersion
|
||
|
? edgeVersion >= 16 && edgeVersion <= 18
|
||
|
: false;
|
||
|
|
||
|
// If the browser does support object-fit, we don't need to continue
|
||
|
var hasSupport = 'objectFit' in document.documentElement.style !== false;
|
||
|
if (hasSupport && !edgePartialSupport) {
|
||
|
window.objectFitPolyfill = function() {
|
||
|
return false;
|
||
|
};
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Check the container's parent element to make sure it will
|
||
|
* correctly handle and clip absolutely positioned children
|
||
|
*
|
||
|
* @param {node} $container - parent element
|
||
|
*/
|
||
|
var checkParentContainer = function($container) {
|
||
|
var styles = window.getComputedStyle($container, null);
|
||
|
var position = styles.getPropertyValue('position');
|
||
|
var overflow = styles.getPropertyValue('overflow');
|
||
|
var display = styles.getPropertyValue('display');
|
||
|
|
||
|
if (!position || position === 'static') {
|
||
|
$container.style.position = 'relative';
|
||
|
}
|
||
|
if (overflow !== 'hidden') {
|
||
|
$container.style.overflow = 'hidden';
|
||
|
}
|
||
|
// Guesstimating that people want the parent to act like full width/height wrapper here.
|
||
|
// Mostly attempts to target <picture> elements, which default to inline.
|
||
|
if (!display || display === 'inline') {
|
||
|
$container.style.display = 'block';
|
||
|
}
|
||
|
if ($container.clientHeight === 0) {
|
||
|
$container.style.height = '100%';
|
||
|
}
|
||
|
|
||
|
// Add a CSS class hook, in case people need to override styles for any reason.
|
||
|
if ($container.className.indexOf('object-fit-polyfill') === -1) {
|
||
|
$container.className = $container.className + ' object-fit-polyfill';
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Check for pre-set max-width/height, min-width/height,
|
||
|
* positioning, or margins, which can mess up image calculations
|
||
|
*
|
||
|
* @param {node} $media - img/video element
|
||
|
*/
|
||
|
var checkMediaProperties = function($media) {
|
||
|
var styles = window.getComputedStyle($media, null);
|
||
|
var constraints = {
|
||
|
'max-width': 'none',
|
||
|
'max-height': 'none',
|
||
|
'min-width': '0px',
|
||
|
'min-height': '0px',
|
||
|
top: 'auto',
|
||
|
right: 'auto',
|
||
|
bottom: 'auto',
|
||
|
left: 'auto',
|
||
|
'margin-top': '0px',
|
||
|
'margin-right': '0px',
|
||
|
'margin-bottom': '0px',
|
||
|
'margin-left': '0px',
|
||
|
};
|
||
|
|
||
|
for (var property in constraints) {
|
||
|
var constraint = styles.getPropertyValue(property);
|
||
|
|
||
|
if (constraint !== constraints[property]) {
|
||
|
$media.style[property] = constraints[property];
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Calculate & set object-position
|
||
|
*
|
||
|
* @param {string} axis - either "x" or "y"
|
||
|
* @param {node} $media - img or video element
|
||
|
* @param {string} objectPosition - e.g. "50% 50%", "top bottom"
|
||
|
*/
|
||
|
var setPosition = function(axis, $media, objectPosition) {
|
||
|
var position, other, start, end, side;
|
||
|
objectPosition = objectPosition.split(' ');
|
||
|
|
||
|
if (objectPosition.length < 2) {
|
||
|
objectPosition[1] = objectPosition[0];
|
||
|
}
|
||
|
|
||
|
if (axis === 'x') {
|
||
|
position = objectPosition[0];
|
||
|
other = objectPosition[1];
|
||
|
start = 'left';
|
||
|
end = 'right';
|
||
|
side = $media.clientWidth;
|
||
|
} else if (axis === 'y') {
|
||
|
position = objectPosition[1];
|
||
|
other = objectPosition[0];
|
||
|
start = 'top';
|
||
|
end = 'bottom';
|
||
|
side = $media.clientHeight;
|
||
|
} else {
|
||
|
return; // Neither x or y axis specified
|
||
|
}
|
||
|
|
||
|
if (position === start || other === start) {
|
||
|
$media.style[start] = '0';
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (position === end || other === end) {
|
||
|
$media.style[end] = '0';
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (position === 'center' || position === '50%') {
|
||
|
$media.style[start] = '50%';
|
||
|
$media.style['margin-' + start] = side / -2 + 'px';
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Percentage values (e.g., 30% 10%)
|
||
|
if (position.indexOf('%') >= 0) {
|
||
|
position = parseInt(position);
|
||
|
|
||
|
if (position < 50) {
|
||
|
$media.style[start] = position + '%';
|
||
|
$media.style['margin-' + start] = side * (position / -100) + 'px';
|
||
|
} else {
|
||
|
position = 100 - position;
|
||
|
$media.style[end] = position + '%';
|
||
|
$media.style['margin-' + end] = side * (position / -100) + 'px';
|
||
|
}
|
||
|
|
||
|
return;
|
||
|
}
|
||
|
// Length-based values (e.g. 10px / 10em)
|
||
|
else {
|
||
|
$media.style[start] = position;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Calculate & set object-fit
|
||
|
*
|
||
|
* @param {node} $media - img/video/picture element
|
||
|
*/
|
||
|
var objectFit = function($media) {
|
||
|
// IE 10- data polyfill
|
||
|
var fit = $media.dataset
|
||
|
? $media.dataset.objectFit
|
||
|
: $media.getAttribute('data-object-fit');
|
||
|
var position = $media.dataset
|
||
|
? $media.dataset.objectPosition
|
||
|
: $media.getAttribute('data-object-position');
|
||
|
|
||
|
// Default fallbacks
|
||
|
fit = fit || 'cover';
|
||
|
position = position || '50% 50%';
|
||
|
|
||
|
// If necessary, make the parent container work with absolutely positioned elements
|
||
|
var $container = $media.parentNode;
|
||
|
checkParentContainer($container);
|
||
|
|
||
|
// Check for any pre-set CSS which could mess up image calculations
|
||
|
checkMediaProperties($media);
|
||
|
|
||
|
// Reset any pre-set width/height CSS and handle fit positioning
|
||
|
$media.style.position = 'absolute';
|
||
|
$media.style.width = 'auto';
|
||
|
$media.style.height = 'auto';
|
||
|
|
||
|
// `scale-down` chooses either `none` or `contain`, whichever is smaller
|
||
|
if (fit === 'scale-down') {
|
||
|
if (
|
||
|
$media.clientWidth < $container.clientWidth &&
|
||
|
$media.clientHeight < $container.clientHeight
|
||
|
) {
|
||
|
fit = 'none';
|
||
|
} else {
|
||
|
fit = 'contain';
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// `none` (width/height auto) and `fill` (100%) and are straightforward
|
||
|
if (fit === 'none') {
|
||
|
setPosition('x', $media, position);
|
||
|
setPosition('y', $media, position);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if (fit === 'fill') {
|
||
|
$media.style.width = '100%';
|
||
|
$media.style.height = '100%';
|
||
|
setPosition('x', $media, position);
|
||
|
setPosition('y', $media, position);
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// `cover` and `contain` must figure out which side needs covering, and add CSS positioning & centering
|
||
|
$media.style.height = '100%';
|
||
|
|
||
|
if (
|
||
|
(fit === 'cover' && $media.clientWidth > $container.clientWidth) ||
|
||
|
(fit === 'contain' && $media.clientWidth < $container.clientWidth)
|
||
|
) {
|
||
|
$media.style.top = '0';
|
||
|
$media.style.marginTop = '0';
|
||
|
setPosition('x', $media, position);
|
||
|
} else {
|
||
|
$media.style.width = '100%';
|
||
|
$media.style.height = 'auto';
|
||
|
$media.style.left = '0';
|
||
|
$media.style.marginLeft = '0';
|
||
|
setPosition('y', $media, position);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Initialize plugin
|
||
|
*
|
||
|
* @param {node} media - Optional specific DOM node(s) to be polyfilled
|
||
|
*/
|
||
|
var objectFitPolyfill = function(media) {
|
||
|
if (typeof media === 'undefined' || media instanceof Event) {
|
||
|
// If left blank, or a default event, all media on the page will be polyfilled.
|
||
|
media = document.querySelectorAll('[data-object-fit]');
|
||
|
} else if (media && media.nodeName) {
|
||
|
// If it's a single node, wrap it in an array so it works.
|
||
|
media = [media];
|
||
|
} else if (typeof media === 'object' && media.length && media[0].nodeName) {
|
||
|
// If it's an array of DOM nodes (e.g. a jQuery selector), it's fine as-is.
|
||
|
media = media;
|
||
|
} else {
|
||
|
// Otherwise, if it's invalid or an incorrect type, return false to let people know.
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
for (var i = 0; i < media.length; i++) {
|
||
|
if (!media[i].nodeName) continue;
|
||
|
|
||
|
var mediaType = media[i].nodeName.toLowerCase();
|
||
|
|
||
|
if (mediaType === 'img') {
|
||
|
if (edgePartialSupport) continue; // Edge supports object-fit for images (but nothing else), so no need to polyfill
|
||
|
|
||
|
if (media[i].complete) {
|
||
|
objectFit(media[i]);
|
||
|
} else {
|
||
|
media[i].addEventListener('load', function() {
|
||
|
objectFit(this);
|
||
|
});
|
||
|
}
|
||
|
} else if (mediaType === 'video') {
|
||
|
if (media[i].readyState > 0) {
|
||
|
objectFit(media[i]);
|
||
|
} else {
|
||
|
media[i].addEventListener('loadedmetadata', function() {
|
||
|
objectFit(this);
|
||
|
});
|
||
|
}
|
||
|
} else {
|
||
|
objectFit(media[i]);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return true;
|
||
|
};
|
||
|
|
||
|
if (document.readyState === 'loading') {
|
||
|
// Loading hasn't finished yet
|
||
|
document.addEventListener('DOMContentLoaded', objectFitPolyfill);
|
||
|
} else {
|
||
|
// `DOMContentLoaded` has already fired
|
||
|
objectFitPolyfill();
|
||
|
}
|
||
|
|
||
|
window.addEventListener('resize', objectFitPolyfill);
|
||
|
|
||
|
window.objectFitPolyfill = objectFitPolyfill;
|
||
|
})();
|