Preloading Images In AngularJS With Promises
When I first started using AngularJS, I was so concerned with doing things the, "Angular Way," that I ended up making some poor choices. Most noticeably was the way in which I tried to preload images in my application. Thinking only of images as "DOM" (Document Object Model) elements, I delegated all of my preloading to the rendered HTML. But, the reality is, Image objects can exist without being attached to the DOM. And, when you start thinking about them as containers for asynchronous data loading (much like AJAX), preloading images in AngularJS becomes a whole lot easier.
Run this demo in my JavaScript Demos project on GitHub.
In AngularJS, your Controllers and your Services are not supposed to know anything about the DOM (Document Object Model) or about your HTML. Their only role is to model the business logic and define behaviors for your application. If you need to do something with the DOM, you're supposed to use a View or a Directive.
As such, if you need to do something with Images, your first thought is probably that they are HTML IMG tags, and, therefore, you need to isolate image loading in a View or a Directive. But this is an over-simplified understanding of images. If you break the image lifecycle into two parts - image loading and image rendering - you can start to see that part of the image lifecycle is about the data model and, therefore, can be handled by either a Controller or, more appropriately, by a Service.
In the following demo, I am using AngularJS to render an ngRepeat list of images. But, before I execute the rendering (via Directive), I'm preloading the image binaries using a Preload service. The Preload service takes an array of source values and returns a promise. The promise is resolved when all of the image binaries have been preloaded.
<!doctype html>
<html ng-app="Demo">
<head>
<meta charset="utf-8" />
<title>
Preloading Images In AngularJS With Promises
</title>
</head>
<body ng-controller="AppController">
<h1>
Preloading Images In AngularJS With Promises
</h1>
<div ng-switch="isLoading">
<!-- BEGIN: Loading View. -->
<p ng-switch-when="true">
Your images are being loaded... {{ percentLoaded }}%
</p>
<!-- END: Loading View. -->
<!-- BEGIN: Results View. -->
<div
ng-switch-when="false"
ng-switch="isSuccessful">
<p ng-switch-when="true">
<img
ng-repeat="src in imageLocations"
ng-src="{{ src }}"
style="width: 100px ; margin-right: 10px ;"
/>
</p>
<p ng-switch-when="false">
<strong>Oops</strong>: One of your images failed to load :(
</p>
</div>
<!-- END: Results View. -->
</div>
<!-- Load scripts. -->
<script type="text/javascript" src="../../vendor/jquery/jquery-2.0.3.min.js"></script>
<script type="text/javascript" src="../../vendor/angularjs/angular-1.2.min.js"></script>
<script type="text/javascript">
// Create an application module for our demo.
var app = angular.module( "Demo", [] );
// -------------------------------------------------- //
// -------------------------------------------------- //
// I control the root of the application.
app.controller(
"AppController",
function( $scope, preloader ) {
// I keep track of the state of the loading images.
$scope.isLoading = true;
$scope.isSuccessful = false;
$scope.percentLoaded = 0;
// I am the image SRC values to preload and display./
// --
// NOTE: "cache" attribute is to prevent images from caching in the
// browser (for the sake of the demo).
$scope.imageLocations = [
( "./ahhh.jpg?v=1&cache=" + ( new Date() ).getTime() ),
( "./ahhh.jpg?v=2&cache=" + ( new Date() ).getTime() ),
( "./ahhh.jpg?v=3&cache=" + ( new Date() ).getTime() ),
( "./ahhh.jpg?v=4&cache=" + ( new Date() ).getTime() ),
( "./ahhh.jpg?v=5&cache=" + ( new Date() ).getTime() ),
( "./ahhh.jpg?v=6&cache=" + ( new Date() ).getTime() ),
( "./ahhh.jpg?v=7&cache=" + ( new Date() ).getTime() ),
( "./ahhh.jpg?v=8&cache=" + ( new Date() ).getTime() ),
( "./ahhh.jpg?v=9&cache=" + ( new Date() ).getTime() ),
( "./ahhh.jpg?v=10&cache=" + ( new Date() ).getTime() ),
( "./ahhh.jpg?v=11&cache=" + ( new Date() ).getTime() ),
( "./ahhh.jpg?v=12&cache=" + ( new Date() ).getTime() ),
( "./ahhh.jpg?v=13&cache=" + ( new Date() ).getTime() ),
( "./ahhh.jpg?v=14&cache=" + ( new Date() ).getTime() ),
( "./ahhh.jpg?v=15&cache=" + ( new Date() ).getTime() ),
];
// Preload the images; then, update display when returned.
preloader.preloadImages( $scope.imageLocations ).then(
function handleResolve( imageLocations ) {
// Loading was successful.
$scope.isLoading = false;
$scope.isSuccessful = true;
console.info( "Preload Successful" );
},
function handleReject( imageLocation ) {
// Loading failed on at least one image.
$scope.isLoading = false;
$scope.isSuccessful = false;
console.error( "Image Failed", imageLocation );
console.info( "Preload Failure" );
},
function handleNotify( event ) {
$scope.percentLoaded = event.percent;
console.info( "Percent loaded:", event.percent );
}
);
}
);
// -------------------------------------------------- //
// -------------------------------------------------- //
// I provide a utility class for preloading image objects.
app.factory(
"preloader",
function( $q, $rootScope ) {
// I manage the preloading of image objects. Accepts an array of image URLs.
function Preloader( imageLocations ) {
// I am the image SRC values to preload.
this.imageLocations = imageLocations;
// As the images load, we'll need to keep track of the load/error
// counts when announing the progress on the loading.
this.imageCount = this.imageLocations.length;
this.loadCount = 0;
this.errorCount = 0;
// I am the possible states that the preloader can be in.
this.states = {
PENDING: 1,
LOADING: 2,
RESOLVED: 3,
REJECTED: 4
};
// I keep track of the current state of the preloader.
this.state = this.states.PENDING;
// When loading the images, a promise will be returned to indicate
// when the loading has completed (and / or progressed).
this.deferred = $q.defer();
this.promise = this.deferred.promise;
}
// ---
// STATIC METHODS.
// ---
// I reload the given images [Array] and return a promise. The promise
// will be resolved with the array of image locations.
Preloader.preloadImages = function( imageLocations ) {
var preloader = new Preloader( imageLocations );
return( preloader.load() );
};
// ---
// INSTANCE METHODS.
// ---
Preloader.prototype = {
// Best practice for "instnceof" operator.
constructor: Preloader,
// ---
// PUBLIC METHODS.
// ---
// I determine if the preloader has started loading images yet.
isInitiated: function isInitiated() {
return( this.state !== this.states.PENDING );
},
// I determine if the preloader has failed to load all of the images.
isRejected: function isRejected() {
return( this.state === this.states.REJECTED );
},
// I determine if the preloader has successfully loaded all of the images.
isResolved: function isResolved() {
return( this.state === this.states.RESOLVED );
},
// I initiate the preload of the images. Returns a promise.
load: function load() {
// If the images are already loading, return the existing promise.
if ( this.isInitiated() ) {
return( this.promise );
}
this.state = this.states.LOADING;
for ( var i = 0 ; i < this.imageCount ; i++ ) {
this.loadImageLocation( this.imageLocations[ i ] );
}
// Return the deferred promise for the load event.
return( this.promise );
},
// ---
// PRIVATE METHODS.
// ---
// I handle the load-failure of the given image location.
handleImageError: function handleImageError( imageLocation ) {
this.errorCount++;
// If the preload action has already failed, ignore further action.
if ( this.isRejected() ) {
return;
}
this.state = this.states.REJECTED;
this.deferred.reject( imageLocation );
},
// I handle the load-success of the given image location.
handleImageLoad: function handleImageLoad( imageLocation ) {
this.loadCount++;
// If the preload action has already failed, ignore further action.
if ( this.isRejected() ) {
return;
}
// Notify the progress of the overall deferred. This is different
// than Resolving the deferred - you can call notify many times
// before the ultimate resolution (or rejection) of the deferred.
this.deferred.notify({
percent: Math.ceil( this.loadCount / this.imageCount * 100 ),
imageLocation: imageLocation
});
// If all of the images have loaded, we can resolve the deferred
// value that we returned to the calling context.
if ( this.loadCount === this.imageCount ) {
this.state = this.states.RESOLVED;
this.deferred.resolve( this.imageLocations );
}
},
// I load the given image location and then wire the load / error
// events back into the preloader instance.
// --
// NOTE: The load/error events trigger a $digest.
loadImageLocation: function loadImageLocation( imageLocation ) {
var preloader = this;
// When it comes to creating the image object, it is critical that
// we bind the event handlers BEFORE we actually set the image
// source. Failure to do so will prevent the events from proper
// triggering in some browsers.
var image = $( new Image() )
.load(
function( event ) {
// Since the load event is asynchronous, we have to
// tell AngularJS that something changed.
$rootScope.$apply(
function() {
preloader.handleImageLoad( event.target.src );
// Clean up object reference to help with the
// garbage collection in the closure.
preloader = image = event = null;
}
);
}
)
.error(
function( event ) {
// Since the load event is asynchronous, we have to
// tell AngularJS that something changed.
$rootScope.$apply(
function() {
preloader.handleImageError( event.target.src );
// Clean up object reference to help with the
// garbage collection in the closure.
preloader = image = event = null;
}
);
}
)
.prop( "src", imageLocation )
;
}
};
// Return the factory instance.
return( Preloader );
}
);
</script>
</body>
</html>
Trying to preload images directly in your HTML is going to make your HTML harder to read, understand, and maintain. Leave the rendering of images up to your HTML; but, if you need to preload images in your AngularJS application, do that in your business logic - the same way you would request non-binary JSON (JavaScript Object Notation) data from the server.
Want to use code from this post? Check out the license.
Reader Comments
Very neat approach, concisely and logically written, well commented. Nice one - thanks!
I would consider using ng-cloak to stop the flash of "Oops: One of your images failed to load :(" during the demo - admittedly not part of what you're demonstrating here, but makes for a neater experience nonetheless.
Interesting idea, but wouldn't it make more sense to show images as they are loaded (rather than waiting for all to finish) and show images if at least one loads instead of showing none if one (or more) fails?
@Steve,
Good suggestion. When I load everything on my local machine, it all happens so fast, I sometimes forget about that stuff. Then, when I upload to GitHub, I forget about the real latency.
@Royce,
I think it depends on the context. At work, we have an even slightly different scenario - we _partially_ preload the set of images _and_ we show the ones that failed to load (they show up as blank). So, each preload idea is going to be a bit different.
I think the example could be update to return the array or source locations that preloaded successfully, even if one or more failed. Especially since we don't actually stop the preloading (behind the scenes).
very intresting ben, thankyou for knowledge........
Ben,
Thanks for sharing this approach. I am curious as to whether or not you considered using $http.get to obtain the preloading of the images. If so, can you explain why what you are doing is better ?
Thanks
Dear Ben,
I am fairly new to AngularJS and just want to initially mention that your articles are a great inspiration and a reliable source of knowledge. Please keep up the good work!
Inspired by your approach I experimented with a slightly different angle for a solution. As I do experiment a lot with photography, I know that if images would be loaded with the approach in your article, a lot of users would leave the page as the loading of, let's say 10 high resolution images in a gallery, would present a blank page for a considerable amount of time before the images would be displayed.
Without including jQuery at all, it is possible to write a very compact attribute directive that emits (upwards) an event which is triggered on the images load event (or a failure to load as well). When the parent controller (the preloader) observes the emitted event, it appends the next image for loading. The result is a preloader that loads the images in sequence (instead of concurrently). The nice effect of this is that the first image in the sequence of pictures is loaded and displayed very fast, and while the visitor observes it, the rest of the images are loaded in the background. It simply provides a strong feeling of visiting a fast page.
I don't know at all if this is an "acceptable" Angular way of doing thing...but still...I would not have been able to write something like that without learning a lot from your articles. I will keep on following your admirable work!
Best regards,
Fedde
@Federico, I think that you could get that sort of feature out of this tool. This service allows you to request any set of images and gives you a promise for them. To get what you want, you probably just need to request your images in chunks. When one chunk of images completes, you start the next chuck. Perhaps break your ten images into sets of 1, 1, 2, 2, and 4. This way your first image is loaded as fast as possible; the second is also loaded as fast as possible. The remaining images are loaded in chunks which may load the entire set faster but the user won't notice because they are still gazing at the first two images you made sure to load super fast. The exact configuration of how you break the overall list of images into sets would depend on a lot of factors and you'd probably have to play with it to get the right feel for your site.
Thanks @squid314. I more or less managed to write what you describe, but in a simpler way. For the landing page I basically preload three random images that slowly blend with eachother. So the important thing there is to manage to load the pics linearly. For my galleries, which can have around 8 to 10 pics, I simply preload the two next images to have something cached ahead. When the user steps forward in the gallery a new future image is loaded, giving the impression that the transitions between the images is extremely "quick".
I just wanted to write all the parts myself to learn how promises really work. That's the best way to learn. But thanks again for your input!
Hey, Thank you.
Could you please elaborate on the triggering of load? (Line 317)
My images are GET twice.
@Mp,
I'm not actually invoke the load() method there, I'm binding to the load-event. The callback that I pass into the .load() method will be invoked when / if the image loads successfully.
When you say your images are being GET twice, take look at the network activity and check to see if one of them says "(from cache)". I have seen some browsers where a hard-refresh of the page will show 2 requests - one from cache and one from the live server.
@Keith,
The reason that I'm doing an actual image load instead of an $http.get is that I don't believe the $http.get will actually populate the browser's native cache. By loading an actual Image() object, the binary of the image is stored in the cache; then, when I got to render the image (with the *same* source URL) on the page, the browser can pull it right from the cache instead of making a request to the server.
@Federico, @squid,
Sounds like you got it sorted out. Promises are really awesome! But, they are a lot wrap your head around. If you are really interested in them, one article that I have bookmarked that I keep going back to is on Promise anti-patterns:
http://taoofcode.net/promise-anti-patterns/
It's really helpful to see how one might fix problematic Promises so that you can actually see how nice a clean solution can be.
But, they are definitely complex!
And, @Federico, thank you so much for the kind words! I hope to keep rocking out more AngularJS goodness!
@Ben, Thank you again.
Both are 200. Then both are 304 after first load.
This occurs with Chrome 34/Safari 7. Not FF (latest). It's strange. The initiators are different for both GETs. jQuery (2.0.3) for the first. Angular-animate (1.2.0) for the second. (I'm also using UI-Router v0.2.10)
@Mp,
I don't have any experience with the the Animate module in AngularJS yet. I'm not sure why it would appear to be initiating an image request. Something sounds a bit funky there. Sorry I can't be more help.
@Ben,
Thanks again!
@Mp,
No problem - good luck!
@Federico,
I actually had to do something similar a while back (before I experimenting with preloading images using a server). What I did in that case was take my collection of img SRC values and, in my Controller, sort them based on the order I wanted them to load:
images = sortImagesByRelevance( images );
... then, in the HTML, I used an ng-repeat to render them AND I had a directive on each of them that would announce a "loaded" event like you are describing:
<img ng-repeat="image in images" ng-src="{{ image.src }}" bn-image-event />
The key point here is that since I ordered them in the Controller, they appear in the DOM in the order that I "hope" they load. At this point, I kind of just let the browser handle the loading/blocking of HTTP requests. This doesn't guarantee that the first ones will be the first to *finish* loading; but they will initiate loading in the order that I want.
This would announce an event for each loaded image that my Controller would listen for. And, for each image that it loaded, it looked at the SRC and checked it against the images that I wanted to preload; and, when the target images were all preloaded, I would do something.
It ends up spreading the logic around, which I wasn't thrilled with; but it worked out fairly well and I didn't have to manage the loading in groups of images.
@Ben,
Thanks again for the detailed reply!
I made a variant of your logic in my code, but implemented a different strategy of it that in my case guarantees that the images are loaded in the order I want.
My controller contains two lists. The first list, the "loading" list, is initially fetched from the server. This list contains a list of image URLs already sorted on some kind of order of relevance.
The second list is the list that ng-repeat loops through. Originally this list is empty and I start to populate it with the first image from my "loading" list. I only append a new element to this list once I get a proper loading event (or a loading error, in which case I skip over it). In this manner I never have multiple images loading at the same time, as what I want is to ensure that my images are loaded in the proper order without having them compete for network capacity. This is a key requirement in for example mobile terminals that communicate over potentially loaded radio networks.
Just as you did, I had to write an attribute directive to generate an event to flag the loading of an individual image. As my image controller is attached to an element that is the parent of my IMG nodes, it can listen for this event to know when to append a new image to the controller's list of images. An just as you experienced, I initially did not like my logic to be divided in this manner. After a while though, the solution matured within me and today I find it elegant for the simple reason that it results in very little code. =)
Once again, your comments are highly valued!
Best regards!
@Ben,
@Ben
Thanks for the great article! Good way to learn the power of promises and creative usage in AngularJS.
I noticed your way of using an Image object bypasses some CORS limitations you would have using $http, so that's great!
I also looked at a way to implement your solution without the Jquery dependency. So I came up with:
var image = angular.element(new Image()).bind('load', ... ).bind('error', ...). attr('src', imageLocation)
I think it does the same eventually, right?
Thanks and keep it up!
@Federico,
Very cool - sounds like you really came up with something nice and elegant.
@Adriaan,
Thank you for the kind words! And yes, I believe that what you have there does the same thing. The important point (from what I've read) is that the event-handlers have to be bound *before* the SRC value is set, which is what your chaining provides. jQuery is pretty awesome that way!
Dear Ben:
Your initial ideas for this post inspired me to completely rewrite my homepage. After exchanging some ideas in the thread above with you, I am very happy with the final results.
Now a "linear" preloading strategy loads the images of my frontpage image slider and my galleries.
http://www.farodyne.com
The best part about the web is people who share. Your blog rocks! =)
This is great Ben - nice work! Any ideas on loading other datatypes - specifically video? I've got a few video backgrounds and it'd be nice to be able to preload them along with the images.
@Ben,
I really like your different approaches on problems. But I have some doubt on this one. Now, I didn't read the whole code and I might be wrong, but aren't you forcing 'N' $digest cycles for 'N' images load?
Hi Ben,
Awesome code , I needed to remove the Jquery's dependency, so I edited the loadImageLocation as follow :
https://gist.github.com/111studio/9b7d6b9b5853c8824950
Awesome post and really high quality javascript/angular. I truly say that I've learned not only from the blog subject but also from your coding style.
Great post. I've learned a lot from it. For last two days I'm trying to figure out how to test this "preloader" factory with Jasmine. I am not sure what and how to mock real image loading here. Could it be a problem that load function is on Preloader instance?
i've found this post very helpful, but how would you implement this in a controller?
Thanks. Works well for me=)
I've found that some browsers (e.g. Chrome) will garbage collect your preloaded images if you haven't used them after a few seconds. To get around this, make sure you keep a reference to the Image objects you create. (And make sure you eventually release them too.)
@Royce / @Ben,
Ben - Very very helpful. Thank you very much!
Did you ever figure out a way to return the successfully loaded source locations regardless of failure?
@Ben & @Royce,
I was able to return successfully loaded images.
I created a gist for anyone looking to achieve this - https://gist.github.com/Lawless55/625a8159f6243dbcba76
I am in no way a master in Angular or JS for that matter. So the code might not be that good, but does what i need it to.
@Federico,
Please, where I can access your preloader source code to play with it?!
Your site rocks!
Regards
Hey guys,
Thanks for the info here. I adapted some of the code here to make an angular service for pre-loading images that I think is a bit tidier. I didn't get all fancy with promises as I tried to keep it relatively light-weight.
Here is the code for the service:
projector.factory('imagePreloader', function() {
var service = {};
/**
* To use the service, inject it into your controller then call:
*
* imagePreloader.preLoadImages with the following paremeters
*
* @param images - a flat array of image URL's
* @param strategy - "linear" or "parallel" - linear will load one image at a time in the order they appear in the array. Parallel
* will attempt to load all images at once
* @param finish_cb - function() - the success callback
* @param status_cb - function(percent_complete, imageDomelement) - a status function that fires after each image has been loaded
* @param error_cb - function(err) - error handler that will trigger for any errors
*/
service.preLoadImages = function(images, strategy, finish_cb, status_cb, error_cb) {
var image_cnt = images.length;
var loaded_cnt=0;
var error_handler = function(err) {
if (typeof(error_cb) == 'function') {
error_cb( err );
}
}
if (strategy == 'linear') {
var loadImage;
var loadImage = function() {
var image_loaded_cb = function(ev) {
loaded_cnt++;
if (typeof(status_cb) == 'function') {
status_cb( ~~(100 * loaded_cnt / image_cnt), ev.path[0] );
}
if (images.length > 0) {
next();
}
if (loaded_cnt == image_cnt) {
finish_cb();
}
}
var next = function() {
var imgUrl = images.shift();
var image = angular.element(new Image()).bind('load', image_loaded_cb )
.bind('error', error_handler)
.attr('src', imgUrl)
}
next();
}();
} else if (strategy =='parallel') {
var image_loaded_cb = function(ev) {
loaded_cnt++;
if (typeof(status_cb) == 'function') {
status_cb( ~~(100 * loaded_cnt / image_cnt), ev.path[0] );
}
if (loaded_cnt == image_cnt) {
finish_cb();
}
}
angular.forEach(images, function(imgUrl) {
var image = angular.element(new Image()).bind('load', image_loaded_cb )
.bind('error', error_handler)
.attr('src', imgUrl)
});
} else {
throw new Error('Invalid strategy. Should either be "parallel" or "linear"')
}
}
return service;
});
And here is how you can use it in a controller:
app.controller('HomeController', function($scope, $http, $timeout, $location, userService, imagePreloader) {
//testing image preloader
var images = [
'https://www.google.com/images/srpr/logo11w.png',
'https://s.yimg.com/pw/images/sohp_2014/trees_noblur.jpg',
'http://i2.cdn.turner.com/cnnnext/dam/assets/130419165742-dzhokar-tsarnaev-vk-exlarge-169.jpg'];
var status_cb = function(status_pct, img) { console.log('Done percent:', status_pct, img )};
var finish_cb = function() { console.log("All done!"); }
imagePreloader.preLoadImages(images, 'parallel', finish_cb, status_cb);
});
Hey,
This is a great article. When I tested this though I get an error:
ReferenceError: $ is not defined
On this line:
var image = $(new Image())
Do you have any idea why?
@Jaymie,
I think this is because the directive is requiring jQuery. I believe this will fix it for you -
var image = angular.element(new Image());
Awesome post.
@Jai,
It might help you!!
http://www.aspdotnetblogspot.blogspot.in/2014/09/angularjs-documentation.html
@Ross,
Yeah you were right. I managed to sort this out about 5 minutes after posting the comment :)
Hi Ben,
Thanks for the great post. This was a life saver from having to use silly $timeouts to wait for images to load before I started up a slider. Only one slight problem (which may never happen in production, but I know someone will break it it):
Error: [$rootScope:inprog] errors.angularjs.org/1.3.14/$rootScope/inprog?p0=%24digest
at Error (native)
at /js/lib/angular.min.js:6:417
at k (/js/lib/angular.min.js:117:465)
at l.$get.l.$apply (/js/lib/angular.min.js:125:514)
at HTMLImageElement.(anonymous) (/js/app.js:359:18)
at HTMLImageElement.n.event.dispatch (code.jquery.com/jquery-2.1.3.min.js:3:6444)
at HTMLImageElement.n.event.add.r.handle (code.jquery.com/jquery-2.1.3.min.js:3:3219)
at Comment.(anonymous) (code.jquery.com/jquery-2.1.3.min.js:3:15402)
at n.fn.extend.domManip (code.jquery.com/jquery-2.1.3.min.js:3:17011)
at n.fn.extend.after (code.jquery.com/jquery-2.1.3.min.js:3:15338)
/js/app.js:359:18 is from the preloader code and it's this line:
$rootScope.$apply(
I'm a relative newbie to AngularJS. I get that only one $apply or $digest can be running at any given time, but I have no clue how to defer another one from happening until the first one is done.
Or maybe I'm thinking about this all wrong. A little help would be greatly appreciated! Thanks!
Very good!! Exactly what I needed. Thanks and congrats!!
Thank you so much for this! Saved me days of trying to figure this out.
This may seem trivial to advanced developers, but I wanted to dynamically create the array of images by data received from an object. So I created a plunker based on your example here and in the controller I am creating the array of images by adding them individually and also by looping over an array of objects to capture the image values that will be added to the array of images. Thank you again for this!
http://plnkr.co/edit/6eVIMlTHKZ7PwlRlXcsq?p=preview
Cheers Ben, I snagged the preload factory utility ;).
Nice Article Ben Nadel,
I have one question.
What is the real reason to separate the code into 3 parts ( constructor, static method and instance methods)?
Why not just code into one function that contains variables, methods?
Great work, I would think it would be a great feature to pass in a backup / placeholder image for the scenario of a failed image link.
Thank you for the great article about preloading. I was able to modify this code slightly and use this as a Audio Preloader for our game. The only task which I had to do was to find out the event when audio got loaded.
obj.addEventListener("canplaythrough", function( event ) {}) came to my rescue.
Thanks once again.
very good.
https://gist.github.com/bennadel/9805954#file-preload-images-htm
TypeError: Preloader.preloadImages is not a function
How To Solve This Error ?
Please , Help me