;(function(){ 'use strict'; // can we support addeventlistener var hasnative = 'addeventlistener' in (new image()); /** * @constructor * @param {array} images - string of images to load * @param {object=} options - overrides to defaults */ var preloader = function(images, options){ this.options = { pipeline: false, auto: true, prefetch: false, /* onprogress: function(){}, */ /* onerror: function(){}, */ oncomplete: function(){} }; options && typeof options == 'object' && this.setoptions(options); this.addqueue(images); this.queue.length && this.options.auto && this.processqueue(); }; /** * naive shallow copy/reference from options into proto options * @param {object} options * @returns {preloader} */ preloader.prototype.setoptions = function(options){ // shallow copy var o = this.options, key; for (key in options) options.hasownproperty(key) && (o[key] = options[key]); return this; }; /** * stores a local array, dereferenced from original * @param images * @returns {preloader} */ preloader.prototype.addqueue = function(images){ this.queue = images.slice(); return this; }; /** * reset the arrays * @returns {preloader} */ preloader.prototype.reset = function(){ this.completed = []; this.errors = []; return this; }; /** * subscribe to events for an imag object and a source * @param {object} image * @param {string} src * @param {number} index * @returns {preloader} * @private */ preloader.prototype._addevents = function(image, src, index){ var self = this, o = this.options, cleanup = function(){ if (hasnative){ this.removeeventlistener('error', abort); this.removeeventlistener('abort', abort); this.removeeventlistener('load', load); } else { this.onerror = this.onabort = this.onload = null; } }, abort = function(){ cleanup.call(this); self.errors.push(src); o.onerror && o.onerror.call(self, src); _checkprogress.call(self, src); o.pipeline && self._loadnext(index); }, load = function(){ cleanup.call(this); // store progress. this === image self.completed.push(src); // this.src may differ _checkprogress.call(self, src, this); o.pipeline && self._loadnext(index); }; if (hasnative){ image.addeventlistener('error', abort, false); image.addeventlistener('abort', abort, false); image.addeventlistener('load', load, false); } else { image.onerror = image.onabort = abort; image.onload = load; } return this; }; /** * private api to load an image * @param {string} src * @param {number} index * @returns {preloader} * @private */ preloader.prototype._load = function(src, index){ /*jshint -w058 */ var image = new image; this._addevents(image, src, index); // actually load image.src = src; return this; }; /** * move up the queue index * @param {number} index * @returns {preloader} * @private */ preloader.prototype._loadnext = function(index){ // when pipeline loading is enabled, calls next item index++; this.queue[index] && this._load(this.queue[index], index); return this; }; /** * iterates through the queue of images to load * @returns {preloader} */ preloader.prototype.processqueue = function(){ // runs through all queued items. var i = 0, queue = this.queue, len = queue.length; // process all queue items this.reset(); if (!this.options.pipeline) for (; i < len; ++i) this._load(queue[i], i); else this._load(queue[0], 0); return this; }; /*jshint validthis:true */ /** * internal checker on the queue progress * @param {string} src * @param {object} image * @returns {preloader} * @private */ function _checkprogress(src, image){ // intermediate checker for queue remaining. not exported. // called on preloader instance as scope var args = [], o = this.options; // call onprogress o.onprogress && src && o.onprogress.call(this, src, image, this.completed.length); if (this.completed.length + this.errors.length === this.queue.length){ args.push(this.completed); this.errors.length && args.push(this.errors); o.oncomplete.apply(this, args); } return this; } /*jshint validthis:false */ /** * static method that loads images lazily from dom based upon data-preload attribute * @param {object} options= optional options to pass to preloader * @returns {preloader} instance */ preloader.lazyload = function(options){ if (!options) options = {}; var lazyimages = document.queryselectorall(options.selector || 'img[data-preload]'), i = 0, l = lazyimages.length, toload = [], oldprogress; for (; i < l; i++) toload.push(lazyimages[i].getattribute('data-preload')); options.onprogress && (oldprogress = options.onprogress); options.onprogress = function(item, imgel, index){ lazyimages[index-1].src = item; lazyimages[index-1].removeattribute('data-preload'); oldprogress && oldprogress.apply(this, arguments); }; return toload.length ? new preloader(toload, options) : null; }; if (typeof define === 'function' && define.amd){ // we have an amd loader. define(function(){ return preloader; }); } else { this.preloader = preloader; } }).call(this);