+/*!
+ hey, [be]Lazy.js - v1.3.1 - 2015.02.01
+ A lazy loading and multi-serving image script
+ (c) Bjoern Klinggaard - @bklinggaard - http://dinbror.dk/blazy
+*/
+;(function(root, blazy) {
+ if (typeof define === 'function' && define.amd) {
+ // AMD. Register bLazy as an anonymous module
+ define(blazy);
+ } else if (typeof exports === 'object') {
+ // Node. Does not work with strict CommonJS, but
+ // only CommonJS-like environments that support module.exports,
+ // like Node.
+ module.exports = blazy();
+ } else {
+ // Browser globals. Register bLazy on window
+ root.Blazy = blazy();
+ }
+})(this, function () {
+ 'use strict';
+
+ //vars
+ var source, options, viewport, images, count, isRetina, destroyed;
+ //throttle vars
+ var validateT, saveViewportOffsetT;
+
+ // constructor
+ function Blazy(settings) {
+ //IE7- fallback for missing querySelectorAll support
+ if (!document.querySelectorAll) {
+ var s=document.createStyleSheet();
+ document.querySelectorAll = function(r, c, i, j, a) {
+ a=document.all, c=[], r = r.replace(/\[for\b/gi, '[htmlFor').split(',');
+ for (i=r.length; i--;) {
+ s.addRule(r[i], 'k:v');
+ for (j=a.length; j--;) a[j].currentStyle.k && c.push(a[j]);
+ s.removeRule(0);
+ }
+ return c;
+ };
+ }
+ //init vars
+ destroyed = true;
+ images = [];
+ viewport = {};
+ //options
+ options = settings || {};
+ options.error = options.error || false;
+ options.offset = options.offset || 100;
+ options.success = options.success || false;
+ options.selector = options.selector || '.b-lazy';
+ options.separator = options.separator || '|';
+ options.container = options.container ? document.querySelectorAll(options.container) : false;
+ options.errorClass = options.errorClass || 'b-error';
+ options.breakpoints = options.breakpoints || false;
+ options.successClass = options.successClass || 'b-loaded';
+ options.src = source = options.src || 'data-src';
+ isRetina = window.devicePixelRatio > 1;
+ viewport.top = 0 - options.offset;
+ viewport.left = 0 - options.offset;
+ //throttle, ensures that we don't call the functions too often
+ validateT = throttle(validate, 25);
+ saveViewportOffsetT = throttle(saveViewportOffset, 50);
+
+ saveViewportOffset();
+
+ //handle multi-served image src
+ each(options.breakpoints, function(object){
+ if(object.width >= window.screen.width) {
+ source = object.src;
+ return false;
+ }
+ });
+
+ // start lazy load
+ initialize();
+ }
+
+ /* public functions
+ ************************************/
+ Blazy.prototype.revalidate = function() {
+ initialize();
+ };
+ Blazy.prototype.load = function(element, force){
+ if(!isElementLoaded(element)) loadImage(element, force);
+ };
+ Blazy.prototype.destroy = function(){
+ if(options.container){
+ each(options.container, function(object){
+ unbindEvent(object, 'scroll', validateT);
+ });
+ }
+ unbindEvent(window, 'scroll', validateT);
+ unbindEvent(window, 'resize', validateT);
+ unbindEvent(window, 'resize', saveViewportOffsetT);
+ count = 0;
+ images.length = 0;
+ destroyed = true;
+ };
+
+ /* private helper functions
+ ************************************/
+ function initialize(){
+ // First we create an array of images to lazy load
+ createImageArray(options.selector);
+ // Then we bind resize and scroll events if not already binded
+ if(destroyed) {
+ destroyed = false;
+ if(options.container) {
+ each(options.container, function(object){
+ bindEvent(object, 'scroll', validateT);
+ });
+ }
+ bindEvent(window, 'resize', saveViewportOffsetT);
+ bindEvent(window, 'resize', validateT);
+ bindEvent(window, 'scroll', validateT);
+ }
+ // And finally, we start to lazy load. Should bLazy ensure domready?
+ validate();
+ }
+
+ function validate() {
+ for(var i = 0; i<count; i++){
+ var image = images[i];
+ if(elementInView(image) || isElementLoaded(image)) {
+ Blazy.prototype.load(image);
+ images.splice(i, 1);
+ count--;
+ i--;
+ }
+ }
+ if(count === 0) {
+ Blazy.prototype.destroy();
+ }
+ }
+
+ function loadImage(ele, force){
+ // if element is visible
+ if(force || (ele.offsetWidth > 0 && ele.offsetHeight > 0)) {
+ var dataSrc = ele.getAttribute(source) || ele.getAttribute(options.src); // fallback to default data-src
+ if(dataSrc) {
+ var dataSrcSplitted = dataSrc.split(options.separator);
+ var src = dataSrcSplitted[isRetina && dataSrcSplitted.length > 1 ? 1 : 0];
+ var img = new Image();
+ // cleanup markup, remove data source attributes
+ each(options.breakpoints, function(object){
+ ele.removeAttribute(object.src);
+ });
+ ele.removeAttribute(options.src);
+ img.onerror = function() {
+ if(options.error) options.error(ele, "invalid");
+ ele.className = ele.className + ' ' + options.errorClass;
+ };
+ img.onload = function() {
+ // Is element an image or should we add the src as a background image?
+ ele.nodeName.toLowerCase() === 'img' ? ele.src = src : ele.style.backgroundImage = 'url("' + src + '")';
+ ele.className = ele.className + ' ' + options.successClass;
+ if(options.success) options.success(ele);
+ };
+ img.src = src; //preload image
+ } else {
+ if(options.error) options.error(ele, "missing");
+ ele.className = ele.className + ' ' + options.errorClass;
+ }
+ }
+ }
+
+ function elementInView(ele) {
+ var rect = ele.getBoundingClientRect();
+
+ return (
+ // Intersection
+ rect.right >= viewport.left
+ && rect.bottom >= viewport.top
+ && rect.left <= viewport.right
+ && rect.top <= viewport.bottom
+ );
+ }
+
+ function isElementLoaded(ele) {
+ return (' ' + ele.className + ' ').indexOf(' ' + options.successClass + ' ') !== -1;
+ }
+
+ function createImageArray(selector) {
+ var nodelist = document.querySelectorAll(selector);
+ count = nodelist.length;
+ //converting nodelist to array
+ for(var i = count; i--; images.unshift(nodelist[i])){}
+ }
+
+ function saveViewportOffset(){
+ viewport.bottom = (window.innerHeight || document.documentElement.clientHeight) + options.offset;
+ viewport.right = (window.innerWidth || document.documentElement.clientWidth) + options.offset;
+ }
+
+ function bindEvent(ele, type, fn) {
+ if (ele.attachEvent) {
+ ele.attachEvent && ele.attachEvent('on' + type, fn);
+ } else {
+ ele.addEventListener(type, fn, false);
+ }
+ }
+
+ function unbindEvent(ele, type, fn) {
+ if (ele.detachEvent) {
+ ele.detachEvent && ele.detachEvent('on' + type, fn);
+ } else {
+ ele.removeEventListener(type, fn, false);
+ }
+ }
+
+ function each(object, fn){
+ if(object && fn) {
+ var l = object.length;
+ for(var i = 0; i<l && fn(object[i], i) !== false; i++){}
+ }
+ }
+
+ function throttle(fn, minDelay) {
+ var lastCall = 0;
+ return function() {
+ var now = +new Date();
+ if (now - lastCall < minDelay) {
+ return;
+ }
+ lastCall = now;
+ fn.apply(images, arguments);
+ };
+ }
+
+ return Blazy;
+});