Skip to content

Commit

Permalink
1.2.0: fixes & default method back to onload
Browse files Browse the repository at this point in the history
It turns out polling for `naturalWidth` becomes unreliable after
changing the `src` attribute on an image. Not every browser resets
`naturalWidth` to 0 after changing `src`, doing this even seems to be
against spec.

Polling for `naturalWidth` is only safe if you can rely on the image
never changing its `src` attribute. So we're changing back to `onload`
as default.

Also fixed an issue with `onload` in IE11 that caused callbacks to fire
before the image adjusted layout.
  • Loading branch information
staaky committed Jun 5, 2015
1 parent 45c0fd2 commit d2be573
Show file tree
Hide file tree
Showing 9 changed files with 153 additions and 122 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ Voilà is a [jQuery](http://jquery.com) plugin that provides callbacks for image

[voila.nickstakenburg.com](http://voila.nickstakenburg.com)

Voilà has an API inspired by [imagesLoaded](https://github.com/desandro/imagesloaded), extended with useful methods like `abort()` and support for `naturalWidth` and `naturalHeight` in all browsers, which makes it compatible with *IE6 & IE7*. Multiple loading methods are supported, by default callbacks are triggered as soon as naturalWidth is available, making Voilà faster than alternatives that wait for `onload` to fire.
Voilà has an API inspired by [imagesLoaded](https://github.com/desandro/imagesloaded), extended with useful methods like `abort()` and support for `naturalWidth` and `naturalHeight` in all browsers, which makes it compatible with *IE6 & IE7*. Multiple loading methods are supported. Allowing callbacks to be triggered as soon as `naturalWidth` is available, making Voilà faster than alternatives that wait for `onload` to fire.

## Install

Expand Down Expand Up @@ -70,18 +70,18 @@ $('#container').voila()

Options can be set as the first parameter.

+ `method` - _String_ - The loading method, the default is `'naturalWidth'` which calls callbacks as soon as `naturalWidth` and `naturalHeight` are available. Images will have dimensions at that point, but could still be in the process of rendering. The alternative is `'onload'` which calls callbacks as soon as `onload` fires on a detached Image object, this is slower, but can give images more time to render.
+ `method` - _String_ - The loading method, the default is `'onload'` which calls callbacks as soon as `onload` fires. The alternative is `'naturalWidth'`, which calls callbacks as soon as `naturalWidth` is available, images will have dimensions at that point but could still be in the process of rendering. Don't use `'naturalWidth'` when changing the `src` attribute of an image, it becomes unreliable at that point, use `'onload'` instead.

```js
// callback as soon as naturalWidth & naturalHeight are available (default)
// callback as soon as naturalWidth & naturalHeight are available
$('#container').voila({ method: 'naturalWidth' }, function(instance) {
$.each(instance.images, function(i, image) {
var img = image.img;
console.log(img.src + ' = ' + img.naturalWidth + 'x' + img.naturalHeight);
});
});

// give images more time to render by waiting for onload
// give images more time to render by waiting for onload (default)
$('#container').voila({ method: 'onload' }, function(instance) {
console.log('All images have finished loading');
});
Expand Down
2 changes: 1 addition & 1 deletion bower.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "voila",
"version": "1.1.0",
"version": "1.2.0",
"description": "A jQuery plugin that provides callbacks for images, letting you know when they've loaded.",
"keywords": [
"image",
Expand Down
4 changes: 2 additions & 2 deletions demo/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<title>Voil&agrave;</title>
<meta name="viewport" content="width=device-width, initial-scale=1"/>

<script src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
<script src="http://code.jquery.com/jquery-1.11.3.min.js"></script>
<script src="../voila.pkgd.js"></script>

<script src="js/demo.js"></script>
Expand Down Expand Up @@ -36,7 +36,7 @@ <h2>Demo</h2>
<button id="reset">Reset</button>
</div>
<div class='checkboxes'>
<input type='checkbox' id='onload'/> <label for='onload'>onload</label>
<input type='checkbox' id='naturalWidth'/> <label for='naturalWidth'>naturalWidth</label>
<input type='checkbox' id='dimensions'/> <label for='dimensions'>dimensions</label>
</div>
</div>
Expand Down
9 changes: 4 additions & 5 deletions demo/js/demo.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ var Demo = {

this.reset();

this.onloadChange();
this.naturalWidthChange();

this.startObserving();
},
Expand All @@ -50,7 +50,7 @@ var Demo = {
startObserving: function() {
$('#add').on('click', $.proxy(this.add, this));
$('#reset').on('click', $.proxy(this.reset, this));
$('#onload').on('change', $.proxy(this.onloadChange, this));
$('#naturalWidth').on('change', $.proxy(this.naturalWidthChange, this));
},

add: function() {
Expand Down Expand Up @@ -89,7 +89,6 @@ var Demo = {
);
});
}

},

reset: function() {
Expand All @@ -107,9 +106,9 @@ var Demo = {
}
},

onloadChange: function() {
naturalWidthChange: function() {
this.setOptions({
method: $('#onload').prop('checked') ? 'onload' : 'naturalWidth'
method: $('#naturalWidth').prop('checked') ? 'naturalWidth' : 'onload'
});
}
};
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "voila",
"title": "Voil&agrave;",
"version": "1.1.0",
"version": "1.2.0",
"description": "A jQuery plugin that provides callbacks for images, letting you know when they've loaded.",
"keywords": [
"image",
Expand Down
118 changes: 67 additions & 51 deletions src/imageready.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,36 +21,16 @@ $.extend(ImageReady.prototype, {
this.isLoaded = false;

this.options = $.extend({
method: 'naturalWidth',
method: 'onload',
pollFallbackAfter: 1000
}, arguments[3] || {});

// a fallback is used when we're not polling for naturalWidth/Height
// IE6-7 also use this to add support for naturalWidth/Height
if (!this.supports.naturalWidth || this.options.method == 'onload') {
setTimeout($.proxy(this.fallback, this));
// onload and a fallback for no naturalWidth support (IE6-7)
if (this.options.method == 'onload' || !this.supports.naturalWidth) {
setTimeout($.proxy(this.load, this));
return;
}

// can exit out right away if we have a naturalWidth
if (this.img.complete && $.type(this.img.naturalWidth) != 'undefined') {
setTimeout($.proxy(function() {
if (this.img.naturalWidth > 0) {
this.success();
} else {
this.error();
}
}, this));
return;
}

// we instantly bind to onerror so we catch right away
$(this.img).bind('error', $.proxy(function() {
setTimeout($.proxy(function() {
this.error();
}, this));
}, this));

this.intervals = [
[1 * 1000, 10],
[2 * 1000, 50],
Expand All @@ -69,6 +49,11 @@ $.extend(ImageReady.prototype, {
this.poll();
},

// NOTE: Polling for naturalWidth is only reliable if the
// <img>.src never changes. naturalWidth isn't always reset
// to 0 after the src changes (depending on how the spec
// was implemented). The spec even seems to be against
// this, making polling unreliable in those cases.
poll: function() {
this._polling = setTimeout($.proxy(function() {
if (this.img.naturalWidth > 0) {
Expand All @@ -79,18 +64,18 @@ $.extend(ImageReady.prototype, {
// update time spend
this._time += this._delay;

// use a fallback after waiting
// use load() after waiting as a fallback
if (this.options.pollFallbackAfter &&
this._time >= this.options.pollFallbackAfter &&
!this._usedPollFallback) {
this._usedPollFallback = true;
this.fallback();
this.load();
}

// next i within the interval
if (this._time > this.intervals[this._ipos][0]) {
// if there's no next interval, we asume
// the image image errored out
// if there's no next interval, we assume
// the image errored out
if (!this.intervals[this._ipos + 1]) {
this.error();
return;
Expand All @@ -106,50 +91,81 @@ $.extend(ImageReady.prototype, {
}, this), this._delay);
},

fallback: function() {
var img = new Image();
this._fallbackImg = img;
load: function() {
var image = new Image();
this._onloadImage = image;

img.onload = $.proxy(function() {
img.onload = function() {};
image.onload = $.proxy(function() {
image.onload = function() {};

if (!this.supports.naturalWidth) {
this.img.naturalWidth = img.width;
this.img.naturalHeight = img.height;
this.img.naturalWidth = image.width;
this.img.naturalHeight = image.height;
}

this.success();
}, this);

img.onerror = $.proxy(this.error, this);

img.src = this.img.src;
},

abort: function() {
if (this._fallbackImg) {
this._fallbackImg.onload = function() { };
}
image.onerror = $.proxy(this.error, this);

if (this._polling) {
clearTimeout(this._polling);
this._polling = null;
}
image.src = this.img.src;
},

success: function() {
if (this._calledSuccess) return;

this._calledSuccess = true;

this.isLoaded = true;
this.successCallback(this);
// stop loading/polling
this.abort();

// some time to allow layout updates, IE requires this!
this._successRenderTimeout = setTimeout($.proxy(function() {
this.isLoaded = true;
this.successCallback(this);
}, this));
},

error: function() {
if (this._calledError) return;

this._calledError = true;

// stop loading/polling
this.abort();
if (this.errorCallback) this.errorCallback(this);

this._errorRenderTimeout = setTimeout($.proxy(function() {
if (this.errorCallback) this.errorCallback(this);
}, this));
},

abort: function() {
this.stopLoading();
this.stopPolling();
this.stopWaitingForRender();
},

stopPolling: function() {
if (!this._polling) return;
clearTimeout(this._polling);
this._polling = null;
},

stopLoading: function() {
if (!this._onloadImage) return;
this._onloadImage.onload = function() { };
this._onloadImage.onerror = function() { };
},

stopWaitingForRender: function() {
if (this._successRenderTimeout) {
clearTimeout(this._successRenderTimeout);
this._successRenderTimeout = null;
}

if (this._errorRenderTimeout) {
clearTimeout(this._errorRenderTimeout);
this._errorRenderTimeout = null;
}
}
});
2 changes: 1 addition & 1 deletion src/voila.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ function Voila(elements, opts, cb) {
$.type(arguments[2]) === 'function' ? arguments[2] : false;

this.options = $.extend({
method: 'naturalWidth'
method: 'onload'
}, options);

this.deferred = new jQuery.Deferred();
Expand Down
Loading

0 comments on commit d2be573

Please sign in to comment.