ofi.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  1. /*! npm.im/object-fit-images 3.2.4 */
  2. var objectFitImages = (function () {
  3. 'use strict';
  4. var OFI = 'fregante:object-fit-images';
  5. var propRegex = /(object-fit|object-position)\s*:\s*([-.\w\s%]+)/g;
  6. var testImg = typeof Image === 'undefined' ? {style: {'object-position': 1}} : new Image();
  7. var supportsObjectFit = 'object-fit' in testImg.style;
  8. var supportsObjectPosition = 'object-position' in testImg.style;
  9. var supportsOFI = 'background-size' in testImg.style;
  10. var supportsCurrentSrc = typeof testImg.currentSrc === 'string';
  11. var nativeGetAttribute = testImg.getAttribute;
  12. var nativeSetAttribute = testImg.setAttribute;
  13. var autoModeEnabled = false;
  14. function createPlaceholder(w, h) {
  15. return ("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='" + w + "' height='" + h + "'%3E%3C/svg%3E");
  16. }
  17. function polyfillCurrentSrc(el) {
  18. if (el.srcset && !supportsCurrentSrc && window.picturefill) {
  19. var pf = window.picturefill._;
  20. // parse srcset with picturefill where currentSrc isn't available
  21. if (!el[pf.ns] || !el[pf.ns].evaled) {
  22. // force synchronous srcset parsing
  23. pf.fillImg(el, {reselect: true});
  24. }
  25. if (!el[pf.ns].curSrc) {
  26. // force picturefill to parse srcset
  27. el[pf.ns].supported = false;
  28. pf.fillImg(el, {reselect: true});
  29. }
  30. // retrieve parsed currentSrc, if any
  31. el.currentSrc = el[pf.ns].curSrc || el.src;
  32. }
  33. }
  34. function getStyle(el) {
  35. var style = getComputedStyle(el).fontFamily;
  36. var parsed;
  37. var props = {};
  38. while ((parsed = propRegex.exec(style)) !== null) {
  39. props[parsed[1]] = parsed[2];
  40. }
  41. return props;
  42. }
  43. function setPlaceholder(img, width, height) {
  44. // Default: fill width, no height
  45. var placeholder = createPlaceholder(width || 1, height || 0);
  46. // Only set placeholder if it's different
  47. if (nativeGetAttribute.call(img, 'src') !== placeholder) {
  48. nativeSetAttribute.call(img, 'src', placeholder);
  49. }
  50. }
  51. function onImageReady(img, callback) {
  52. // naturalWidth is only available when the image headers are loaded,
  53. // this loop will poll it every 100ms.
  54. if (img.naturalWidth) {
  55. callback(img);
  56. } else {
  57. setTimeout(onImageReady, 100, img, callback);
  58. }
  59. }
  60. function fixOne(el) {
  61. var style = getStyle(el);
  62. var ofi = el[OFI];
  63. style['object-fit'] = style['object-fit'] || 'fill'; // default value
  64. // Avoid running where unnecessary, unless OFI had already done its deed
  65. if (!ofi.img) {
  66. // fill is the default behavior so no action is necessary
  67. if (style['object-fit'] === 'fill') {
  68. return;
  69. }
  70. // Where object-fit is supported and object-position isn't (Safari < 10)
  71. if (
  72. !ofi.skipTest && // unless user wants to apply regardless of browser support
  73. supportsObjectFit && // if browser already supports object-fit
  74. !style['object-position'] // unless object-position is used
  75. ) {
  76. return;
  77. }
  78. }
  79. // keep a clone in memory while resetting the original to a blank
  80. if (!ofi.img) {
  81. ofi.img = new Image(el.width, el.height);
  82. ofi.img.srcset = nativeGetAttribute.call(el, "data-ofi-srcset") || el.srcset;
  83. ofi.img.src = nativeGetAttribute.call(el, "data-ofi-src") || el.src;
  84. // preserve for any future cloneNode calls
  85. // https://github.com/fregante/object-fit-images/issues/53
  86. nativeSetAttribute.call(el, "data-ofi-src", el.src);
  87. if (el.srcset) {
  88. nativeSetAttribute.call(el, "data-ofi-srcset", el.srcset);
  89. }
  90. setPlaceholder(el, el.naturalWidth || el.width, el.naturalHeight || el.height);
  91. // remove srcset because it overrides src
  92. if (el.srcset) {
  93. el.srcset = '';
  94. }
  95. try {
  96. keepSrcUsable(el);
  97. } catch (err) {
  98. if (window.console) {
  99. console.warn('https://bit.ly/ofi-old-browser');
  100. }
  101. }
  102. }
  103. polyfillCurrentSrc(ofi.img);
  104. el.style.backgroundImage = "url(\"" + ((ofi.img.currentSrc || ofi.img.src).replace(/"/g, '\\"')) + "\")";
  105. el.style.backgroundPosition = style['object-position'] || 'center';
  106. el.style.backgroundRepeat = 'no-repeat';
  107. el.style.backgroundOrigin = 'content-box';
  108. if (/scale-down/.test(style['object-fit'])) {
  109. onImageReady(ofi.img, function () {
  110. if (ofi.img.naturalWidth > el.width || ofi.img.naturalHeight > el.height) {
  111. el.style.backgroundSize = 'contain';
  112. } else {
  113. el.style.backgroundSize = 'auto';
  114. }
  115. });
  116. } else {
  117. el.style.backgroundSize = style['object-fit'].replace('none', 'auto').replace('fill', '100% 100%');
  118. }
  119. onImageReady(ofi.img, function (img) {
  120. setPlaceholder(el, img.naturalWidth, img.naturalHeight);
  121. });
  122. }
  123. function keepSrcUsable(el) {
  124. var descriptors = {
  125. get: function get(prop) {
  126. return el[OFI].img[prop ? prop : 'src'];
  127. },
  128. set: function set(value, prop) {
  129. el[OFI].img[prop ? prop : 'src'] = value;
  130. nativeSetAttribute.call(el, ("data-ofi-" + prop), value); // preserve for any future cloneNode
  131. fixOne(el);
  132. return value;
  133. }
  134. };
  135. Object.defineProperty(el, 'src', descriptors);
  136. Object.defineProperty(el, 'currentSrc', {
  137. get: function () { return descriptors.get('currentSrc'); }
  138. });
  139. Object.defineProperty(el, 'srcset', {
  140. get: function () { return descriptors.get('srcset'); },
  141. set: function (ss) { return descriptors.set(ss, 'srcset'); }
  142. });
  143. }
  144. function hijackAttributes() {
  145. function getOfiImageMaybe(el, name) {
  146. return el[OFI] && el[OFI].img && (name === 'src' || name === 'srcset') ? el[OFI].img : el;
  147. }
  148. if (!supportsObjectPosition) {
  149. HTMLImageElement.prototype.getAttribute = function (name) {
  150. return nativeGetAttribute.call(getOfiImageMaybe(this, name), name);
  151. };
  152. HTMLImageElement.prototype.setAttribute = function (name, value) {
  153. return nativeSetAttribute.call(getOfiImageMaybe(this, name), name, String(value));
  154. };
  155. }
  156. }
  157. function fix(imgs, opts) {
  158. var startAutoMode = !autoModeEnabled && !imgs;
  159. opts = opts || {};
  160. imgs = imgs || 'img';
  161. if ((supportsObjectPosition && !opts.skipTest) || !supportsOFI) {
  162. return false;
  163. }
  164. // use imgs as a selector or just select all images
  165. if (imgs === 'img') {
  166. imgs = document.getElementsByTagName('img');
  167. } else if (typeof imgs === 'string') {
  168. imgs = document.querySelectorAll(imgs);
  169. } else if (!('length' in imgs)) {
  170. imgs = [imgs];
  171. }
  172. // apply fix to all
  173. for (var i = 0; i < imgs.length; i++) {
  174. imgs[i][OFI] = imgs[i][OFI] || {
  175. skipTest: opts.skipTest
  176. };
  177. fixOne(imgs[i]);
  178. }
  179. if (startAutoMode) {
  180. document.body.addEventListener('load', function (e) {
  181. if (e.target.tagName === 'IMG') {
  182. fix(e.target, {
  183. skipTest: opts.skipTest
  184. });
  185. }
  186. }, true);
  187. autoModeEnabled = true;
  188. imgs = 'img'; // reset to a generic selector for watchMQ
  189. }
  190. // if requested, watch media queries for object-fit change
  191. if (opts.watchMQ) {
  192. window.addEventListener('resize', fix.bind(null, imgs, {
  193. skipTest: opts.skipTest
  194. }));
  195. }
  196. }
  197. fix.supportsObjectFit = supportsObjectFit;
  198. fix.supportsObjectPosition = supportsObjectPosition;
  199. hijackAttributes();
  200. return fix;
  201. }());