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 +4 -4
- data/.gitignore +1 -0
- data/Gemfile.lock +2 -2
- data/dist/crds.html +57 -0
- data/dist/imgix-optimizer-0.0.6.min.js +1 -0
- data/dist/imgix-optimizer.js +1063 -191
- data/dist/index.html +209 -85
- data/package-lock.json +328 -332
- data/package.json +2 -1
- data/src/{imgix_bg_image.js → background_image.js} +55 -16
- data/src/{imgix_image.js → image.js} +80 -10
- data/src/main.js +0 -4
- data/src/optimizer.js +15 -7
- data/src/polyfills/object_assign.js +32 -0
- metadata +7 -6
- data/dist/imgix-optimizer-0.0.5.min.js +0 -1
- data/vendor/assets/javascripts/imgix-optimizer.js +0 -695
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 6630b32252b9019d7cdcb4b0652f7d2dcf2dd09176f624cbbdfa64f041b3dcbb
|
4
|
+
data.tar.gz: 9715b46c1e2520797153afdbbb1a679646417a8ea1876504cce8959009011455
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6c8152c7c744eb1e66a8a136330044e6a96ea75842622421904aa4709d9a5bdfc4e66c397e3a80e7f1cefb96b1ac052664d81e7fba875345e937c7e82b5cc624
|
7
|
+
data.tar.gz: 94fcbdfb69bc9c3fd5934bcd9a969982d9e3e0d7b6d2c3d43363c8d95c2d779b7b8b641a9793b71fc9d92f4064924f33422026d8462a6ad34bd094899af50b93
|
data/.gitignore
CHANGED
data/Gemfile.lock
CHANGED
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}();
|
data/dist/imgix-optimizer.js
CHANGED
@@ -25,14 +25,315 @@
|
|
25
25
|
};
|
26
26
|
}();
|
27
27
|
|
28
|
-
var
|
29
|
-
function
|
30
|
-
classCallCheck(this,
|
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(
|
358
|
+
createClass(BackgroundImage, [{
|
58
359
|
key: 'initOptimization',
|
59
360
|
value: function initOptimization() {
|
60
|
-
|
361
|
+
$('<img>').on('load', $.proxy(this.listenForIntersection, this)).attr('src', this.placeholderImgUrl);
|
362
|
+
}
|
61
363
|
|
62
|
-
|
63
|
-
|
64
|
-
|
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,
|
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
|
-
//
|
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.
|
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
|
643
|
+
var _this = this;
|
313
644
|
|
314
645
|
this.fadeOutTmpPlaceholderEl();
|
315
646
|
setTimeout(function () {
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
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
|
-
|
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
|
756
|
+
return BackgroundImage;
|
422
757
|
}();
|
423
758
|
|
424
|
-
|
425
|
-
|
426
|
-
|
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
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
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
|
-
|
440
|
-
|
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
|
-
|
446
|
-
|
447
|
-
|
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
|
-
|
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
|
-
|
459
|
-
|
460
|
-
|
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
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
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
|
-
|
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
|
-
|
481
|
-
*/
|
997
|
+
return margins;
|
998
|
+
};
|
482
999
|
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
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
|
-
|
500
|
-
|
501
|
-
|
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
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
514
|
-
|
515
|
-
|
516
|
-
|
517
|
-
|
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
|
-
|
523
|
-
|
524
|
-
|
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
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
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
|
-
|
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
|
-
|
556
|
-
|
557
|
-
|
558
|
-
|
559
|
-
|
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
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
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
|
-
|
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
|
-
|
590
|
-
|
591
|
-
|
592
|
-
|
593
|
-
|
594
|
-
|
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
|
-
|
603
|
-
|
604
|
-
|
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
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
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
|
-
|
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
|
-
|
626
|
-
|
627
|
-
|
628
|
-
|
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
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
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
|
-
// ---------------------------------------- |
|
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
|
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
|
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
|
}());
|