imgix-optimizer 0.0.5 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5f8ebafc45462fd9534d73b40fd5d700688a15c94852d57e63f5e47e633d26f2
4
- data.tar.gz: 95d929ae1beda1f74fc82cedd1f414c90a069ab8ece1d90a67159782393d9ec7
3
+ metadata.gz: 6630b32252b9019d7cdcb4b0652f7d2dcf2dd09176f624cbbdfa64f041b3dcbb
4
+ data.tar.gz: 9715b46c1e2520797153afdbbb1a679646417a8ea1876504cce8959009011455
5
5
  SHA512:
6
- metadata.gz: 8c42f618d3b5aef875911b05b2e76b942110fcaa06af2a870d2d3443bc8bf131eb7b2d21d520f4cdcdfae5be04e053eae4424951dbcf545e74220b2e9b404edd
7
- data.tar.gz: d4d88a4384f3030009e6f5e9564cc5788e4fd712be36a305dcc87062ad7816d16765456b939cc052884f635b3d6250985fe28b502d88a410e9dfcf679ad982d4
6
+ metadata.gz: 6c8152c7c744eb1e66a8a136330044e6a96ea75842622421904aa4709d9a5bdfc4e66c397e3a80e7f1cefb96b1ac052664d81e7fba875345e937c7e82b5cc624
7
+ data.tar.gz: 94fcbdfb69bc9c3fd5934bcd9a969982d9e3e0d7b6d2c3d43363c8d95c2d779b7b8b641a9793b71fc9d92f4064924f33422026d8462a6ad34bd094899af50b93
data/.gitignore CHANGED
@@ -1,3 +1,4 @@
1
1
  .DS_Store
2
+ .vscode
2
3
  node_modules
3
4
  pkg
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- imgix-optimizer (0.0.5)
4
+ imgix-optimizer (0.0.6)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -16,4 +16,4 @@ DEPENDENCIES
16
16
  rake
17
17
 
18
18
  BUNDLED WITH
19
- 1.16.2
19
+ 1.17.1
data/dist/crds.html ADDED
@@ -0,0 +1,57 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
6
+ <title>IMGIX Optimizer</title>
7
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
8
+
9
+ <link rel="stylesheet" href="http://d1tmclqz61gqwd.cloudfront.net/styles/crds-styles-3.0.9.css">
10
+ <link rel="stylesheet" href="main.css" />
11
+
12
+ <script
13
+ src="https://code.jquery.com/jquery-2.2.4.js"
14
+ integrity="sha256-iT6Q9iMJYuQiMWNd9lDyBUStIq/8PuOW33aOqmvFpqI="
15
+ crossorigin="anonymous"
16
+ ></script>
17
+ <script src="https://rawgit.com/imgix/imgix.js/master/dist/imgix.min.js"></script>
18
+ <script src="imgix-optimizer.js"></script>
19
+ </head>
20
+
21
+ <body>
22
+
23
+ <div class="feature-cards card-deck--expanded-layout">
24
+ <div class="card">
25
+ <a href="/andover" class="block">
26
+ <img
27
+ src="https://crds-media-int.imgix.net/2cNdFxxlsIuY2Syw4UuioA/17449e3a1c91aa0cd4d09c91973526c3/crossroads-church-andover-bg.jpg?auto=format,compress&w=12&h=9&fit=crop"
28
+ data-optimize-img
29
+ />
30
+ </a>
31
+ <div class="card-block">
32
+ <h4 class="card-title card-title--overlap text-uppercase"><a href="/andover">Andover</a></h4>
33
+ <div class="card-text">
34
+ <p>
35
+ 123 Testing Lane<br />
36
+ Cincinnati, OH 45424
37
+ </p>
38
+ (<a target="_blank" href="https://map-url.com">Map</a>)
39
+ <p>
40
+ <strong>Service Times:</strong><br />
41
+ <p>
42
+ SUNDAY ALL DAY
43
+ <a href="https://www.crossroads.net/andover"><strong>See Christmas Service Times Here</strong></a>
44
+ </p>
45
+ </p>
46
+ </div>
47
+ </div>
48
+ </div>
49
+ </div>
50
+
51
+ <script>
52
+ (function() {
53
+ new Imgix.Optimizer();
54
+ })();
55
+ </script>
56
+ </body>
57
+ </html>
@@ -0,0 +1 @@
1
+ !function(){"use strict";var i=function(t,e){if(!(t instanceof e))throw new TypeError("Cannot call a class as a function")},t=function(){function n(t,e){for(var i=0;i<e.length;i++){var n=e[i];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(t,n.key,n)}}return function(t,e,i){return e&&n(t.prototype,e),i&&n(t,i),t}}(),n=function(){function e(t){i(this,e),this.timeToFade=500,this.processingAttr="data-imgix-img-processed",this.placeholderImg=$(t),this.initPlaceholder(),this.initOptimization()}return t(e,[{key:"initOptimization",value:function(){$("<img>").on("load",$.proxy(this.listenForIntersection,this)).attr("src",this.placeholderImg.attr("src"))}},{key:"listenForIntersection",value:function(){new IntersectionObserver($.proxy(this.onIntersection,this)).observe(this.placeholderImg[0])}},{key:"onIntersection",value:function(t,e){var i=$(t[0].target);t[0].isIntersecting&&!$(i).attr(this.processingAttr)&&(i.attr(this.processingAttr,!0),this.renderFullSizeImg())}},{key:"initPlaceholder",value:function(){this.setPlaceholderCss(),this.setPlaceholderParentTmpCss()}},{key:"setPlaceholderCss",value:function(){"absolute"!=this.placeholderImg.css("position")&&this.placeholderImg.css("position","relative")}},{key:"setPlaceholderParentTmpCss",value:function(){this.parentStyles={display:this.placeholderImg.parent().css("display"),position:this.placeholderImg.parent().css("position")},this.placeholderImg.parent().css({display:"block",position:"relative"})}},{key:"renderFullSizeImg",value:function(){this.initFullSizeImg(),this.setFullSizeImgTempCss(),this.setFullSizeImgSrc(),this.addFullSizeImgToDom(),this.initTransition()}},{key:"initFullSizeImg",value:function(){this.fullSizeImg=this.placeholderImg.clone()}},{key:"setFullSizeImgTempCss",value:function(){this.fullSizeImg.css({position:"absolute",top:this.placeholderImg.position().top,left:this.placeholderImg.position().left,width:"100%",height:"100%"})}},{key:"getPlaceholderImgRect",value:function(){return{width:this.placeholderImg[0].getBoundingClientRect().width,height:this.placeholderImg[0].getBoundingClientRect().height}}},{key:"setFullSizeImgSrc",value:function(){var t=this.placeholderImg.attr("src").replace(/(\?|\&)(w=)(\d+)/i,"$1$2"+this.getPlaceholderImgRect().width).replace(/(\?|\&)(h=)(\d+)/i,"$1$2"+this.getPlaceholderImgRect().height);t.search(/(\?|\&)(h=)(\d+)/i)<0&&(t=t+"&h="+this.getPlaceholderImgRect().height+"&fit=crop"),this.fullSizeImg.attr("ix-src",t),this.fullSizeImg.addClass("img-responsive imgix-optimizing"),this.fullSizeImg.removeAttr("data-optimize-img")}},{key:"addFullSizeImgToDom",value:function(){this.fullSizeImg.insertBefore(this.placeholderImg)}},{key:"initTransition",value:function(){var t=this;this.fullSizeImg.on("load",function(){return t.transitionImg()}),imgix.init()}},{key:"transitionImg",value:function(){var t=this;if(!this.placeholderImg)return!0;this.fadeOutPlaceholder(),setTimeout(function(){t.removeFullSizeImgProperties(),t.replacePlaceholderParentTmpCss(),t.removeImg()},this.timeToFade)}},{key:"fadeOutPlaceholder",value:function(){this.placeholderImg.fadeTo(this.timeToFade,0)}},{key:"removeFullSizeImgProperties",value:function(){this.fullSizeImg.removeAttr("style"),this.fullSizeImg.removeClass("imgix-optimizing")}},{key:"replacePlaceholderParentTmpCss",value:function(){this.placeholderImg.parent().css({display:this.parentStyles.display,position:this.parentStyles.position})}},{key:"removeImg",value:function(){this.placeholderImg&&(this.placeholderImg.remove(),this.placeholderImg=void 0)}}]),e}(),o=function(){function e(t){i(this,e),this.timeToFade=500,this.processingAttr="data-imgix-bg-processed",this.dpr=window.devicePixelRatio||1,this.largestImageWidth=0,this.el=$(t),"none"!=this.el.css("background-image")&&(this.initEl(),this.initOptimization(),this.initEventListeners())}return t(e,[{key:"initOptimization",value:function(){$("<img>").on("load",$.proxy(this.listenForIntersection,this)).attr("src",this.placeholderImgUrl)}},{key:"listenForIntersection",value:function(){new IntersectionObserver($.proxy(this.onIntersection,this)).observe(this.el[0])}},{key:"onIntersection",value:function(t,e){var i=$(t[0].target);t[0].isIntersecting&&!$(i).attr(this.processingAttr)&&($(i).attr(this.processingAttr,!0),this.renderTmpPlaceholderEl())}},{key:"initEl",value:function(){this.setPlaceholderImgUrl(),this.setContainerTmpCss(),this.setElTmpCss()}},{key:"setPlaceholderImgUrl",value:function(){this.placeholderImgUrl=this.el.css("background-image").replace("url(","").replace(")","").replace(/\"/gi,"").replace(/\'/gi,"").split(", ")[0]}},{key:"setContainerTmpCss",value:function(){this.parentStyles={display:this.el.parent().css("display"),position:this.el.parent().css("position")},this.el.parent().css({display:"block",position:"relative"})}},{key:"setElTmpCss",value:function(){"absolute"!=this.el.css("position")&&this.el.css("position","relative")}},{key:"renderTmpPlaceholderEl",value:function(){this.initTmpPlaceholderEl(),this.setTmpPlaceholderElCss(),this.addTmpPlaceholderElToDom(),this.renderFullSizeImg()}},{key:"initTmpPlaceholderEl",value:function(){this.tmpPlaceholderEl=this.el.clone(),this.tmpPlaceholderEl.html("")}},{key:"setTmpPlaceholderElCss",value:function(){this.tmpPlaceholderEl.addClass("imgix-optimizing"),this.tmpPlaceholderEl.css({position:"absolute",top:this.el.position().top,left:this.el.position().left,width:this.el.outerWidth(),height:this.el.outerHeight(),backgroundColor:"transparent"})}},{key:"addTmpPlaceholderElToDom",value:function(){this.tmpPlaceholderEl.insertBefore(this.el)}},{key:"renderFullSizeImg",value:function(){this.removeElBgImg(),this.initTmpFullSizeEl(),this.setTmpFullSizeElImg(),this.addTmpFullSizeElToDom(),this.initTransition()}},{key:"removeElBgImg",value:function(){this.elBgColor=this.el.css("background-color"),this.el.css("background-color","transparent"),this.el.css("background-image","")}},{key:"initTmpFullSizeEl",value:function(){this.tmpFullSizeEl=this.tmpPlaceholderEl.clone()}},{key:"setFullSizeImgUrl",value:function(){if(!(this.fullSizeImgUrl&&this.el.outerWidth()*this.dpr<=this.largestImageWidth)){this.largestImageWidth=this.el.outerWidth()*this.dpr;var t=this.placeholderImgUrl.split("?"),e=t[t.length-1].split("&"),i={};for(var n in e.map(function(t){return i[t.split("=")[0]]=t.split("=")[1]}),this.el.outerWidth()>=this.el.outerHeight()?(i.w=this.largestImageWidth,delete i.h):(i.h=this.el.outerHeight()*this.dpr,delete i.w),e=[],i)e.push(n+"="+i[n]);return this.fullSizeImgUrl=t[0]+"?"+e.join("&")}}},{key:"setTmpFullSizeElImg",value:function(){this.setFullSizeImgUrl(),this.tmpFullSizeEl.css("background-image",'url("'+this.fullSizeImgUrl+'")')}},{key:"addTmpFullSizeElToDom",value:function(){this.tmpFullSizeEl.insertBefore(this.tmpPlaceholderEl)}},{key:"initTransition",value:function(){$("<img>").on("load",$.proxy(this.transitionImg,this)).attr("src",this.fullSizeImgUrl)}},{key:"transitionImg",value:function(){var t=this;this.fadeOutTmpPlaceholderEl(),setTimeout(function(){t.updateElImg(),t.replaceElTmpCss(),t.replaceContainerTmpCss(),t.removeTmpEls()},this.timeToFade)}},{key:"fadeOutTmpPlaceholderEl",value:function(){this.tmpPlaceholderEl.fadeTo(this.timeToFade,0)}},{key:"updateElImg",value:function(){var e=this;this.setFullSizeImgUrl(),$("<img>").on("load",function(t){return e.el.css("background-image","url('"+e.fullSizeImgUrl+"')")}).attr("src",this.placeholderImgUrl)}},{key:"replaceElTmpCss",value:function(){this.el.css("background-color",this.elBgColor)}},{key:"replaceContainerTmpCss",value:function(){this.el.parent().css({display:this.parentStyles.display,position:this.parentStyles.position})}},{key:"removeTmpEls",value:function(){this.tmpPlaceholderEl.remove(),this.tmpFullSizeEl.remove(),this.tmpPlaceholderEl=void 0,this.tmpFullSizeEl=void 0}},{key:"initEventListeners",value:function(){var e=this;this.initResizeEnd(),$(window).on("resizeEnd",function(t){return e.updateElImg()})}},{key:"initResizeEnd",value:function(){$(window).resize(function(){this.resizeTo&&clearTimeout(this.resizeTo),this.resizeTo=setTimeout(function(){$(this).trigger("resizeEnd")},500)})}}]),e}();!function(g,f){function a(t){this.time=t.time,this.target=t.target,this.rootBounds=t.rootBounds,this.boundingClientRect=t.boundingClientRect,this.intersectionRect=t.intersectionRect||{top:0,bottom:0,left:0,right:0,width:0,height:0},this.isIntersecting=!!t.intersectionRect;var e=this.boundingClientRect,i=e.width*e.height,n=this.intersectionRect,o=n.width*n.height;this.intersectionRatio=i?Number((o/i).toFixed(4)):this.isIntersecting?1:0}function t(t,e){var i,n,o,r=e||{};if("function"!=typeof t)throw new Error("callback must be a function");if(r.root&&1!=r.root.nodeType)throw new Error("root must be an Element");this._checkForIntersections=(i=this._checkForIntersections.bind(this),n=this.THROTTLE_TIMEOUT,o=null,function(){o||(o=setTimeout(function(){i(),o=null},n))}),this._callback=t,this._observationTargets=[],this._queuedEntries=[],this._rootMarginValues=this._parseRootMargin(r.rootMargin),this.thresholds=this._initThresholds(r.threshold),this.root=r.root||null,this.rootMargin=this._rootMarginValues.map(function(t){return t.value+t.unit}).join(" ")}function e(t,e,i,n){"function"==typeof t.addEventListener?t.addEventListener(e,i,n||!1):"function"==typeof t.attachEvent&&t.attachEvent("on"+e,i)}function i(t,e,i,n){"function"==typeof t.removeEventListener?t.removeEventListener(e,i,n||!1):"function"==typeof t.detatchEvent&&t.detatchEvent("on"+e,i)}function v(t){var e;try{e=t.getBoundingClientRect()}catch(t){}return e?(e.width&&e.height||(e={top:e.top,right:e.right,bottom:e.bottom,left:e.left,width:e.right-e.left,height:e.bottom-e.top}),e):{top:0,bottom:0,left:0,right:0,width:0,height:0}}function n(t,e){for(var i=e;i;){if(i==t)return!0;i=I(i)}return!1}function I(t){var e=t.parentNode;return e&&11==e.nodeType&&e.host?e.host:e}"IntersectionObserver"in g&&"IntersectionObserverEntry"in g&&"intersectionRatio"in g.IntersectionObserverEntry.prototype?"isIntersecting"in g.IntersectionObserverEntry.prototype||Object.defineProperty(g.IntersectionObserverEntry.prototype,"isIntersecting",{get:function(){return 0<this.intersectionRatio}}):(t.prototype.THROTTLE_TIMEOUT=100,t.prototype.POLL_INTERVAL=null,t.prototype.USE_MUTATION_OBSERVER=!0,t.prototype.observe=function(e){if(!this._observationTargets.some(function(t){return t.element==e})){if(!e||1!=e.nodeType)throw new Error("target must be an Element");this._registerInstance(),this._observationTargets.push({element:e,entry:null}),this._monitorIntersections(),this._checkForIntersections()}},t.prototype.unobserve=function(e){this._observationTargets=this._observationTargets.filter(function(t){return t.element!=e}),this._observationTargets.length||(this._unmonitorIntersections(),this._unregisterInstance())},t.prototype.disconnect=function(){this._observationTargets=[],this._unmonitorIntersections(),this._unregisterInstance()},t.prototype.takeRecords=function(){var t=this._queuedEntries.slice();return this._queuedEntries=[],t},t.prototype._initThresholds=function(t){var e=t||[0];return Array.isArray(e)||(e=[e]),e.sort().filter(function(t,e,i){if("number"!=typeof t||isNaN(t)||t<0||1<t)throw new Error("threshold must be a number between 0 and 1 inclusively");return t!==i[e-1]})},t.prototype._parseRootMargin=function(t){var e=(t||"0px").split(/\s+/).map(function(t){var e=/^(-?\d*\.?\d+)(px|%)$/.exec(t);if(!e)throw new Error("rootMargin must be specified in pixels or percent");return{value:parseFloat(e[1]),unit:e[2]}});return e[1]=e[1]||e[0],e[2]=e[2]||e[0],e[3]=e[3]||e[1],e},t.prototype._monitorIntersections=function(){this._monitoringIntersections||(this._monitoringIntersections=!0,this.POLL_INTERVAL?this._monitoringInterval=setInterval(this._checkForIntersections,this.POLL_INTERVAL):(e(g,"resize",this._checkForIntersections,!0),e(f,"scroll",this._checkForIntersections,!0),this.USE_MUTATION_OBSERVER&&"MutationObserver"in g&&(this._domObserver=new MutationObserver(this._checkForIntersections),this._domObserver.observe(f,{attributes:!0,childList:!0,characterData:!0,subtree:!0}))))},t.prototype._unmonitorIntersections=function(){this._monitoringIntersections&&(this._monitoringIntersections=!1,clearInterval(this._monitoringInterval),this._monitoringInterval=null,i(g,"resize",this._checkForIntersections,!0),i(f,"scroll",this._checkForIntersections,!0),this._domObserver&&(this._domObserver.disconnect(),this._domObserver=null))},t.prototype._checkForIntersections=function(){var l=this._rootIsInDom(),h=l?this._getRootRect():{top:0,bottom:0,left:0,right:0,width:0,height:0};this._observationTargets.forEach(function(t){var e=t.element,i=v(e),n=this._rootContainsTarget(e),o=t.entry,r=l&&n&&this._computeTargetAndRootIntersection(e,h),s=t.entry=new a({time:g.performance&&performance.now&&performance.now(),target:e,boundingClientRect:i,rootBounds:h,intersectionRect:r});o?l&&n?this._hasCrossedThreshold(o,s)&&this._queuedEntries.push(s):o&&o.isIntersecting&&this._queuedEntries.push(s):this._queuedEntries.push(s)},this),this._queuedEntries.length&&this._callback(this.takeRecords(),this)},t.prototype._computeTargetAndRootIntersection=function(t,e){if("none"!=g.getComputedStyle(t).display){for(var i,n,o,r,s,l,h,a,c=v(t),u=I(t),p=!1;!p;){var m=null,d=1==u.nodeType?g.getComputedStyle(u):{};if("none"==d.display)return;if(u==this.root||u==f?(p=!0,m=e):u!=f.body&&u!=f.documentElement&&"visible"!=d.overflow&&(m=v(u)),m&&(i=m,n=c,void 0,o=Math.max(i.top,n.top),r=Math.min(i.bottom,n.bottom),s=Math.max(i.left,n.left),l=Math.min(i.right,n.right),a=r-o,!(c=0<=(h=l-s)&&0<=a&&{top:o,bottom:r,left:s,right:l,width:h,height:a})))break;u=I(u)}return c}},t.prototype._getRootRect=function(){var t;if(this.root)t=v(this.root);else{var e=f.documentElement,i=f.body;t={top:0,left:0,right:e.clientWidth||i.clientWidth,width:e.clientWidth||i.clientWidth,bottom:e.clientHeight||i.clientHeight,height:e.clientHeight||i.clientHeight}}return this._expandRectByRootMargin(t)},t.prototype._expandRectByRootMargin=function(i){var t=this._rootMarginValues.map(function(t,e){return"px"==t.unit?t.value:t.value*(e%2?i.width:i.height)/100}),e={top:i.top-t[0],right:i.right+t[1],bottom:i.bottom+t[2],left:i.left-t[3]};return e.width=e.right-e.left,e.height=e.bottom-e.top,e},t.prototype._hasCrossedThreshold=function(t,e){var i=t&&t.isIntersecting?t.intersectionRatio||0:-1,n=e.isIntersecting?e.intersectionRatio||0:-1;if(i!==n)for(var o=0;o<this.thresholds.length;o++){var r=this.thresholds[o];if(r==i||r==n||r<i!=r<n)return!0}},t.prototype._rootIsInDom=function(){return!this.root||n(f,this.root)},t.prototype._rootContainsTarget=function(t){return n(this.root||f,t)},t.prototype._registerInstance=function(){},t.prototype._unregisterInstance=function(){},g.IntersectionObserver=t,g.IntersectionObserverEntry=a)}(window,document),"function"!=typeof Object.assign&&Object.defineProperty(Object,"assign",{value:function(t,e){if(null==t)throw new TypeError("Cannot convert undefined or null to object");for(var i=Object(t),n=1;n<arguments.length;n++){var o=arguments[n];if(null!=o)for(var r in o)Object.prototype.hasOwnProperty.call(o,r)&&(i[r]=o[r])}return i},writable:!0,configurable:!0});var e=function(){function e(){var t=0<arguments.length&&void 0!==arguments[0]?arguments[0]:{};i(this,e),this.initDependencies(),this.initOptions(t),this.optimizeImages(),this.optimizeBgImages()}return t(e,[{key:"initDependencies",value:function(){}},{key:"initOptions",value:function(){var t=0<arguments.length&&void 0!==arguments[0]?arguments[0]:{};this.options=t;var e={parent:"body"};for(var i in e)e.hasOwnProperty(i)&&!this.options[i]&&(this.options[i]=e[i])}},{key:"optimizeImages",value:function(){$(this.options.parent+" img[data-optimize-img]").each(function(t,e){new n(e)})}},{key:"optimizeBgImages",value:function(){return $(this.options.parent+" [data-optimize-bg-img]").each(function(t,e){new o(e)}),!0}}]),e}();window.Imgix=window.Imgix||{},Imgix.Optimizer=e}();
@@ -25,14 +25,315 @@
25
25
  };
26
26
  }();
27
27
 
28
- var ImgixBgImage = function () {
29
- function ImgixBgImage(el) {
30
- classCallCheck(this, ImgixBgImage);
28
+ var Image = function () {
29
+ function Image(img) {
30
+ classCallCheck(this, Image);
31
+
32
+ // Length of crossfade transition.
33
+ this.timeToFade = 500;
34
+ // Data attribute applied before processing.
35
+ this.processingAttr = 'data-imgix-img-processed';
36
+ // The main image (pixelated placeholder).
37
+ this.placeholderImg = $(img);
38
+ // Configure the main placeholder image.
39
+ this.initPlaceholder();
40
+ // Kick off the optimization process.
41
+ this.initOptimization();
42
+ }
43
+
44
+ /**
45
+ * Load an image in memory (not within the DOM) with the same source as the
46
+ * placeholder image. Once that has completed, we know we're safe to begin
47
+ * listening for the image to intersect the viewport.
48
+ */
49
+
50
+
51
+ createClass(Image, [{
52
+ key: 'initOptimization',
53
+ value: function initOptimization() {
54
+ $('<img>').on('load', $.proxy(this.listenForIntersection, this)).attr('src', this.placeholderImg.attr('src'));
55
+ }
56
+
57
+ /**
58
+ * When the placeholder image intersects the viewport, begin processing.
59
+ * (IntersectionObserver and Object.assign() are not supported by IE, but the
60
+ * polyfills are loaded by Imgix.Optimizer.)
61
+ */
62
+
63
+ }, {
64
+ key: 'listenForIntersection',
65
+ value: function listenForIntersection() {
66
+ var observer = new IntersectionObserver($.proxy(this.onIntersection, this));
67
+ observer.observe(this.placeholderImg[0]);
68
+ }
69
+
70
+ /**
71
+ * When the placeholder image intersects the viewport, check if it is in the
72
+ * viewport and has not yet been processed. If those conditions are true,
73
+ * begin rendering the full size image and the transition process.
74
+ */
75
+
76
+ }, {
77
+ key: 'onIntersection',
78
+ value: function onIntersection(entries, observer) {
79
+ var img = $(entries[0].target);
80
+ if (!entries[0].isIntersecting || $(img).attr(this.processingAttr)) return;
81
+ img.attr(this.processingAttr, true);
82
+ this.renderFullSizeImg();
83
+ }
84
+
85
+ // ---------------------------------------- | Placeholder Image
86
+
87
+ /**
88
+ * Make necessary CSS adjustments to main placeholder image.
89
+ */
90
+
91
+ }, {
92
+ key: 'initPlaceholder',
93
+ value: function initPlaceholder() {
94
+ this.setPlaceholderCss();
95
+ this.setPlaceholderParentTmpCss();
96
+ }
97
+
98
+ /**
99
+ * The main image must have a position set for it to remain in front of the
100
+ * full-size image. We assume that if the element is not explicitly positioned
101
+ * absolutely, then it can safely be positioned relatively.
102
+ */
103
+
104
+ }, {
105
+ key: 'setPlaceholderCss',
106
+ value: function setPlaceholderCss() {
107
+ if (this.placeholderImg.css('position') != 'absolute') {
108
+ this.placeholderImg.css('position', 'relative');
109
+ }
110
+ }
111
+
112
+ /**
113
+ * The parent of the image container should be relatively positioned
114
+ * (temporarily) so temp image can be absolutely positioned.
115
+ */
116
+
117
+ }, {
118
+ key: 'setPlaceholderParentTmpCss',
119
+ value: function setPlaceholderParentTmpCss() {
120
+ this.parentStyles = {
121
+ display: this.placeholderImg.parent().css('display'),
122
+ position: this.placeholderImg.parent().css('position')
123
+ };
124
+ this.placeholderImg.parent().css({
125
+ display: 'block',
126
+ position: 'relative'
127
+ });
128
+ }
129
+
130
+ // ---------------------------------------- | Full-Size Image
131
+
132
+ /**
133
+ * Render the full-size image behind the placeholder image.
134
+ */
135
+
136
+ }, {
137
+ key: 'renderFullSizeImg',
138
+ value: function renderFullSizeImg() {
139
+ this.initFullSizeImg();
140
+ this.setFullSizeImgTempCss();
141
+ this.setFullSizeImgSrc();
142
+ this.addFullSizeImgToDom();
143
+ this.initTransition();
144
+ }
145
+
146
+ /**
147
+ * The full-size image is a clone of the placeholder image. This enables us to
148
+ * easily replace it without losing any necessary styles or attributes.
149
+ */
150
+
151
+ }, {
152
+ key: 'initFullSizeImg',
153
+ value: function initFullSizeImg() {
154
+ this.fullSizeImg = this.placeholderImg.clone();
155
+ }
156
+
157
+ /**
158
+ * Give the full-size image a temporary set of CSS rules so that it can sit
159
+ * directly behind the placeholder image while loading.
160
+ */
161
+
162
+ }, {
163
+ key: 'setFullSizeImgTempCss',
164
+ value: function setFullSizeImgTempCss() {
165
+ this.fullSizeImg.css({
166
+ position: 'absolute',
167
+ top: this.placeholderImg.position().top,
168
+ left: this.placeholderImg.position().left,
169
+ width: '100%',
170
+ height: '100%'
171
+ });
172
+ }
173
+
174
+ /**
175
+ * Return the width and height of the placeholder image, including decimals.
176
+ * Uses precise measurements like this helps ensure the element doesn't slide
177
+ * when transitioning to the full size image.
178
+ */
179
+
180
+ }, {
181
+ key: 'getPlaceholderImgRect',
182
+ value: function getPlaceholderImgRect() {
183
+ return {
184
+ width: this.placeholderImg[0].getBoundingClientRect().width,
185
+ height: this.placeholderImg[0].getBoundingClientRect().height
186
+ };
187
+ }
188
+
189
+ /**
190
+ * Prep the full-size image with the attributes necessary to become its full
191
+ * size. Right now it is still just a replica of the placeholder, sitting
192
+ * right behind the placeholder.
193
+ *
194
+ * We set the src directly even though we're using imgix.js because older
195
+ * browsers don't support the srcset attribute which is what imgix.js relies
196
+ * upon.
197
+ */
198
+
199
+ }, {
200
+ key: 'setFullSizeImgSrc',
201
+ value: function setFullSizeImgSrc() {
202
+ var newSrc = this.placeholderImg.attr('src').replace(/(\?|\&)(w=)(\d+)/i, '$1$2' + this.getPlaceholderImgRect().width).replace(/(\?|\&)(h=)(\d+)/i, '$1$2' + this.getPlaceholderImgRect().height);
203
+ // Add a height attribute if it is missing. This is the key to the image not
204
+ // jumping around after transitioning to the full-size image.
205
+ if (newSrc.search(/(\?|\&)(h=)(\d+)/i) < 0) {
206
+ newSrc = newSrc + '&h=' + this.getPlaceholderImgRect().height + '&fit=crop';
207
+ }
208
+ this.fullSizeImg.attr('ix-src', newSrc);
209
+ // TODO: Make this a configurable option or document it as a more semantic temporary class
210
+ this.fullSizeImg.addClass('img-responsive imgix-optimizing');
211
+ // TODO: This should respect the option from the Optimizer class for the select
212
+ this.fullSizeImg.removeAttr('data-optimize-img');
213
+ }
214
+
215
+ /**
216
+ * Render the full-size image in the DOM.
217
+ */
218
+
219
+ }, {
220
+ key: 'addFullSizeImgToDom',
221
+ value: function addFullSizeImgToDom() {
222
+ this.fullSizeImg.insertBefore(this.placeholderImg);
223
+ }
224
+
225
+ // ---------------------------------------- | Image Transition
226
+
227
+ /**
228
+ * Once the full-size image is loaded, begin the transition. This is the
229
+ * critical piece of this process. Imgix.js uses the ix-src attribute to build
230
+ * out the srcset attribute. Then, based on the sizes attribute, the browser
231
+ * determines which source to render. Therefore we can't preload in memory
232
+ * because we need imgix to do its thing directly in the DOM.
233
+ */
234
+
235
+ }, {
236
+ key: 'initTransition',
237
+ value: function initTransition() {
238
+ var _this = this;
239
+
240
+ this.fullSizeImg.on('load', function () {
241
+ return _this.transitionImg();
242
+ });
243
+ imgix.init();
244
+ }
245
+
246
+ /**
247
+ * Fade out the placeholder image, effectively showing the image behind it.
248
+ *
249
+ * Once the fade out transition has completed, remove any temporary properties
250
+ * from the full-size image (so it gets back to being a clone of the
251
+ * placeholder, with the full-size src).
252
+ *
253
+ * Finally, remove the placeholder image from the DOM since we don't need it
254
+ * any more.
255
+ */
256
+
257
+ }, {
258
+ key: 'transitionImg',
259
+ value: function transitionImg() {
260
+ var _this2 = this;
261
+
262
+ if (!this.placeholderImg) return true;
263
+ this.fadeOutPlaceholder();
264
+ setTimeout(function () {
265
+ _this2.removeFullSizeImgProperties();
266
+ _this2.replacePlaceholderParentTmpCss();
267
+ _this2.removeImg();
268
+ }, this.timeToFade);
269
+ }
270
+
271
+ /**
272
+ * Fade out the placeholder image.
273
+ */
274
+
275
+ }, {
276
+ key: 'fadeOutPlaceholder',
277
+ value: function fadeOutPlaceholder() {
278
+ this.placeholderImg.fadeTo(this.timeToFade, 0);
279
+ }
280
+
281
+ /**
282
+ * Remove temporary styles and class from the full-size image, which
283
+ * effectively means it has replaced the placeholder image.
284
+ */
285
+
286
+ }, {
287
+ key: 'removeFullSizeImgProperties',
288
+ value: function removeFullSizeImgProperties() {
289
+ this.fullSizeImg.removeAttr('style');
290
+ // TODO: Update this with how the class is handled above.
291
+ this.fullSizeImg.removeClass('imgix-optimizing');
292
+ }
293
+
294
+ /**
295
+ * Reset the container's adjusted CSS properties.
296
+ */
297
+
298
+ }, {
299
+ key: 'replacePlaceholderParentTmpCss',
300
+ value: function replacePlaceholderParentTmpCss() {
301
+ this.placeholderImg.parent().css({
302
+ display: this.parentStyles.display,
303
+ position: this.parentStyles.position
304
+ });
305
+ }
306
+
307
+ /**
308
+ * Remove the placeholder image from the DOM since we no longer need it.
309
+ */
310
+
311
+ }, {
312
+ key: 'removeImg',
313
+ value: function removeImg() {
314
+ if (!this.placeholderImg) {
315
+ return;
316
+ }
317
+ this.placeholderImg.remove();
318
+ this.placeholderImg = undefined;
319
+ }
320
+ }]);
321
+ return Image;
322
+ }();
323
+
324
+ var BackgroundImage = function () {
325
+ function BackgroundImage(el) {
326
+ classCallCheck(this, BackgroundImage);
31
327
 
32
328
  // Length of time to complete fade-in transition.
33
329
  this.timeToFade = 500;
330
+ // Data attribute applied before processing.
331
+ this.processingAttr = 'data-imgix-bg-processed';
34
332
  // Device pixel ratio assumes 1 if not set.
35
333
  this.dpr = window['devicePixelRatio'] || 1;
334
+ // The largest image that has been loaded. This assumes the height of the
335
+ // container will not change.
336
+ this.largestImageWidth = 0;
36
337
  // The primary element (i.e. the one with the background image).
37
338
  this.el = $(el);
38
339
  // Background image CSS property must be present.
@@ -54,14 +355,38 @@
54
355
  */
55
356
 
56
357
 
57
- createClass(ImgixBgImage, [{
358
+ createClass(BackgroundImage, [{
58
359
  key: 'initOptimization',
59
360
  value: function initOptimization() {
60
- var _this = this;
361
+ $('<img>').on('load', $.proxy(this.listenForIntersection, this)).attr('src', this.placeholderImgUrl);
362
+ }
61
363
 
62
- $('<img>').on('load', function () {
63
- return _this.renderTmpPlaceholderEl();
64
- }).attr('src', this.placeholderImgUrl);
364
+ /**
365
+ * When the element intersects the viewport, begin processing.
366
+ * (IntersectionObserver and Object.assign() are not supported by IE, but the
367
+ * polyfills are loaded by Imgix.Optimizer.)
368
+ */
369
+
370
+ }, {
371
+ key: 'listenForIntersection',
372
+ value: function listenForIntersection() {
373
+ var observer = new IntersectionObserver($.proxy(this.onIntersection, this));
374
+ observer.observe(this.el[0]);
375
+ }
376
+
377
+ /**
378
+ * When the element intersects the viewport, check if it is in the viewport
379
+ * and has not yet been processed. If those conditions are true, begin
380
+ * rendering the full size image and the transition process.
381
+ */
382
+
383
+ }, {
384
+ key: 'onIntersection',
385
+ value: function onIntersection(entries, observer) {
386
+ var el = $(entries[0].target);
387
+ if (!entries[0].isIntersecting || $(el).attr(this.processingAttr)) return;
388
+ $(el).attr(this.processingAttr, true);
389
+ this.renderTmpPlaceholderEl();
65
390
  }
66
391
 
67
392
  // ---------------------------------------- | Main Element
@@ -86,7 +411,7 @@
86
411
  }, {
87
412
  key: 'setPlaceholderImgUrl',
88
413
  value: function setPlaceholderImgUrl() {
89
- this.placeholderImgUrl = this.el.css('background-image').replace('url(', '').replace(')', '').replace(/\"/gi, "").replace(/\'/gi, "").split(', ')[0];
414
+ this.placeholderImgUrl = this.el.css('background-image').replace('url(', '').replace(')', '').replace(/\"/gi, '').replace(/\'/gi, '').split(', ')[0];
90
415
  }
91
416
 
92
417
  /**
@@ -234,7 +559,13 @@
234
559
  }, {
235
560
  key: 'setFullSizeImgUrl',
236
561
  value: function setFullSizeImgUrl() {
237
- // Work with the placeholdler image URL, which has been pulled from the
562
+ // If the full size image URL exists and if the new size is going to be
563
+ // smaller than the largest size loaded, then we stick with the largest size
564
+ // that has been used.
565
+ if (this.fullSizeImgUrl && this.el.outerWidth() * this.dpr <= this.largestImageWidth) return;
566
+ // Assume that the new width will be the largest size used.
567
+ this.largestImageWidth = this.el.outerWidth() * this.dpr;
568
+ // Work with the placeholder image URL, which has been pulled from the
238
569
  // background-image css property of the main elements.
239
570
  var url = this.placeholderImgUrl.split('?');
240
571
  // q is an array of querystring parameters as ["k=v", "k=v", ...].
@@ -248,7 +579,7 @@
248
579
  // If the image's container is wider than it is tall, we only set width and
249
580
  // unset height, and vice versa.
250
581
  if (this.el.outerWidth() >= this.el.outerHeight()) {
251
- args['w'] = this.el.outerWidth() * this.dpr;
582
+ args['w'] = this.largestImageWidth;
252
583
  delete args['h'];
253
584
  } else {
254
585
  args['h'] = this.el.outerHeight() * this.dpr;
@@ -309,14 +640,14 @@
309
640
  }, {
310
641
  key: 'transitionImg',
311
642
  value: function transitionImg() {
312
- var _this2 = this;
643
+ var _this = this;
313
644
 
314
645
  this.fadeOutTmpPlaceholderEl();
315
646
  setTimeout(function () {
316
- _this2.updateElImg();
317
- _this2.replaceElTmpCss();
318
- _this2.replaceContainerTmpCss();
319
- _this2.removeTmpEls();
647
+ _this.updateElImg();
648
+ _this.replaceElTmpCss();
649
+ _this.replaceContainerTmpCss();
650
+ _this.removeTmpEls();
320
651
  }, this.timeToFade);
321
652
  }
322
653
 
@@ -343,8 +674,12 @@
343
674
  }, {
344
675
  key: 'updateElImg',
345
676
  value: function updateElImg() {
677
+ var _this2 = this;
678
+
346
679
  this.setFullSizeImgUrl();
347
- this.el.css('background-image', 'url(\'' + this.fullSizeImgUrl + '\')');
680
+ $('<img>').on('load', function (event) {
681
+ return _this2.el.css('background-image', 'url(\'' + _this2.fullSizeImgUrl + '\')');
682
+ }).attr('src', this.placeholderImgUrl);
348
683
  }
349
684
 
350
685
  /**
@@ -418,235 +753,774 @@
418
753
  });
419
754
  }
420
755
  }]);
421
- return ImgixBgImage;
756
+ return BackgroundImage;
422
757
  }();
423
758
 
424
- var ImgixImage = function () {
425
- function ImgixImage(img) {
426
- classCallCheck(this, ImgixImage);
759
+ /**
760
+ * Copyright 2016 Google Inc. All Rights Reserved.
761
+ *
762
+ * Licensed under the W3C SOFTWARE AND DOCUMENT NOTICE AND LICENSE.
763
+ *
764
+ * https://www.w3.org/Consortium/Legal/2015/copyright-software-and-document
765
+ *
766
+ */
767
+
768
+ (function(window, document) {
769
+
770
+
771
+ // Exits early if all IntersectionObserver and IntersectionObserverEntry
772
+ // features are natively supported.
773
+ if ('IntersectionObserver' in window &&
774
+ 'IntersectionObserverEntry' in window &&
775
+ 'intersectionRatio' in window.IntersectionObserverEntry.prototype) {
776
+
777
+ // Minimal polyfill for Edge 15's lack of `isIntersecting`
778
+ // See: https://github.com/w3c/IntersectionObserver/issues/211
779
+ if (!('isIntersecting' in window.IntersectionObserverEntry.prototype)) {
780
+ Object.defineProperty(window.IntersectionObserverEntry.prototype,
781
+ 'isIntersecting', {
782
+ get: function () {
783
+ return this.intersectionRatio > 0;
784
+ }
785
+ });
786
+ }
787
+ return;
788
+ }
789
+
790
+
791
+ /**
792
+ * Creates the global IntersectionObserverEntry constructor.
793
+ * https://w3c.github.io/IntersectionObserver/#intersection-observer-entry
794
+ * @param {Object} entry A dictionary of instance properties.
795
+ * @constructor
796
+ */
797
+ function IntersectionObserverEntry(entry) {
798
+ this.time = entry.time;
799
+ this.target = entry.target;
800
+ this.rootBounds = entry.rootBounds;
801
+ this.boundingClientRect = entry.boundingClientRect;
802
+ this.intersectionRect = entry.intersectionRect || getEmptyRect();
803
+ this.isIntersecting = !!entry.intersectionRect;
804
+
805
+ // Calculates the intersection ratio.
806
+ var targetRect = this.boundingClientRect;
807
+ var targetArea = targetRect.width * targetRect.height;
808
+ var intersectionRect = this.intersectionRect;
809
+ var intersectionArea = intersectionRect.width * intersectionRect.height;
810
+
811
+ // Sets intersection ratio.
812
+ if (targetArea) {
813
+ // Round the intersection ratio to avoid floating point math issues:
814
+ // https://github.com/w3c/IntersectionObserver/issues/324
815
+ this.intersectionRatio = Number((intersectionArea / targetArea).toFixed(4));
816
+ } else {
817
+ // If area is zero and is intersecting, sets to 1, otherwise to 0
818
+ this.intersectionRatio = this.isIntersecting ? 1 : 0;
819
+ }
820
+ }
427
821
 
428
- // Length of crossfade transition.
429
- this.timeToFade = 500;
430
- // The main image (pixelated placeholder).
431
- this.placeholderImg = $(img);
432
- // Configure the main placeholder image.
433
- this.initPlaceholder();
434
- // Kick off the optimization process.
435
- this.initOptimization();
822
+
823
+ /**
824
+ * Creates the global IntersectionObserver constructor.
825
+ * https://w3c.github.io/IntersectionObserver/#intersection-observer-interface
826
+ * @param {Function} callback The function to be invoked after intersection
827
+ * changes have queued. The function is not invoked if the queue has
828
+ * been emptied by calling the `takeRecords` method.
829
+ * @param {Object=} opt_options Optional configuration options.
830
+ * @constructor
831
+ */
832
+ function IntersectionObserver(callback, opt_options) {
833
+
834
+ var options = opt_options || {};
835
+
836
+ if (typeof callback != 'function') {
837
+ throw new Error('callback must be a function');
436
838
  }
437
839
 
438
- /**
439
- * Load an image in memory (not within the DOM) with the same source as the
440
- * placeholder image. Once that has completed, we know we're safe to begin
441
- * processing.
442
- */
840
+ if (options.root && options.root.nodeType != 1) {
841
+ throw new Error('root must be an Element');
842
+ }
443
843
 
844
+ // Binds and throttles `this._checkForIntersections`.
845
+ this._checkForIntersections = throttle(
846
+ this._checkForIntersections.bind(this), this.THROTTLE_TIMEOUT);
847
+
848
+ // Private properties.
849
+ this._callback = callback;
850
+ this._observationTargets = [];
851
+ this._queuedEntries = [];
852
+ this._rootMarginValues = this._parseRootMargin(options.rootMargin);
853
+
854
+ // Public properties.
855
+ this.thresholds = this._initThresholds(options.threshold);
856
+ this.root = options.root || null;
857
+ this.rootMargin = this._rootMarginValues.map(function(margin) {
858
+ return margin.value + margin.unit;
859
+ }).join(' ');
860
+ }
861
+
862
+
863
+ /**
864
+ * The minimum interval within which the document will be checked for
865
+ * intersection changes.
866
+ */
867
+ IntersectionObserver.prototype.THROTTLE_TIMEOUT = 100;
868
+
869
+
870
+ /**
871
+ * The frequency in which the polyfill polls for intersection changes.
872
+ * this can be updated on a per instance basis and must be set prior to
873
+ * calling `observe` on the first target.
874
+ */
875
+ IntersectionObserver.prototype.POLL_INTERVAL = null;
876
+
877
+ /**
878
+ * Use a mutation observer on the root element
879
+ * to detect intersection changes.
880
+ */
881
+ IntersectionObserver.prototype.USE_MUTATION_OBSERVER = true;
882
+
883
+
884
+ /**
885
+ * Starts observing a target element for intersection changes based on
886
+ * the thresholds values.
887
+ * @param {Element} target The DOM element to observe.
888
+ */
889
+ IntersectionObserver.prototype.observe = function(target) {
890
+ var isTargetAlreadyObserved = this._observationTargets.some(function(item) {
891
+ return item.element == target;
892
+ });
893
+
894
+ if (isTargetAlreadyObserved) {
895
+ return;
896
+ }
444
897
 
445
- createClass(ImgixImage, [{
446
- key: 'initOptimization',
447
- value: function initOptimization() {
448
- $('<img>').on('load', $.proxy(this.renderFullSizeImg, this)).attr('src', this.placeholderImg.attr('src'));
449
- }
898
+ if (!(target && target.nodeType == 1)) {
899
+ throw new Error('target must be an Element');
900
+ }
450
901
 
451
- // ---------------------------------------- | Placeholder Image
902
+ this._registerInstance();
903
+ this._observationTargets.push({element: target, entry: null});
904
+ this._monitorIntersections();
905
+ this._checkForIntersections();
906
+ };
452
907
 
453
- /**
454
- * Make necessary CSS adjustments to main placeholder image.
455
- */
456
908
 
457
- }, {
458
- key: 'initPlaceholder',
459
- value: function initPlaceholder() {
460
- this.setPlaceholderCss();
909
+ /**
910
+ * Stops observing a target element for intersection changes.
911
+ * @param {Element} target The DOM element to observe.
912
+ */
913
+ IntersectionObserver.prototype.unobserve = function(target) {
914
+ this._observationTargets =
915
+ this._observationTargets.filter(function(item) {
916
+
917
+ return item.element != target;
918
+ });
919
+ if (!this._observationTargets.length) {
920
+ this._unmonitorIntersections();
921
+ this._unregisterInstance();
922
+ }
923
+ };
924
+
925
+
926
+ /**
927
+ * Stops observing all target elements for intersection changes.
928
+ */
929
+ IntersectionObserver.prototype.disconnect = function() {
930
+ this._observationTargets = [];
931
+ this._unmonitorIntersections();
932
+ this._unregisterInstance();
933
+ };
934
+
935
+
936
+ /**
937
+ * Returns any queue entries that have not yet been reported to the
938
+ * callback and clears the queue. This can be used in conjunction with the
939
+ * callback to obtain the absolute most up-to-date intersection information.
940
+ * @return {Array} The currently queued entries.
941
+ */
942
+ IntersectionObserver.prototype.takeRecords = function() {
943
+ var records = this._queuedEntries.slice();
944
+ this._queuedEntries = [];
945
+ return records;
946
+ };
947
+
948
+
949
+ /**
950
+ * Accepts the threshold value from the user configuration object and
951
+ * returns a sorted array of unique threshold values. If a value is not
952
+ * between 0 and 1 and error is thrown.
953
+ * @private
954
+ * @param {Array|number=} opt_threshold An optional threshold value or
955
+ * a list of threshold values, defaulting to [0].
956
+ * @return {Array} A sorted list of unique and valid threshold values.
957
+ */
958
+ IntersectionObserver.prototype._initThresholds = function(opt_threshold) {
959
+ var threshold = opt_threshold || [0];
960
+ if (!Array.isArray(threshold)) threshold = [threshold];
961
+
962
+ return threshold.sort().filter(function(t, i, a) {
963
+ if (typeof t != 'number' || isNaN(t) || t < 0 || t > 1) {
964
+ throw new Error('threshold must be a number between 0 and 1 inclusively');
461
965
  }
966
+ return t !== a[i - 1];
967
+ });
968
+ };
462
969
 
463
- /**
464
- * The main image must have a position set for it to remain in front of the
465
- * full-size image. We assume that if the element is not explicitly positioned
466
- * absolutely, then it can safely be positioned relatively.
467
- */
468
970
 
469
- }, {
470
- key: 'setPlaceholderCss',
471
- value: function setPlaceholderCss() {
472
- if (this.placeholderImg.css('position') != 'absolute') {
473
- this.placeholderImg.css('position', 'relative');
474
- }
971
+ /**
972
+ * Accepts the rootMargin value from the user configuration object
973
+ * and returns an array of the four margin values as an object containing
974
+ * the value and unit properties. If any of the values are not properly
975
+ * formatted or use a unit other than px or %, and error is thrown.
976
+ * @private
977
+ * @param {string=} opt_rootMargin An optional rootMargin value,
978
+ * defaulting to '0px'.
979
+ * @return {Array<Object>} An array of margin objects with the keys
980
+ * value and unit.
981
+ */
982
+ IntersectionObserver.prototype._parseRootMargin = function(opt_rootMargin) {
983
+ var marginString = opt_rootMargin || '0px';
984
+ var margins = marginString.split(/\s+/).map(function(margin) {
985
+ var parts = /^(-?\d*\.?\d+)(px|%)$/.exec(margin);
986
+ if (!parts) {
987
+ throw new Error('rootMargin must be specified in pixels or percent');
475
988
  }
989
+ return {value: parseFloat(parts[1]), unit: parts[2]};
990
+ });
476
991
 
477
- // ---------------------------------------- | Full-Size Image
992
+ // Handles shorthand.
993
+ margins[1] = margins[1] || margins[0];
994
+ margins[2] = margins[2] || margins[0];
995
+ margins[3] = margins[3] || margins[1];
478
996
 
479
- /**
480
- * Render the full-size image behind the placeholder image.
481
- */
997
+ return margins;
998
+ };
482
999
 
483
- }, {
484
- key: 'renderFullSizeImg',
485
- value: function renderFullSizeImg() {
486
- this.initFullSizeImg();
487
- this.setFullSizeImgTempCss();
488
- this.setFullSizeImgSrc();
489
- this.addFullSizeImgToDom();
490
- this.initTransition();
1000
+
1001
+ /**
1002
+ * Starts polling for intersection changes if the polling is not already
1003
+ * happening, and if the page's visibility state is visible.
1004
+ * @private
1005
+ */
1006
+ IntersectionObserver.prototype._monitorIntersections = function() {
1007
+ if (!this._monitoringIntersections) {
1008
+ this._monitoringIntersections = true;
1009
+
1010
+ // If a poll interval is set, use polling instead of listening to
1011
+ // resize and scroll events or DOM mutations.
1012
+ if (this.POLL_INTERVAL) {
1013
+ this._monitoringInterval = setInterval(
1014
+ this._checkForIntersections, this.POLL_INTERVAL);
1015
+ }
1016
+ else {
1017
+ addEvent(window, 'resize', this._checkForIntersections, true);
1018
+ addEvent(document, 'scroll', this._checkForIntersections, true);
1019
+
1020
+ if (this.USE_MUTATION_OBSERVER && 'MutationObserver' in window) {
1021
+ this._domObserver = new MutationObserver(this._checkForIntersections);
1022
+ this._domObserver.observe(document, {
1023
+ attributes: true,
1024
+ childList: true,
1025
+ characterData: true,
1026
+ subtree: true
1027
+ });
1028
+ }
491
1029
  }
1030
+ }
1031
+ };
492
1032
 
493
- /**
494
- * The full-size image is a clone of the placeholder image. This enables us to
495
- * easily replace it without losing any necessary styles or attributes.
496
- */
497
1033
 
498
- }, {
499
- key: 'initFullSizeImg',
500
- value: function initFullSizeImg() {
501
- this.fullSizeImg = this.placeholderImg.clone();
1034
+ /**
1035
+ * Stops polling for intersection changes.
1036
+ * @private
1037
+ */
1038
+ IntersectionObserver.prototype._unmonitorIntersections = function() {
1039
+ if (this._monitoringIntersections) {
1040
+ this._monitoringIntersections = false;
1041
+
1042
+ clearInterval(this._monitoringInterval);
1043
+ this._monitoringInterval = null;
1044
+
1045
+ removeEvent(window, 'resize', this._checkForIntersections, true);
1046
+ removeEvent(document, 'scroll', this._checkForIntersections, true);
1047
+
1048
+ if (this._domObserver) {
1049
+ this._domObserver.disconnect();
1050
+ this._domObserver = null;
502
1051
  }
1052
+ }
1053
+ };
503
1054
 
504
- /**
505
- * Give the full-size image a temporary set of CSS rules so that it can sit
506
- * directly behind the placeholder image while loading.
507
- */
508
1055
 
509
- }, {
510
- key: 'setFullSizeImgTempCss',
511
- value: function setFullSizeImgTempCss() {
512
- this.fullSizeImg.css({
513
- position: 'absolute',
514
- top: this.placeholderImg.position().top,
515
- left: this.placeholderImg.position().left,
516
- width: this.placeholderImg.width(),
517
- height: this.placeholderImg.height()
518
- });
1056
+ /**
1057
+ * Scans each observation target for intersection changes and adds them
1058
+ * to the internal entries queue. If new entries are found, it
1059
+ * schedules the callback to be invoked.
1060
+ * @private
1061
+ */
1062
+ IntersectionObserver.prototype._checkForIntersections = function() {
1063
+ var rootIsInDom = this._rootIsInDom();
1064
+ var rootRect = rootIsInDom ? this._getRootRect() : getEmptyRect();
1065
+
1066
+ this._observationTargets.forEach(function(item) {
1067
+ var target = item.element;
1068
+ var targetRect = getBoundingClientRect(target);
1069
+ var rootContainsTarget = this._rootContainsTarget(target);
1070
+ var oldEntry = item.entry;
1071
+ var intersectionRect = rootIsInDom && rootContainsTarget &&
1072
+ this._computeTargetAndRootIntersection(target, rootRect);
1073
+
1074
+ var newEntry = item.entry = new IntersectionObserverEntry({
1075
+ time: now(),
1076
+ target: target,
1077
+ boundingClientRect: targetRect,
1078
+ rootBounds: rootRect,
1079
+ intersectionRect: intersectionRect
1080
+ });
1081
+
1082
+ if (!oldEntry) {
1083
+ this._queuedEntries.push(newEntry);
1084
+ } else if (rootIsInDom && rootContainsTarget) {
1085
+ // If the new entry intersection ratio has crossed any of the
1086
+ // thresholds, add a new entry.
1087
+ if (this._hasCrossedThreshold(oldEntry, newEntry)) {
1088
+ this._queuedEntries.push(newEntry);
1089
+ }
1090
+ } else {
1091
+ // If the root is not in the DOM or target is not contained within
1092
+ // root but the previous entry for this target had an intersection,
1093
+ // add a new record indicating removal.
1094
+ if (oldEntry && oldEntry.isIntersecting) {
1095
+ this._queuedEntries.push(newEntry);
1096
+ }
519
1097
  }
1098
+ }, this);
520
1099
 
521
- /**
522
- * Prep the full-size image with the attributes necessary to become its full
523
- * size. Right now it is still just a replica of the placeholder, sitting
524
- * right behind the placeholder.
525
- *
526
- * We set the src directly even though we're using imgix.js because older
527
- * browsers don't support the srcset attribute which is what imgix.js relies
528
- * upon.
529
- */
1100
+ if (this._queuedEntries.length) {
1101
+ this._callback(this.takeRecords(), this);
1102
+ }
1103
+ };
530
1104
 
531
- }, {
532
- key: 'setFullSizeImgSrc',
533
- value: function setFullSizeImgSrc() {
534
- var newSrc = this.placeholderImg.attr('src').replace(/(\?|\&)(w=)(\d+)/i, '$1$2' + this.placeholderImg.width()).replace(/(\?|\&)(h=)(\d+)/i, '$1$2' + this.placeholderImg.height());
535
- this.fullSizeImg.attr('ix-src', newSrc);
536
- // TODO: Make this a configurable option or document it as a more semantic temporary class
537
- this.fullSizeImg.addClass('img-responsive imgix-optimizing');
538
- // TODO: This should respect the option from the Optimizer class for the select
539
- this.fullSizeImg.removeAttr('data-optimize-img');
1105
+
1106
+ /**
1107
+ * Accepts a target and root rect computes the intersection between then
1108
+ * following the algorithm in the spec.
1109
+ * TODO(philipwalton): at this time clip-path is not considered.
1110
+ * https://w3c.github.io/IntersectionObserver/#calculate-intersection-rect-algo
1111
+ * @param {Element} target The target DOM element
1112
+ * @param {Object} rootRect The bounding rect of the root after being
1113
+ * expanded by the rootMargin value.
1114
+ * @return {?Object} The final intersection rect object or undefined if no
1115
+ * intersection is found.
1116
+ * @private
1117
+ */
1118
+ IntersectionObserver.prototype._computeTargetAndRootIntersection =
1119
+ function(target, rootRect) {
1120
+
1121
+ // If the element isn't displayed, an intersection can't happen.
1122
+ if (window.getComputedStyle(target).display == 'none') return;
1123
+
1124
+ var targetRect = getBoundingClientRect(target);
1125
+ var intersectionRect = targetRect;
1126
+ var parent = getParentNode(target);
1127
+ var atRoot = false;
1128
+
1129
+ while (!atRoot) {
1130
+ var parentRect = null;
1131
+ var parentComputedStyle = parent.nodeType == 1 ?
1132
+ window.getComputedStyle(parent) : {};
1133
+
1134
+ // If the parent isn't displayed, an intersection can't happen.
1135
+ if (parentComputedStyle.display == 'none') return;
1136
+
1137
+ if (parent == this.root || parent == document) {
1138
+ atRoot = true;
1139
+ parentRect = rootRect;
1140
+ } else {
1141
+ // If the element has a non-visible overflow, and it's not the <body>
1142
+ // or <html> element, update the intersection rect.
1143
+ // Note: <body> and <html> cannot be clipped to a rect that's not also
1144
+ // the document rect, so no need to compute a new intersection.
1145
+ if (parent != document.body &&
1146
+ parent != document.documentElement &&
1147
+ parentComputedStyle.overflow != 'visible') {
1148
+ parentRect = getBoundingClientRect(parent);
1149
+ }
540
1150
  }
541
1151
 
542
- /**
543
- * Render the full-size image in the DOM.
544
- */
1152
+ // If either of the above conditionals set a new parentRect,
1153
+ // calculate new intersection data.
1154
+ if (parentRect) {
1155
+ intersectionRect = computeRectIntersection(parentRect, intersectionRect);
545
1156
 
546
- }, {
547
- key: 'addFullSizeImgToDom',
548
- value: function addFullSizeImgToDom() {
549
- this.fullSizeImg.insertBefore(this.placeholderImg);
1157
+ if (!intersectionRect) break;
550
1158
  }
1159
+ parent = getParentNode(parent);
1160
+ }
1161
+ return intersectionRect;
1162
+ };
551
1163
 
552
- // ---------------------------------------- | Image Transition
553
1164
 
554
- /**
555
- * Once the full-size image is loaded, begin the transition. This is the
556
- * critical piece of this process. Imgix.js uses the ix-src attribute to build
557
- * out the srcset attribute. Then, based on the sizes attribute, the browser
558
- * determines which source to render. Therefore we can't preload in memory
559
- * because we need imgix to do its thing directly in the DOM.
560
- */
1165
+ /**
1166
+ * Returns the root rect after being expanded by the rootMargin value.
1167
+ * @return {Object} The expanded root rect.
1168
+ * @private
1169
+ */
1170
+ IntersectionObserver.prototype._getRootRect = function() {
1171
+ var rootRect;
1172
+ if (this.root) {
1173
+ rootRect = getBoundingClientRect(this.root);
1174
+ } else {
1175
+ // Use <html>/<body> instead of window since scroll bars affect size.
1176
+ var html = document.documentElement;
1177
+ var body = document.body;
1178
+ rootRect = {
1179
+ top: 0,
1180
+ left: 0,
1181
+ right: html.clientWidth || body.clientWidth,
1182
+ width: html.clientWidth || body.clientWidth,
1183
+ bottom: html.clientHeight || body.clientHeight,
1184
+ height: html.clientHeight || body.clientHeight
1185
+ };
1186
+ }
1187
+ return this._expandRectByRootMargin(rootRect);
1188
+ };
561
1189
 
562
- }, {
563
- key: 'initTransition',
564
- value: function initTransition() {
565
- var _this = this;
566
1190
 
567
- this.fullSizeImg.on('load', function () {
568
- return _this.transitionImg();
569
- });
570
- imgix.init();
571
- }
1191
+ /**
1192
+ * Accepts a rect and expands it by the rootMargin value.
1193
+ * @param {Object} rect The rect object to expand.
1194
+ * @return {Object} The expanded rect.
1195
+ * @private
1196
+ */
1197
+ IntersectionObserver.prototype._expandRectByRootMargin = function(rect) {
1198
+ var margins = this._rootMarginValues.map(function(margin, i) {
1199
+ return margin.unit == 'px' ? margin.value :
1200
+ margin.value * (i % 2 ? rect.width : rect.height) / 100;
1201
+ });
1202
+ var newRect = {
1203
+ top: rect.top - margins[0],
1204
+ right: rect.right + margins[1],
1205
+ bottom: rect.bottom + margins[2],
1206
+ left: rect.left - margins[3]
1207
+ };
1208
+ newRect.width = newRect.right - newRect.left;
1209
+ newRect.height = newRect.bottom - newRect.top;
572
1210
 
573
- /**
574
- * Fade out the placeholder image, effectively showing the image behind it.
575
- *
576
- * Once the fade out transition has completed, remove any temporary properties
577
- * from the full-size image (so it gets back to being a clone of the
578
- * placeholder, with the full-size src).
579
- *
580
- * Finally, remove the placeholder image from the DOM since we don't need it
581
- * any more.
582
- */
1211
+ return newRect;
1212
+ };
583
1213
 
584
- }, {
585
- key: 'transitionImg',
586
- value: function transitionImg() {
587
- var _this2 = this;
588
1214
 
589
- if (!this.placeholderImg) return true;
590
- this.fadeOutPlaceholder();
591
- setTimeout(function () {
592
- _this2.removeFullSizeImgProperties();
593
- _this2.removeImg();
594
- }, this.timeToFade);
1215
+ /**
1216
+ * Accepts an old and new entry and returns true if at least one of the
1217
+ * threshold values has been crossed.
1218
+ * @param {?IntersectionObserverEntry} oldEntry The previous entry for a
1219
+ * particular target element or null if no previous entry exists.
1220
+ * @param {IntersectionObserverEntry} newEntry The current entry for a
1221
+ * particular target element.
1222
+ * @return {boolean} Returns true if a any threshold has been crossed.
1223
+ * @private
1224
+ */
1225
+ IntersectionObserver.prototype._hasCrossedThreshold =
1226
+ function(oldEntry, newEntry) {
1227
+
1228
+ // To make comparing easier, an entry that has a ratio of 0
1229
+ // but does not actually intersect is given a value of -1
1230
+ var oldRatio = oldEntry && oldEntry.isIntersecting ?
1231
+ oldEntry.intersectionRatio || 0 : -1;
1232
+ var newRatio = newEntry.isIntersecting ?
1233
+ newEntry.intersectionRatio || 0 : -1;
1234
+
1235
+ // Ignore unchanged ratios
1236
+ if (oldRatio === newRatio) return;
1237
+
1238
+ for (var i = 0; i < this.thresholds.length; i++) {
1239
+ var threshold = this.thresholds[i];
1240
+
1241
+ // Return true if an entry matches a threshold or if the new ratio
1242
+ // and the old ratio are on the opposite sides of a threshold.
1243
+ if (threshold == oldRatio || threshold == newRatio ||
1244
+ threshold < oldRatio !== threshold < newRatio) {
1245
+ return true;
595
1246
  }
1247
+ }
1248
+ };
596
1249
 
597
- /**
598
- * Fade out the placeholder image.
599
- */
600
1250
 
601
- }, {
602
- key: 'fadeOutPlaceholder',
603
- value: function fadeOutPlaceholder() {
604
- this.placeholderImg.fadeTo(this.timeToFade, 0);
605
- }
1251
+ /**
1252
+ * Returns whether or not the root element is an element and is in the DOM.
1253
+ * @return {boolean} True if the root element is an element and is in the DOM.
1254
+ * @private
1255
+ */
1256
+ IntersectionObserver.prototype._rootIsInDom = function() {
1257
+ return !this.root || containsDeep(document, this.root);
1258
+ };
606
1259
 
607
- /**
608
- * Remove temporary styles and class from the full-size image, which
609
- * effectively means it has replaced the placeholder image.
610
- */
611
1260
 
612
- }, {
613
- key: 'removeFullSizeImgProperties',
614
- value: function removeFullSizeImgProperties() {
615
- this.fullSizeImg.removeAttr('style');
616
- // TODO: Update this with how the class is handled above.
617
- this.fullSizeImg.removeClass('imgix-optimizing');
1261
+ /**
1262
+ * Returns whether or not the target element is a child of root.
1263
+ * @param {Element} target The target element to check.
1264
+ * @return {boolean} True if the target element is a child of root.
1265
+ * @private
1266
+ */
1267
+ IntersectionObserver.prototype._rootContainsTarget = function(target) {
1268
+ return containsDeep(this.root || document, target);
1269
+ };
1270
+
1271
+
1272
+ /**
1273
+ * Adds the instance to the global IntersectionObserver registry if it isn't
1274
+ * already present.
1275
+ * @private
1276
+ */
1277
+ IntersectionObserver.prototype._registerInstance = function() {
1278
+ };
1279
+
1280
+
1281
+ /**
1282
+ * Removes the instance from the global IntersectionObserver registry.
1283
+ * @private
1284
+ */
1285
+ IntersectionObserver.prototype._unregisterInstance = function() {
1286
+ };
1287
+
1288
+
1289
+ /**
1290
+ * Returns the result of the performance.now() method or null in browsers
1291
+ * that don't support the API.
1292
+ * @return {number} The elapsed time since the page was requested.
1293
+ */
1294
+ function now() {
1295
+ return window.performance && performance.now && performance.now();
1296
+ }
1297
+
1298
+
1299
+ /**
1300
+ * Throttles a function and delays its execution, so it's only called at most
1301
+ * once within a given time period.
1302
+ * @param {Function} fn The function to throttle.
1303
+ * @param {number} timeout The amount of time that must pass before the
1304
+ * function can be called again.
1305
+ * @return {Function} The throttled function.
1306
+ */
1307
+ function throttle(fn, timeout) {
1308
+ var timer = null;
1309
+ return function () {
1310
+ if (!timer) {
1311
+ timer = setTimeout(function() {
1312
+ fn();
1313
+ timer = null;
1314
+ }, timeout);
618
1315
  }
1316
+ };
1317
+ }
1318
+
1319
+
1320
+ /**
1321
+ * Adds an event handler to a DOM node ensuring cross-browser compatibility.
1322
+ * @param {Node} node The DOM node to add the event handler to.
1323
+ * @param {string} event The event name.
1324
+ * @param {Function} fn The event handler to add.
1325
+ * @param {boolean} opt_useCapture Optionally adds the even to the capture
1326
+ * phase. Note: this only works in modern browsers.
1327
+ */
1328
+ function addEvent(node, event, fn, opt_useCapture) {
1329
+ if (typeof node.addEventListener == 'function') {
1330
+ node.addEventListener(event, fn, opt_useCapture || false);
1331
+ }
1332
+ else if (typeof node.attachEvent == 'function') {
1333
+ node.attachEvent('on' + event, fn);
1334
+ }
1335
+ }
1336
+
1337
+
1338
+ /**
1339
+ * Removes a previously added event handler from a DOM node.
1340
+ * @param {Node} node The DOM node to remove the event handler from.
1341
+ * @param {string} event The event name.
1342
+ * @param {Function} fn The event handler to remove.
1343
+ * @param {boolean} opt_useCapture If the event handler was added with this
1344
+ * flag set to true, it should be set to true here in order to remove it.
1345
+ */
1346
+ function removeEvent(node, event, fn, opt_useCapture) {
1347
+ if (typeof node.removeEventListener == 'function') {
1348
+ node.removeEventListener(event, fn, opt_useCapture || false);
1349
+ }
1350
+ else if (typeof node.detatchEvent == 'function') {
1351
+ node.detatchEvent('on' + event, fn);
1352
+ }
1353
+ }
1354
+
1355
+
1356
+ /**
1357
+ * Returns the intersection between two rect objects.
1358
+ * @param {Object} rect1 The first rect.
1359
+ * @param {Object} rect2 The second rect.
1360
+ * @return {?Object} The intersection rect or undefined if no intersection
1361
+ * is found.
1362
+ */
1363
+ function computeRectIntersection(rect1, rect2) {
1364
+ var top = Math.max(rect1.top, rect2.top);
1365
+ var bottom = Math.min(rect1.bottom, rect2.bottom);
1366
+ var left = Math.max(rect1.left, rect2.left);
1367
+ var right = Math.min(rect1.right, rect2.right);
1368
+ var width = right - left;
1369
+ var height = bottom - top;
1370
+
1371
+ return (width >= 0 && height >= 0) && {
1372
+ top: top,
1373
+ bottom: bottom,
1374
+ left: left,
1375
+ right: right,
1376
+ width: width,
1377
+ height: height
1378
+ };
1379
+ }
1380
+
1381
+
1382
+ /**
1383
+ * Shims the native getBoundingClientRect for compatibility with older IE.
1384
+ * @param {Element} el The element whose bounding rect to get.
1385
+ * @return {Object} The (possibly shimmed) rect of the element.
1386
+ */
1387
+ function getBoundingClientRect(el) {
1388
+ var rect;
1389
+
1390
+ try {
1391
+ rect = el.getBoundingClientRect();
1392
+ } catch (err) {
1393
+ // Ignore Windows 7 IE11 "Unspecified error"
1394
+ // https://github.com/w3c/IntersectionObserver/pull/205
1395
+ }
619
1396
 
620
- /**
621
- * Remove the placeholder image from the DOM since we no longer need it.
622
- */
1397
+ if (!rect) return getEmptyRect();
1398
+
1399
+ // Older IE
1400
+ if (!(rect.width && rect.height)) {
1401
+ rect = {
1402
+ top: rect.top,
1403
+ right: rect.right,
1404
+ bottom: rect.bottom,
1405
+ left: rect.left,
1406
+ width: rect.right - rect.left,
1407
+ height: rect.bottom - rect.top
1408
+ };
1409
+ }
1410
+ return rect;
1411
+ }
1412
+
1413
+
1414
+ /**
1415
+ * Returns an empty rect object. An empty rect is returned when an element
1416
+ * is not in the DOM.
1417
+ * @return {Object} The empty rect.
1418
+ */
1419
+ function getEmptyRect() {
1420
+ return {
1421
+ top: 0,
1422
+ bottom: 0,
1423
+ left: 0,
1424
+ right: 0,
1425
+ width: 0,
1426
+ height: 0
1427
+ };
1428
+ }
1429
+
1430
+ /**
1431
+ * Checks to see if a parent element contains a child element (including inside
1432
+ * shadow DOM).
1433
+ * @param {Node} parent The parent element.
1434
+ * @param {Node} child The child element.
1435
+ * @return {boolean} True if the parent node contains the child node.
1436
+ */
1437
+ function containsDeep(parent, child) {
1438
+ var node = child;
1439
+ while (node) {
1440
+ if (node == parent) return true;
1441
+
1442
+ node = getParentNode(node);
1443
+ }
1444
+ return false;
1445
+ }
1446
+
1447
+
1448
+ /**
1449
+ * Gets the parent node of an element or its host element if the parent node
1450
+ * is a shadow root.
1451
+ * @param {Node} node The node whose parent to get.
1452
+ * @return {Node|null} The parent node or null if no parent exists.
1453
+ */
1454
+ function getParentNode(node) {
1455
+ var parent = node.parentNode;
1456
+
1457
+ if (parent && parent.nodeType == 11 && parent.host) {
1458
+ // If the parent is a shadow root, return the host element.
1459
+ return parent.host;
1460
+ }
1461
+ return parent;
1462
+ }
623
1463
 
624
- }, {
625
- key: 'removeImg',
626
- value: function removeImg() {
627
- if (!this.placeholderImg) {
628
- return;
1464
+
1465
+ // Exposes the constructors globally.
1466
+ window.IntersectionObserver = IntersectionObserver;
1467
+ window.IntersectionObserverEntry = IntersectionObserverEntry;
1468
+
1469
+ }(window, document));
1470
+
1471
+ if (typeof Object.assign != 'function') {
1472
+ // Must be writable: true, enumerable: false, configurable: true
1473
+ Object.defineProperty(Object, 'assign', {
1474
+ value: function assign(target, varArgs) {
1475
+
1476
+ if (target == null) {
1477
+ // TypeError if undefined or null
1478
+ throw new TypeError('Cannot convert undefined or null to object');
629
1479
  }
630
- this.placeholderImg.remove();
631
- this.placeholderImg = undefined;
632
- }
633
- }]);
634
- return ImgixImage;
635
- }();
1480
+
1481
+ var to = Object(target);
1482
+
1483
+ for (var index = 1; index < arguments.length; index++) {
1484
+ var nextSource = arguments[index];
1485
+
1486
+ if (nextSource != null) {
1487
+ // Skip over if undefined or null
1488
+ for (var nextKey in nextSource) {
1489
+ // Avoid bugs when hasOwnProperty is shadowed
1490
+ if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
1491
+ to[nextKey] = nextSource[nextKey];
1492
+ }
1493
+ }
1494
+ }
1495
+ }
1496
+ return to;
1497
+ },
1498
+ writable: true,
1499
+ configurable: true
1500
+ });
1501
+ }
636
1502
 
637
1503
  var Optimizer = function () {
638
1504
  function Optimizer() {
639
1505
  var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
640
1506
  classCallCheck(this, Optimizer);
641
1507
 
1508
+ this.initDependencies();
642
1509
  this.initOptions(options);
643
1510
  this.optimizeImages();
644
1511
  this.optimizeBgImages();
645
1512
  }
646
1513
 
647
- // ---------------------------------------- | Options
1514
+ // ---------------------------------------- | Dependencies
648
1515
 
649
1516
  createClass(Optimizer, [{
1517
+ key: 'initDependencies',
1518
+ value: function initDependencies() {
1519
+ }
1520
+
1521
+ // ---------------------------------------- | Options
1522
+
1523
+ }, {
650
1524
  key: 'initOptions',
651
1525
  value: function initOptions() {
652
1526
  var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
@@ -668,7 +1542,7 @@
668
1542
  key: 'optimizeImages',
669
1543
  value: function optimizeImages() {
670
1544
  $(this.options.parent + ' img[data-optimize-img]').each(function (idx, img) {
671
- new ImgixImage(img);
1545
+ new Image(img);
672
1546
  });
673
1547
  }
674
1548
 
@@ -678,7 +1552,7 @@
678
1552
  key: 'optimizeBgImages',
679
1553
  value: function optimizeBgImages() {
680
1554
  $(this.options.parent + ' [data-optimize-bg-img]').each(function (idx, img) {
681
- new ImgixBgImage(img);
1555
+ new BackgroundImage(img);
682
1556
  });
683
1557
  return true;
684
1558
  }
@@ -688,8 +1562,6 @@
688
1562
 
689
1563
  window['Imgix'] = window['Imgix'] || {};
690
1564
 
691
- Imgix.ImgixBgImage = ImgixBgImage;
692
- Imgix.ImgixImage = ImgixImage;
693
1565
  Imgix.Optimizer = Optimizer;
694
1566
 
695
1567
  }());