I am trying to pre-load many (thousands) of images, and thought I was doing it right. I have all the URLs (some are valid, some are not) in an array. I loop through the array, and attache onload and onerror events to the img.src function. When the image event returns an error I do not add it to the "good" array, and I continue in my loop.
However, I have noticed that, while this should prevent images from making it into my "good" array, it does not always (Actually I have enough images that I can't tell if it ever does). When I actually go to load those images into the page, I get the broken image symbol and a 404 in my console.
I see 404 errors in my console while pre-loading, so I am assuming it does detect some broken images but not all, or maybe they still make it into my other array? Could it be that the images are being loaded so fast that the continue statement I have does not work (There are thousands)? If so, is there a way around this? I have attached my code below, here I tried using continue in the .onerror condition but I guess the img.src made it an invalid loop condition. Thank you for any help.
EDIT:
The src attribute is one property of an object, it also will have name and userName properties, so I only want to add the objects with valid urls. I tried to abbreviate my code but should have added this part (I only added the first three lines, even though I now realize I should push the item onload
var name = 'test',
username = 'testUser'
url;
for(i = 0; i < imgURLs.length; i++) {
url = url[i];
var img = new Image();
valid = true;
img.onload = function(){
console.log('New Media Loaded')
};
img.onerror = function(){
console.log('error: bad image source');
valid = false;
};
img.src = url;
if(valid) {
goodArray.push(img);
}
}
onerror is asynchronous. It happens sometime in the future. You are testing the valid variable long before onerror or onload has even run. If you only want to push good images into the array, then you should just push them into the array in the onload handler like this:
for(i = 0; i < imgURLs.length; i++) {
var img = new Image();
img.onload = function(){
console.log('New Media Loaded')
goodArray.push(this);
};
img.onerror = function(){
console.log('error: bad image source');
};
img.src = url;
}
FYI. Note, I'm also pushing this, not img because the variable img has been changed by the subsequent iterations of your for loop, but this will be the image that just successfully loaded.
If you want an image preloader that will notify when the last image has loaded, you can use the code form this answer.
Javascript is asynchronous. Your condition ( if(valid) ) is tested before loading.
If you want to push the good images in an array, push it in the "onload" event.
for(i = 0; i < imgURLs.length; i++) {
var img = new Image();
img.onload = function(){
goodArray.push(this);
console.log('New Media Loaded')
};
img.onerror = function(){
console.log('error: bad image source');
};
img.src = url;
}
After, if you want to add an action after loading all images, add a counter like that
var imagesCount = imgURLs.length,
counter = 0;
for(i = 0; i < imagesCount; i++) {
var img = new Image();
img.onload = function(){
goodArray.push(this);
console.log('New Media Loaded')
counter++;
if(counter == imagesCount) yourAction();
};
img.onerror = function(){
console.log('error: bad image source');
counter++;
if(counter == imagesCount) yourAction();
};
img.src = url;
}
function yourAction(){
// your final action here ...
}
Related
The code below is intended to display a set of images numbered x.jpg (where x is a number starting at 0 and going up to n) in normal order, then in reverse order. The final product is meant to do it only in reverse order but the normal order part was put in for testing purposes. It currently displays the images in normal order correctly, but does not display them in reverse order. I understand that this sort of functionality was not really intended to be done in normal javascript alone but it would be helpful to know why this does not work.
<script>
var counter = 0;
var imageArray = [];
function getImg(){
var imgURL = "images/" + counter + ".jpg";
var img = new Image();
img.src=imgURL;
imageArray.push(img)
document.getElementById('imageList').appendChild(img);
counter++;
img.onload = function(){
//alert(this.width);
if(this.width!=0){
getImg();
}
else{
var popped = imageArray.pop()
}
}
}
function reverseImg() {
imageArray.reverse();
for (i=1; i<imageArray.length; i++) {
image = imageArray[i]
document.getElementById('reversedList').appendChild(image);
}
}
getImg();
reverseImg();
</script>
It doesn't work because when you call reverseImg, imageArray has only one image in it, and you're skipping that in your for loop by starting with 1. The code in the onload callbacks in getImg doesn't run until later, when the first image loads.
If you want the images displayed in reverse order with a time delay in-between, you need to do something like getImg with its onload behavior (but I'd handle error as well), but starting by reversing the array first. Or actually, there's no need for the array at all, just start with n (the last image) and count backward, creating the Image elements within the callbacks.
For example:
function reverseImg() {
var n = 9;
tick();
function tick() {
var img = document.createElement("img");
if (n > 0) {
// When this image loads/fails, load the next
img.addEventListener("load", tick);
img.addEventListener("error", tick);
}
// Show this image
img.src = "https://via.placeholder.com/100/000080/FFFFFF?text=Image%20" + n;
document.body.appendChild(img);
--n;
}
}
reverseImg();
img {
padding: 2px;
}
I have a folder with the contents like the following (giving a minimal example)
ex1_1.png
ex1_2.png
ex2_1.png
page.html
and I've written the following JavaScript code. The HTML file has two canvas elements that are designed to draw ex1_1.png and ex2_1.png and each canvas element has an associated "Next" button. If the first one is clicked it erases the first canvas element and draws ex_2.png. What I want is for the Next button to cycle through all my images, going back to the start when the last image is exceeded. The following JavaScript accomplishes this, except for the part where it cycles back. When it reaches the image with source ex1_3.png (which doesn't exist in the folder), I get a crash, but on the draw() command--which tells me that for whatever reason, it's not cycling the source back to ex1_1.png before attempting to draw.
To the best of my ability to debug this, something is going wrong with the img.onerror part, or how its implemented with the global variable window.indicator. When I cycle through using the next button, the indicator shows true then false if I print the value from within the img.onerror function. But if I print from within the next() function, it never shows false. This sounds like some kind of an issue with the window.indicator keeping its value globally.
// Variable to indicate whether the most recently generated image
// was valid.
window.indicator = true;
// Give the file base-name and index as stored in the local address.
// Return the corresponding Image() object.
function initImg(name, ind) {
var img = new Image();
// The file is local, the image is always of the form
// baseName_i.png
window.indicator = true;
img.src = name + "_" + ind + ".png";
img.onerror = function() {
// Find the appropriate canvas state element, and
// update its state back to 1.
for (i = 0; i < canStates.length; i++) {
var n = canStates[i][0].getAttribute("id");
n = n.split("_")[1];
if (name == canStates[i][0].getAttribute("id")) {
canStates[i][1] = 1;
}
window.indicator = false;
}
};
return img;
}
// Give the canvas context and image objects. Draw the image to
// the context, no return value.
function draw(ctx, img) {
// Check that the image is loaded before writing. Keep
// checking every 50 milliseconds.
if (!img.complete) {
setTimeout( function() {
draw(ctx, img);
}, 50);
}
// Clear the current image and draw the new.
ctx.clearRect(0,0, 200,200);
ctx.drawImage(img, 0,0, 200,200);
}
// Give the string canvas id and string base-name, create the
// canvas object and draw the first image to the canvas.
function slideShow(canId, name) {
var can = document.getElementById(canId);
can.width = 300;
can.height = 300;
var ctx = can.getContext('2d');
var img = initImg(name, 1);
draw(ctx, img);
}
// Next button function. Give the name of the canvas, draw the
// next image or cycle to the start.
function next(button) {
var name = button.getAttribute("name");
// Find the appropriate canvas element.
for (i = 0; i < canStates.length; i++) {
var id = canStates[i][0].getAttribute("id");
id = id.split("_")[1];
if (id == name) {
// Use the states to produce an image, and update the
// states.
canStates[i][1] += 1;
var img = initImg(name, canStates[i][1]);
if (!window.indicator) {
img = initImg(name, 1);
}
// Draw to the canvas.
draw(canStates[i][0].getContext('2d'),img);
}
}
}
// Create a global variable tracking all states of "Next" buttons.
// Stored as a list, each element is a list, the left coordinate is
// a canvas and the right coordinate is its state (image index).
// Also initialize all slide shows.
// The variable r stores the canvases and states, initialized
// outside the function in order to pass-by-reference so as to act
// as a global variable.
var canStates = new Array();
window.onload = function() {
var cans = document.getElementsByTagName("canvas");
for (i=0; i < cans.length; i++) {
var c = cans[i];
var n = c.getAttribute("id").split("_")[1];
slideShow("can_"+n, n);
}
for (i = 0; i < cans.length; i++) {
canStates[i] = [cans[i],1];
}
}
I could switch strategies completely here. I've heard that PHP is a decent way to server-side look at the files in a directory, and I could use that, but I don't know how to make a PHP script execute when a browser is loaded, or how to take its results and hand them over to the JavaScript file.
I'm in the process of creating a site that preloads several large gifs. Due to the size of the images. I need them all to be loaded before displayed to the user. In the past I have done this numerous times using something basic like this:
var image = new Image();
image.onload = function () { document.appendChild(image); }
image.src = '/myimage.jpg';
However, i'm loading a group of images from an array, which contains the image source url. It should show a loading message and once they have all loaded it show perform a callback and hide the loading message etc.
The code I've been using is below:
var images = ['image1.gif', 'image2.gif', 'image3.gif'];
function preload_images (target, callback) {
// get feedback container
var feedback = document.getElementById('feedback');
// show feedback (loading message)
feedback.style.display = 'block';
// set target
var target = document.getElementById(target);
// clear html of target incase they refresh (tmp fix)
target.innerHTML = '';
// internal counter var
var counter = 0;
// image containers attach to window
var img = new Array();
// loop images
if (images.length > 0) {
for (var i in images) {
// new image object
img[i] = new Image();
// when ready peform certain actions.
img[i].onload = (function (value) {
// append to container
target.appendChild(img[value]);
// hide all images apart from the first image
if (value > 0) {
hide(img[value]);
}
// increment counter
++counter;
// on counter at correct value use callback!
if (counter == images.length) {
// hide feedback (loading message)
feedback.style.display = 'none';
if (callback) {
callback(); // when ready do callback!
}
}
})(i);
// give image alt name
img[i].alt = 'My Image ' + i;
// give image id
img[i].id = 'my_image_' + i
// preload src
img[i].src = images[i];
}//end loop
}//endif length
}//end preload image
It's really weird, I'm sure it should just work, but it doesn't even show my loading message. It just goes straight to the callback.. I'm sure it must be something simple, I've been busy and looking at it for ages and finding it a tad hard to narrow down.
I've been looking over stackoverflow and people have had similar problems and I've tried the solutions without much luck.
Any help would be greatly appreciated! I'll post more code if needed.
Cheers!
If I'm not totally wrong the problem is with you assignment to
// when ready peform certain actions.
img[i].onload = (function (value) {...})(i);
here you instantly call and execute the function and return undefined to the onload attribute, what can not be called when the image is loaded.
What you can do to have access to the value 'i' when the image is loaded you can try something like the following:
onload = (function(val){
var temp = val;
return function(){
i = temp;
//your code here
}
})(i);
this should store the value in temp and will return a callable function which should be able to access this value.
I did not test that if it is working and there maybe a better solution, but this one came to my mind :)
Try this for your onload callback:
img[i].onload = function(event) {
target.appendChild(this);
if (img.indexOf(this) > 0) {
hide(this);
}
// ...
};
Hope you can get it working! It's bed time for me though.
Edit: You'll probably have to do something about img.indexOf(this)... just realized you are using associative array for img. In your original code, I don't think comparing value to 0 is logical in that case, since value is a string. Perhaps you shouldn't use an associative array?
Im trying to load in jpeg images, frame by frame to create an sequence animation of jpeg images. I'm attempting to load them in a recursive loop using javascript. I need to load images in linearly to achieve progressive playback of the animation. (start playback before all frames are loaded) I get a Stack overflow at line: 0 error from IE due to the natural recursion of the function. (My real code loads in over 60+ frames)
Here is a basic example of how I'm doing this:
var paths = ['image1.jpg', 'image2.jpg', 'image3.jpg']; //real code has 60+ frames
var images = [];
var load_index = 0;
var load = function(){
var img = new Image();
img.onload = function(){
if(load_index<=paths.length){
load_index++;
load();
}else{
alert('done loading');
}
}
img.src = paths[load_index];
images.push(img);
}
It seems I can avoid this error by using a setTimeout with an interval of 1 when calling the next step of the load. This seems to let IE "breathe" before loading the next image, but decreases the speed at which the images load dramatically.
Any one know how to avoid this stack overflow error?
http://cappuccino.org/discuss/2010/03/01/internet-explorer-global-variables-and-stack-overflows/
The above link suggests that wrapping the function to remove it from the window object will help avoid stack overflow errors. But I then see strangeness with it only getting about 15 frames through the sequence and just dies.
Put simply, don't use a recursive function for this situation, there isn't any need:
var paths = ['image1.jpg', 'image2.jpg', 'image3.jpg'];
var images = [];
var loads = [];
/// all complete function, probably should be renamed to something with a
/// unique namespace unless you are working within your own function scope.
var done = function(){
alert('all loaded');
}
var loaded = function(e,t){
/// fallbacks for old IE
e = e||Event; t = e.target||e.srcElement;
/// keep a list of the loaded images, you can delete this later if wanted
loads.push( t.src );
if ( loads.length >= paths.length ) {
done();
}
}
var load = function(){
var i, l = paths.length, img;
for( i=0; i<l; i++ ){
images.push(img = new Image());
img.onload = loaded;
img.src = paths[i];
}
}
In fact, as you are finding, the method you are using currently is quite intensive. Instead, the above version doesn't create a new function for each onload listener (saves memory) and will trigger off as many concurrent loads as your browser will allow (rather than waiting for each image load).
(the above has been manually typed and not tested, as of yet)
update
Ah, then it makes more sense as to why you are doing things this way :) In that case then your first approach using the setTimeout would probably be the best solution (you should be able to use a timeout of 0). There is still room for rearranging things to see if you can avoid that though. The following may get around the problem...
var paths = ['image1.jpg', 'image2.jpg', 'image3.jpg'];
var images = []; /// will contain the image objects
var loads = []; /// will contain loaded paths
var buffer = []; /// temporary buffer
var done = function(){ alert('all loaded'); }
var loaded = function(e,t){
e = e||Event; t = e.target||e.srcElement; loads.push( t.src );
/// you can do your "timing/start animation" calculation here...
/// check to see if we are complete
if ( loads.length >= paths.length ) { done(); }
/// if not fire off the next image load
else { next(); }
}
var next = function(){
/// current will be the next image
var current = buffer.shift();
/// set the load going for the current image
if ( current ) { current.img.src = current.path; }
}
var load = function(){
var i, l = paths.length, img;
for( i=0; i<l; i++ ){
img = new Image();
img.onload = loaded;
/// build up a list of images and paths to load
buffer.push({ img: img, path: paths[i] });
}
/// set everything going
next();
}
If the above doesn't do it, another way of getting around the issue would be to step through your list of paths, one at a time, and append a string of image markup (that would render off-screen) to the DOM with it's own onload="next()" handler... next() would be responsible for inserting the next image. By doing this it would hand off the triggering of the load and the subsequent load event to outside of your code, and should get around stacking calls.
I am building a slideshow with a few hundred images and would like to build a nice loading bar, so the idea was to preload the images using JavaScript, then initialize the rest of the UI afterwords.
Preloading the images is not a problem, but getting the browser to update the status as things load is. I've tried a few things, but the browser will only repaint the display after it finishes.
I've even tried the script from this question, but I get the same results.
Here's what I've got so far (imgList is an array of filenames. I'm using Prototype.)
var imageBuf = []
var loadCount = 0
$('loadStatus').update("0/"+imgList.length)
function init() {
imgList.each(function(element){
imageBuf[element] = new Image()
//imageBuf[element].onload = window.setTimeout("count()",0) // gives "not implemented" error in IE
imageBuf[element].onload = function(){count()}
imageBuf[element].src = "thumbs/"+element
})
}
function count() {
loadCount++
$('loadStatus').update(loadCount+"/"+imgList.length)
}
init()
Try using the function from my answer to this question:
function incrementallyProcess(workerCallback, data, chunkSize, timeout, completionCallback) {
var itemIndex = 0;
(function() {
var remainingDataLength = (data.length - itemIndex);
var currentChunkSize = (remainingDataLength >= chunkSize) ? chunkSize : remainingDataLength;
if(itemIndex < data.length) {
while(currentChunkSize--) {
workerCallback(data[itemIndex++]);
}
setTimeout(arguments.callee, timeout);
} else if(completionCallback) {
completionCallback();
}
})();
}
// here we are using the above function to take
// a short break every time we load an image
function init() {
incrementallyProcess(function(element) {
imageBuf[element] = new Image();
imageBuf[element].onload = function(){count()};
imageBuf[element].src = "thumbs/"+element;
}, imgList, 1, 250, function() {
alert("done loading");
});
}
You may want to modify the chunk size parameter as well as the length of the timeout to get it to behave just like you want it to. I am not 100% sure this will work for you, but it is worth a try...