Files
kupshop/web/common/static/jquery/jquery.unveil2.js
2025-08-02 16:30:27 +02:00

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);