|
@@ -0,0 +1,232 @@
|
|
|
+/*!
|
|
|
+ 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;
|
|
|
+});
|