A Graceful Ember.js Image Loading Component
Fall is definitely on its way out here in the Pacific North West. And that means lots of wood has to be chopped for Winter, so you’ll understand and bear with me if this week’s article is a little short gem that I’ve used on several occasions now.
There are times when you might be loading a large image, that for whatever reason hasn’t been resized for displaying properly – topographical maps, sonar scans, aerial imagery or what have you, and you want a better experience for your users than having to watch the image slowly load. I’m going to show you how with a little CSS and DOM magic, you can have a slow loading image fade in when it is fully loaded.
There’s an absolutely fantastic article by The Barrel gang called Taking Control of Image Loading – I highly recommend you read it as it’s first technique is the basis for what I am about to show you. All credit for the CSS technique goes to them.
Essentially, we wait for the browser to fire off a ‘load’ DOM event on the image when it is fully loaded. When this event occurs, we use it to attach a loaded
CSS class to the Ember Component containing div
. Then with some simple CSS, we can toggle from the image in question having an opacity of 0 to an opacity of 1, aided with some nice CSS 3 transitions to perform the fading. Perhaps a demo, Sir/Madame? Voila!
Now, hopefully above you saw a nice little spinning pie, before a lovely image of the Ember Zone mobile Headquarters appeared. If you hover on the JSBin above, and click the ‘Run with JS’ button a few times, you should be able to repeat the effect.
So, what would such an Ember Component look like? Here you go:
App.ImageLoaderComponent = Ember.Component.extend({
src: "",
classNames: ["img_wrapper"],
classNameBindings: ["loaded"],
loaded: false,
handleLoad: function () {
var view = this;
// Use jQuery's `one` to ensure the handler is remove afterwards
this.$()
.children("img")
.one(
"load",
function () {
// Wrap in an Ember.run to stop Ember from guessing what to do:
// http://emberjs.com/guides/understanding-ember/run-loop/#toc_what-happens-if-i-forget-to-start-a-run-loop-in-an-async-handler
Ember.run(function () {
view.set("loaded", true);
});
}.bind(this)
);
}.on("didInsertElement"),
});
We’ve been able to accomplish this effect in 12 lines of code. This is the Ember Zone. I was able to read the Barrel article, and directly translate it into an Ember Component without having to look anything up in the Ember guides – the concept translated directly into code that I thought should work, and it did.
The most interesting part of the above code snippet is where we use a function provided as a prototype extension of Function.prototype
by Ember.js called on
to signify that the handleLoad
function should be run when the didInsertElement
event occurs. This is a useful technique to stay clear of writing a giant monolithic didInsertElement
function and instead have cohesive functions that trigger on the correct event.
One thing that could be a bit confusing is the unfortunate naming collision between jQuery’s on
method – which is used to attach event handlers to event types, and Ember’s own on
Function prototype extension which although it has a similar function is not the same, and can only listen for Ember events as far as I can see. Hopefully someone will correct me!
Then we use bind
to Function.prototype
that allows you to keep the context of this
in the method used as callback allowing us to avoid having to do any var _this = this'
hoopla.
Summary
You now have a neat little Component that will show images in a nice way, enhancing the default experience provided by the browser to something a little bit better. And hopefully you learned something about Ember’s on
extension, and the bind
extension present in Javascript.
Update
Thanks to Pat O’Callaghan for pointing out in the comments that I was still using the old jQuery bind
method for attaching event handlers, so I’ve updated the article. Thanks, Pat!
Update 2
More updates! Thanks to @mixonic for reaching out to point out there’s some reliability issues with the image load
event in browsers – stay tuned for a complete overhaul of this component later!