You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

304 lines
9.0 KiB

1 year ago
  1. /*----------------------------------------
  2. * objectFitPolyfill 2.3.5
  3. *
  4. * Made by Constance Chen
  5. * Released under the ISC license
  6. *
  7. * https://github.com/constancecchen/object-fit-polyfill
  8. *--------------------------------------*/
  9. (function() {
  10. 'use strict';
  11. // if the page is being rendered on the server, don't continue
  12. if (typeof window === 'undefined') return;
  13. // Workaround for Edge 16-18, which only implemented object-fit for <img> tags
  14. var edgeMatch = window.navigator.userAgent.match(/Edge\/(\d{2})\./);
  15. var edgeVersion = edgeMatch ? parseInt(edgeMatch[1], 10) : null;
  16. var edgePartialSupport = edgeVersion
  17. ? edgeVersion >= 16 && edgeVersion <= 18
  18. : false;
  19. // If the browser does support object-fit, we don't need to continue
  20. var hasSupport = 'objectFit' in document.documentElement.style !== false;
  21. if (hasSupport && !edgePartialSupport) {
  22. window.objectFitPolyfill = function() {
  23. return false;
  24. };
  25. return;
  26. }
  27. /**
  28. * Check the container's parent element to make sure it will
  29. * correctly handle and clip absolutely positioned children
  30. *
  31. * @param {node} $container - parent element
  32. */
  33. var checkParentContainer = function($container) {
  34. var styles = window.getComputedStyle($container, null);
  35. var position = styles.getPropertyValue('position');
  36. var overflow = styles.getPropertyValue('overflow');
  37. var display = styles.getPropertyValue('display');
  38. if (!position || position === 'static') {
  39. $container.style.position = 'relative';
  40. }
  41. if (overflow !== 'hidden') {
  42. $container.style.overflow = 'hidden';
  43. }
  44. // Guesstimating that people want the parent to act like full width/height wrapper here.
  45. // Mostly attempts to target <picture> elements, which default to inline.
  46. if (!display || display === 'inline') {
  47. $container.style.display = 'block';
  48. }
  49. if ($container.clientHeight === 0) {
  50. $container.style.height = '100%';
  51. }
  52. // Add a CSS class hook, in case people need to override styles for any reason.
  53. if ($container.className.indexOf('object-fit-polyfill') === -1) {
  54. $container.className = $container.className + ' object-fit-polyfill';
  55. }
  56. };
  57. /**
  58. * Check for pre-set max-width/height, min-width/height,
  59. * positioning, or margins, which can mess up image calculations
  60. *
  61. * @param {node} $media - img/video element
  62. */
  63. var checkMediaProperties = function($media) {
  64. var styles = window.getComputedStyle($media, null);
  65. var constraints = {
  66. 'max-width': 'none',
  67. 'max-height': 'none',
  68. 'min-width': '0px',
  69. 'min-height': '0px',
  70. top: 'auto',
  71. right: 'auto',
  72. bottom: 'auto',
  73. left: 'auto',
  74. 'margin-top': '0px',
  75. 'margin-right': '0px',
  76. 'margin-bottom': '0px',
  77. 'margin-left': '0px',
  78. };
  79. for (var property in constraints) {
  80. var constraint = styles.getPropertyValue(property);
  81. if (constraint !== constraints[property]) {
  82. $media.style[property] = constraints[property];
  83. }
  84. }
  85. };
  86. /**
  87. * Calculate & set object-position
  88. *
  89. * @param {string} axis - either "x" or "y"
  90. * @param {node} $media - img or video element
  91. * @param {string} objectPosition - e.g. "50% 50%", "top left"
  92. */
  93. var setPosition = function(axis, $media, objectPosition) {
  94. var position, other, start, end, side;
  95. objectPosition = objectPosition.split(' ');
  96. if (objectPosition.length < 2) {
  97. objectPosition[1] = objectPosition[0];
  98. }
  99. /* istanbul ignore else */
  100. if (axis === 'x') {
  101. position = objectPosition[0];
  102. other = objectPosition[1];
  103. start = 'left';
  104. end = 'right';
  105. side = $media.clientWidth;
  106. } else if (axis === 'y') {
  107. position = objectPosition[1];
  108. other = objectPosition[0];
  109. start = 'top';
  110. end = 'bottom';
  111. side = $media.clientHeight;
  112. } else {
  113. return; // Neither x or y axis specified
  114. }
  115. if (position === start || other === start) {
  116. $media.style[start] = '0';
  117. return;
  118. }
  119. if (position === end || other === end) {
  120. $media.style[end] = '0';
  121. return;
  122. }
  123. if (position === 'center' || position === '50%') {
  124. $media.style[start] = '50%';
  125. $media.style['margin-' + start] = side / -2 + 'px';
  126. return;
  127. }
  128. // Percentage values (e.g., 30% 10%)
  129. if (position.indexOf('%') >= 0) {
  130. position = parseInt(position, 10);
  131. if (position < 50) {
  132. $media.style[start] = position + '%';
  133. $media.style['margin-' + start] = side * (position / -100) + 'px';
  134. } else {
  135. position = 100 - position;
  136. $media.style[end] = position + '%';
  137. $media.style['margin-' + end] = side * (position / -100) + 'px';
  138. }
  139. return;
  140. }
  141. // Length-based values (e.g. 10px / 10em)
  142. else {
  143. $media.style[start] = position;
  144. }
  145. };
  146. /**
  147. * Calculate & set object-fit
  148. *
  149. * @param {node} $media - img/video/picture element
  150. */
  151. var objectFit = function($media) {
  152. // IE 10- data polyfill
  153. var fit = $media.dataset
  154. ? $media.dataset.objectFit
  155. : $media.getAttribute('data-object-fit');
  156. var position = $media.dataset
  157. ? $media.dataset.objectPosition
  158. : $media.getAttribute('data-object-position');
  159. // Default fallbacks
  160. fit = fit || 'cover';
  161. position = position || '50% 50%';
  162. // If necessary, make the parent container work with absolutely positioned elements
  163. var $container = $media.parentNode;
  164. checkParentContainer($container);
  165. // Check for any pre-set CSS which could mess up image calculations
  166. checkMediaProperties($media);
  167. // Reset any pre-set width/height CSS and handle fit positioning
  168. $media.style.position = 'absolute';
  169. $media.style.width = 'auto';
  170. $media.style.height = 'auto';
  171. // `scale-down` chooses either `none` or `contain`, whichever is smaller
  172. if (fit === 'scale-down') {
  173. if (
  174. $media.clientWidth < $container.clientWidth &&
  175. $media.clientHeight < $container.clientHeight
  176. ) {
  177. fit = 'none';
  178. } else {
  179. fit = 'contain';
  180. }
  181. }
  182. // `none` (width/height auto) and `fill` (100%) and are straightforward
  183. if (fit === 'none') {
  184. setPosition('x', $media, position);
  185. setPosition('y', $media, position);
  186. return;
  187. }
  188. if (fit === 'fill') {
  189. $media.style.width = '100%';
  190. $media.style.height = '100%';
  191. setPosition('x', $media, position);
  192. setPosition('y', $media, position);
  193. return;
  194. }
  195. // `cover` and `contain` must figure out which side needs covering, and add CSS positioning & centering
  196. $media.style.height = '100%';
  197. if (
  198. (fit === 'cover' && $media.clientWidth > $container.clientWidth) ||
  199. (fit === 'contain' && $media.clientWidth < $container.clientWidth)
  200. ) {
  201. $media.style.top = '0';
  202. $media.style.marginTop = '0';
  203. setPosition('x', $media, position);
  204. } else {
  205. $media.style.width = '100%';
  206. $media.style.height = 'auto';
  207. $media.style.left = '0';
  208. $media.style.marginLeft = '0';
  209. setPosition('y', $media, position);
  210. }
  211. };
  212. /**
  213. * Initialize plugin
  214. *
  215. * @param {node} media - Optional specific DOM node(s) to be polyfilled
  216. */
  217. var objectFitPolyfill = function(media) {
  218. if (typeof media === 'undefined' || media instanceof Event) {
  219. // If left blank, or a default event, all media on the page will be polyfilled.
  220. media = document.querySelectorAll('[data-object-fit]');
  221. } else if (media && media.nodeName) {
  222. // If it's a single node, wrap it in an array so it works.
  223. media = [media];
  224. } else if (typeof media === 'object' && media.length && media[0].nodeName) {
  225. // If it's an array of DOM nodes (e.g. a jQuery selector), it's fine as-is.
  226. media = media;
  227. } else {
  228. // Otherwise, if it's invalid or an incorrect type, return false to let people know.
  229. return false;
  230. }
  231. for (var i = 0; i < media.length; i++) {
  232. if (!media[i].nodeName) continue;
  233. var mediaType = media[i].nodeName.toLowerCase();
  234. if (mediaType === 'img') {
  235. if (edgePartialSupport) continue; // Edge supports object-fit for images (but nothing else), so no need to polyfill
  236. if (media[i].complete) {
  237. objectFit(media[i]);
  238. } else {
  239. media[i].addEventListener('load', function() {
  240. objectFit(this);
  241. });
  242. }
  243. } else if (mediaType === 'video') {
  244. if (media[i].readyState > 0) {
  245. objectFit(media[i]);
  246. } else {
  247. media[i].addEventListener('loadedmetadata', function() {
  248. objectFit(this);
  249. });
  250. }
  251. } else {
  252. objectFit(media[i]);
  253. }
  254. }
  255. return true;
  256. };
  257. if (document.readyState === 'loading') {
  258. // Loading hasn't finished yet
  259. document.addEventListener('DOMContentLoaded', objectFitPolyfill);
  260. } else {
  261. // `DOMContentLoaded` has already fired
  262. objectFitPolyfill();
  263. }
  264. window.addEventListener('resize', objectFitPolyfill);
  265. window.objectFitPolyfill = objectFitPolyfill;
  266. })();