├── LICENSE.txt ├── README.md └── jquery.imageloader.js /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (C) 2012, Takashi MIZOHATA. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | jQuery.imageloader() 2 | ==================== 3 | 4 | jQuery.imageloader() lets you have granular controls of image loading timing. 5 | 6 | DEMO 7 | ---- 8 | 9 | [beatak.github.com/jquery-imageloader/](http://beatak.github.com/jquery-imageloader/ "github page") 10 | 11 | EXAMPLE 12 | ------- 13 | 14 | $('.YOUR-SELECTOR').imageloader(OPTIONS) 15 | 16 | OPTIONS 17 | ------- 18 | 19 | The instance option as follows: 20 | 21 | Name|Type|Default|Description 22 | ----|----|-------|----------- 23 | **selector**|string|(empty string)|Selector for the elements that $.fn.imageloader() will look for in order to load the image. If the empty string is passed, $.fn.imageloader() will try to load the image on "*this*" object. 24 | **dataattr**|string|"src"|Data attribute for the elements that $.fn.imageloader() will look for. As default, $.fn.imageloader() will look for data-src attribute. 25 | **background**|boolean|false|*true* if you want to load image as background-image css. 26 | **each**|function|null|Callback function for each image is loaded. when you try to load one image, it doesn't make any difference from *callback*. See [Demo](http://beatak.github.com/jquery-imageloader/ "github page") 27 | **callback**|function|null|Callback function when the image is loaded. "*this*" is passed as the first argument. See [Demo](http://beatak.github.com/jquery-imageloader/ "github page") 28 | **timeout**|number|5000|Millisecond for loading timeout. 29 | 30 | *** 31 | 32 | There is also a global option: 33 | 34 | Name|Type|Default|Description 35 | ----|----|-------|----------- 36 | **$.imageloader.queueInterval**|number|17|A browser can only issue parallel HTTP requests 2 to 9 for each domain name as a default ([according to Browserscope](http://www.browserscope.org/?category=network "Connections per Hostname"), May 2012). When you try to load massive numbers of images all at once, it can consume all browser's UI thread. So *$.fn.imageloader()* is using a time-domain queue for controling loading timing. 17 millisecond is a one frame delay under 60fps. As default, when you apply $.fn.imageloader() to numbers of elements, it will make <img> elements every 17 milliseconds. 37 | 38 | 39 | CREDIT 40 | ------ 41 | 42 | © 2012 Takashi Mizohata. 43 | 44 | Licensed under MIT. 45 | 46 | Special thanks to [Meetup](http://www.meetup.com/ "Meetup") -------------------------------------------------------------------------------- /jquery.imageloader.js: -------------------------------------------------------------------------------- 1 | (function($) { 2 | 3 | "use strict"; 4 | 5 | /** 6 | * jQuery.imageloader 7 | * (C) 2012, Takashi Mizohata 8 | * http://beatak.github.com/jquery-imageloader/ 9 | * MIT LICENSE 10 | */ 11 | 12 | var DEFAULT_OPTIONS = { 13 | selector: '', 14 | dataattr: 'src', 15 | background: false, 16 | each: null, 17 | eacherror: null, 18 | callback: null, 19 | timeout: 5000 20 | }; 21 | 22 | var init = function (_i, self, opts) { 23 | var q = Queue.getInstance(); 24 | var $this = $(self); 25 | var defaults = $.extend({}, DEFAULT_OPTIONS, opts || {}); 26 | var ns = '_' + ('' + (new Date()).valueOf()).slice(-7); 27 | var $elms; 28 | var len = 0; 29 | 30 | if (defaults.selector === '' && $this.data(defaults.dataattr) ) { 31 | $elms = $this; 32 | len = 1; 33 | } 34 | else { 35 | $elms = $this.find([defaults.selector, '[data-', defaults.dataattr, ']'].join('')); 36 | len = $elms.length; 37 | } 38 | 39 | $this.data( 40 | ns, 41 | { 42 | each: defaults.each, 43 | eacherror: defaults.eacherror, 44 | callback: defaults.callback, 45 | isLoading: true, 46 | loadedImageCounter: 0, 47 | length: len 48 | } 49 | ); 50 | 51 | if (len === 0) { 52 | finishImageLoad(self, ns); 53 | } 54 | else { 55 | $elms.each( 56 | function (i, elm) { 57 | q.add(buildImageLoadFunc(elm, self, ns, defaults.background, defaults.dataattr, defaults.timeout)); 58 | } 59 | ); 60 | // console.log(['we are gonna load ', len, ' image(s) on ', ns].join('')); 61 | $this.on('loadImage.' + ns, onLoadImage); 62 | q.run(); 63 | } 64 | return self; 65 | }; 66 | 67 | // =================================================================== 68 | 69 | var onLoadImage = function (ev, elm, img, isError) { 70 | // console.log('onLoadImage: ', ev.namespace); 71 | var parent = ev.currentTarget; 72 | var defaults = $(parent).data(ev.namespace); 73 | if (!defaults.isLoading) { 74 | // console.log('onLoadImage: is not loading but still called?'); 75 | return; 76 | } 77 | if (isError) { 78 | if (typeof defaults.eacherror === 'function') { 79 | defaults.eacherror(elm); 80 | } 81 | else { 82 | if (elm.parentNode !== null) { 83 | elm.parentNode.removeChild(elm); 84 | } 85 | } 86 | } 87 | else if (typeof defaults.each === 'function') { 88 | defaults.each(elm, img); 89 | } 90 | ++defaults.loadedImageCounter; 91 | if (defaults.loadedImageCounter >= defaults.length) { 92 | finishImageLoad(parent, ev.namespace); 93 | } 94 | }; 95 | 96 | var finishImageLoad = function (parent, ns) { 97 | // console.log('finishImageLoad: ', ns); 98 | var $parent = $(parent); 99 | var data = $parent.data(); 100 | var callback = data[ns].callback; 101 | $parent.off('loadImage.' + ns, onLoadImage); 102 | delete data[ns]; 103 | if (typeof callback === 'function') { 104 | setTimeout( 105 | function () { 106 | callback(parent); 107 | }, 108 | $.imageloader.queueInterval * 2 109 | ); 110 | } 111 | }; 112 | 113 | var buildImageLoadFunc = function (elm, parent, namespace, isBg, attr, milsec_timeout) { 114 | var $elm = $(elm); 115 | var src = $elm.data(attr); 116 | var hasFinished = false; 117 | 118 | var onFinishLoagImage = function (ev, img) { 119 | // delete attribute 120 | $elm.removeAttr( ['data-', attr].join('') ); 121 | $(parent).triggerHandler('loadImage.' + namespace, [elm, img, (ev && ev.type === 'error')]); 122 | }; 123 | 124 | return function () { 125 | var timer_handler; 126 | var $img = $(''); // this statement is kinda silly, but IE needs this separated. 127 | $img 128 | .bind( 129 | 'error', 130 | function (ev) { 131 | hasFinished = true; 132 | clearTimeout(timer_handler); 133 | $(this).unbind('error').unbind('load'); 134 | onFinishLoagImage(ev); 135 | } 136 | ) 137 | .bind( 138 | 'load', 139 | function(ev) { 140 | hasFinished = true; 141 | clearTimeout(timer_handler); 142 | $(this).unbind('error').unbind('load'); 143 | if (isBg) { 144 | $elm.css('background-image', ['url(', src, ')'].join('')); 145 | } 146 | else { 147 | $elm.attr('src', src); 148 | } 149 | onFinishLoagImage(ev, $img[0]); 150 | } 151 | ) 152 | .attr('src', src); 153 | timer_handler = setTimeout( 154 | function () { 155 | if (hasFinished === false) { 156 | // console.log('timeout'); 157 | $img.trigger('error'); 158 | } 159 | }, 160 | milsec_timeout 161 | ); 162 | }; 163 | }; 164 | 165 | // =================================================================== 166 | 167 | var _queue_instance_; 168 | var Queue = { 169 | getInstance: function () { 170 | if (_queue_instance_ instanceof QueueImpl === false) { 171 | _queue_instance_ = new QueueImpl(); 172 | } 173 | return _queue_instance_; 174 | } 175 | }; 176 | 177 | var QueueImpl = function () { 178 | this.index = 0; 179 | this.queue = []; 180 | this.isRunning = false; 181 | }; 182 | 183 | QueueImpl.prototype.add = function (func) { 184 | if (typeof func !== 'function') { 185 | throw new Error('you can only pass function.'); 186 | } 187 | this.queue.push(func); 188 | }; 189 | 190 | QueueImpl.prototype.run = function (firenow) { 191 | var run = $.proxy(this.run, this); 192 | firenow = firenow || false; 193 | if (this.isRunning && !firenow) { 194 | return; 195 | } 196 | this.isRunning = true; 197 | this.queue[this.index++](); 198 | if (this.index < this.queue.length) { 199 | setTimeout( 200 | function () { 201 | run(true); 202 | }, 203 | $.imageloader.queueInterval 204 | ); 205 | } 206 | else { 207 | this.isRunning = false; 208 | } 209 | }; 210 | 211 | // =================================================================== 212 | 213 | $.imageloader = { 214 | queueInterval: 17 215 | }; 216 | 217 | $.fn.imageloader = function (opts) { 218 | return this.each( 219 | function (i, elm) { 220 | init(i, elm, opts); 221 | } 222 | ); 223 | }; 224 | 225 | })(jQuery); --------------------------------------------------------------------------------