320 lines
9.0 KiB
JavaScript
320 lines
9.0 KiB
JavaScript
/**
|
|
* Unveil2.js
|
|
* A very lightweight jQuery/Zepto plugin to lazy load images
|
|
* Based on https://github.com/luis-almeida/unveil
|
|
*
|
|
* Licensed under the MIT license.
|
|
* Copyright 2015 Joram van den Boezem
|
|
* https://github.com/nabble/unveil2
|
|
*/
|
|
(function ($) {
|
|
|
|
"use strict";
|
|
|
|
/**
|
|
* # GLOBAL VARIABLES
|
|
* ---
|
|
*/
|
|
|
|
/**
|
|
* Store the string 'unveil' in a variable to save some bytes
|
|
*
|
|
*/
|
|
var unveilString = 'unveil';
|
|
|
|
/**
|
|
* A jQuery/Zepto collection of images which will be lazy loaded
|
|
*/
|
|
var images = $();
|
|
|
|
/**
|
|
* A flag to set initialized state, so we can set global listeners only once
|
|
*/
|
|
var initialized = false;
|
|
|
|
/**
|
|
* # PLUGIN
|
|
* ---
|
|
*/
|
|
|
|
/**
|
|
* @param {object} opts An object of options, see API section in README
|
|
* @returns {$}
|
|
*/
|
|
$.fn.unveil = function (opts) {
|
|
|
|
opts = opts || {};
|
|
|
|
// Initialize variables
|
|
var $window = $(window),
|
|
height = $window.height(),
|
|
retina = window.devicePixelRatio > 1;
|
|
|
|
// Initialize defaults
|
|
var $container = opts.container || $window,
|
|
placeholder = opts.placeholder || 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7',
|
|
offset = opts.offset || 0,
|
|
breakpoints = opts.breakpoints || [],
|
|
throttleTimeout = opts.throttle || 250,
|
|
debug = opts.debug || false,
|
|
throttledLookup = throttle(lookup);
|
|
|
|
if (debug) console.log('Called unveil on', this.length, 'elements with the following options:', opts);
|
|
|
|
/**
|
|
* Sort sizes array, arrange highest minWidth to front of array
|
|
*/
|
|
breakpoints.sort(function (a, b) {
|
|
return b.minWidth - a.minWidth;
|
|
});
|
|
|
|
/**
|
|
* # UNVEIL IMAGES
|
|
* ---
|
|
*/
|
|
|
|
/**
|
|
* This is the actual plugin logic, which determines the source attribute to use based on window width and presence of a retina screen, changes the source of the image, handles class name changes and triggers a callback if set. Once the image has been loaded, start the unveil lookup because the page layout could have changed.
|
|
*/
|
|
this.one(unveilString, function () {
|
|
var i, $this = $(this), windowWidth = $window.width(),
|
|
attrib = 'src', targetSrc, defaultSrc, retinaSrc;
|
|
|
|
// Determine attribute to extract source from
|
|
for (i = 0; i < breakpoints.length; i++) {
|
|
var dataAttrib = breakpoints[i].attribute.replace(/^data-/, '');
|
|
if (windowWidth >= breakpoints[i].minWidth && $this.data(dataAttrib)) {
|
|
attrib = dataAttrib;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Extract source
|
|
defaultSrc = retinaSrc = $this.data(attrib);
|
|
|
|
// Do we have a retina source?
|
|
if (defaultSrc && defaultSrc.indexOf("|") !== -1) {
|
|
retinaSrc = defaultSrc.split("|")[1];
|
|
defaultSrc = defaultSrc.split("|")[0];
|
|
}
|
|
|
|
// Change attribute on image
|
|
if (defaultSrc) {
|
|
targetSrc = (retina && retinaSrc) ? retinaSrc : defaultSrc;
|
|
|
|
if (debug) console.log('Unveiling image', {
|
|
attribute: attrib,
|
|
retina: retina,
|
|
defaultSrc: defaultSrc,
|
|
retinaSrc: retinaSrc,
|
|
targetSrc: targetSrc
|
|
});
|
|
|
|
// Change classes
|
|
classLoading($this);
|
|
|
|
// Set new source
|
|
$this.prop("src", targetSrc);
|
|
|
|
// When new source has loaded, do stuff
|
|
$this.one('load', function () {
|
|
|
|
// Change classes
|
|
classLoaded($this);
|
|
|
|
// Fire up the callback if it's a function
|
|
if (typeof opts.loaded === "function") {
|
|
opts.loaded.call(this);
|
|
}
|
|
|
|
// Loading the image may have modified page layout,
|
|
// so unveil again
|
|
lookup();
|
|
});
|
|
|
|
// If the image has instantly loaded, change classes now
|
|
if (this.complete) {
|
|
classLoaded($this);
|
|
}
|
|
}
|
|
});
|
|
|
|
/**
|
|
* # HELPER FUNCTIONS
|
|
* ---
|
|
*/
|
|
|
|
/**
|
|
* Zepto doesn't support the :visible and :hidden selector by default
|
|
*
|
|
* @param {object} $elm
|
|
* @returns {boolean}
|
|
*/
|
|
function visible($elm) {
|
|
return !!($elm.width() || $elm.height()) && $elm.css("display") !== "none";
|
|
}
|
|
|
|
/**
|
|
* Sets the classes when an image is loading
|
|
*
|
|
* @param {object} $elm
|
|
*/
|
|
function classLoading($elm) {
|
|
$elm.addClass(unveilString + '-loading');
|
|
}
|
|
|
|
/**
|
|
* Sets the classes when an image is done loading
|
|
*
|
|
* @param {object} $elm
|
|
*/
|
|
function classLoaded($elm) {
|
|
$elm.removeClass(unveilString + '-placeholder ' + unveilString + '-loading');
|
|
$elm.addClass(unveilString + '-loaded');
|
|
}
|
|
|
|
/**
|
|
* Filter function which returns true when a given image is in the viewport.
|
|
*
|
|
* @returns {boolean}
|
|
*/
|
|
function inview() {
|
|
if (!document.documentElement.contains(this))
|
|
{
|
|
delete images[arguments[0]];
|
|
return false;
|
|
}
|
|
|
|
// jshint validthis: true
|
|
var $this = $(this);
|
|
|
|
if (!visible($this)) {
|
|
return;
|
|
}
|
|
|
|
var viewportTop = $window.scrollTop(),
|
|
viewportEnd = viewportTop + height,
|
|
containerTop = $container !== $window ? viewportTop - $container.offset().top : 0,
|
|
elementTop = $this.offset().top + containerTop,
|
|
elementEnd = elementTop + $this.height();
|
|
|
|
return elementEnd >= viewportTop - offset && elementTop <= viewportEnd + offset;
|
|
}
|
|
|
|
/**
|
|
* Sets the window height and calls the lookup function
|
|
*/
|
|
function resize() {
|
|
height = $window.height();
|
|
lookup();
|
|
}
|
|
|
|
/**
|
|
* Throttle function with function call in tail. Based on http://sampsonblog.com/749/simple-throttle-function
|
|
*
|
|
* @param {function} callback
|
|
* @returns {function}
|
|
*/
|
|
function throttle(callback) {
|
|
var wait = false; // Initially, we're not waiting
|
|
return function () { // We return a throttled function
|
|
if (!wait) { // If we're not waiting
|
|
wait = true; // Prevent future invocations
|
|
setTimeout(function () { // After a period of time
|
|
callback(); // Execute users function
|
|
wait = false; // And allow future invocations
|
|
}, throttleTimeout);
|
|
}
|
|
};
|
|
}
|
|
|
|
/**
|
|
* # LOOKUP FUNCTION
|
|
* ---
|
|
*/
|
|
|
|
/**
|
|
* Function which filters images which are in view and triggers the unveil event on those images.
|
|
*/
|
|
function lookup() {
|
|
if (debug) console.log('Unveiling');
|
|
|
|
var batch = images.filter(inview);
|
|
|
|
batch.trigger(unveilString);
|
|
images = images.not(batch);
|
|
|
|
if (batch.length) {
|
|
if (debug) console.log('New images in view', batch.length, ', leaves', images.length, 'in collection');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* # BOOTSTRAPPING
|
|
* ---
|
|
*/
|
|
|
|
/**
|
|
* Read and reset the src attribute, to prevent loading of original images
|
|
*/
|
|
this.each(function () {
|
|
var $this = $(this),
|
|
elmPlaceholder = $this.data('src-placeholder') || placeholder;
|
|
|
|
// Add element to global array
|
|
images = $(images).add(this);
|
|
|
|
// If this element has been called before,
|
|
// don't set placeholder now to prevent FOUI (Flash Of Ustyled Image)
|
|
if (!$this.data(unveilString)) {
|
|
|
|
// Set the unveil flag
|
|
$this.data(unveilString, true);
|
|
|
|
// Set data-src if not set
|
|
if (!$this.data('src')) {
|
|
$this.data('src', $this.prop('src'));
|
|
}
|
|
|
|
// Set placeholder
|
|
$this
|
|
.one('load', function () {
|
|
$(this).addClass(unveilString + '-placeholder');
|
|
throttledLookup();
|
|
})
|
|
.prop('src', '')
|
|
.prop('src', elmPlaceholder);
|
|
}
|
|
});
|
|
|
|
if (debug) console.log('Images now in collection', images.length);
|
|
|
|
/**
|
|
* Bind global listeners
|
|
*/
|
|
{if (!initialized) {
|
|
$container.on({
|
|
'resize.unveil': throttle(resize),
|
|
'scroll.unveil': throttledLookup,
|
|
'lookup.unveil': lookup
|
|
});
|
|
initialized = true;
|
|
}}
|
|
|
|
/**
|
|
* Wait a little bit for the placeholder to be set, so the src attribute is not empty (which will mark the image as hidden)
|
|
*/
|
|
{setTimeout(lookup, 0);}
|
|
|
|
/**
|
|
* That's all folks!
|
|
*/
|
|
return this;
|
|
|
|
};
|
|
|
|
$.fn.unveil.clean = function (){
|
|
//images = $();
|
|
};
|
|
|
|
})(window.jQuery || window.Zepto); |