javascript load and show each image in loop
javascript load and show each image in loop
I'm new to javascript, and I've been stuck on this simple problem for days. I have a series of thousands of images (collectively a "dataset") that when viewed rapidly one after the other gives the appearance of video. I want to loop through all of my images.
I've created a function like so:
function updateImage(dataset, record_id)
image_url = '/image?dataset='+dataset+'&record-id='+record_id;
if ($('#mpeg-image').length > 0)
$('#mpeg-image')[0].src = image_url;
else
$("#image-thumbnail").html('<img id="mpeg-image", src="'+image_url+'"> </img>');
Calling the function once, e.g., updateImage('dataset_28_18-08-11',444);
results in the image content updating in the browser. However, I want to show the images in a loop, so I created a function like this:
updateImage('dataset_28_18-08-11',444);
function playVideo()
for (i = 0; i < 1800; i++)
updateImage(dataset,i);
This function uses the same updateImage()
function that worked above, but when I run playVideo();
Chrome doesn't update the image until the very end. The src
tag changes, but the actual content does not. How can I alter my loop so that each image is loaded and shown in sequence? I don't want to use something like "Sprite" that merges all images into one, since that's too data intensive on my backend. I just want to load and show my images one after the other.
updateImage()
playVideo();
src
4 Answers
4
Browsers won't re-render page elements until there's a break in the execution of your code. Your entire image loop will run before the browser ever gets a chance to re-paint the images.
It's not because the loop "runs too fast" or because the images haven't loaded (though that can definitely pose problems). To see an example of redrawing issue, try changing your animation into an infinite loop, where it continually plays. The page will freeze ("pinwheel") forever and the page elements will never be updated, even long after all of your images have loaded. You need to give the browser an opportunity to redraw the page whenever you've made changes to page elements.
In web animation, this is often done via window.requestAnimationFrame()
(see MDN docs). The method takes a callback function which is executed after the browser has redrawn the page. The browser will execute your callback once it's ready for page changes.
window.requestAnimationFrame()
A simplistic example of this with you code would go something like this.
var imageNo = 0;
function step()
updateImage(dataset, imageNo);
imageNo++;
if (imageNo < 1800)
window.requestAnimationFrame(step);
window.requestAnimationFrame(step);
You can achieve a similar effect using setTimeout()
or setInterval()
, but those methods aren't optimized for animation.
setTimeout()
setInterval()
The reason why you only see the last image is because of two things.
1. as @andriusain said, it takes the browser some indeterminate amount of time to download each image.
2. The loop runs so fast that the last iteration is what takes affect.
With that said, it would be best to either sprite the image (which it sounds like you can't do), or you can preload the images so that the browser caches them, then set the urls. You can do something like this.
// using es6
// I'd recommend adding <img src="mpeg-image" /> to your html page
// just to simplify the code. Also I recommend saving the image
// element in a local variable instead of using jQuery in each
// iteration loop which will definitely give a slower performance.
const mpegImgElement = $('#mpeg-image')[0];
const FPS = 30 / 1000; // 30 Frames Per Second
function playVideo()
preLoadImages()
.then(urls =>
playUrls(urls);
)
.catch(error =>
console.log('error loading images', error);
)
function playUrls(urls)
const index = 0;
const intervalId = setInterval(() =>
// set the url
mpegImgElement.src = urls[index];
// all done. stop the interval
if (++index === urls.length)
clearInterval(intervalId);
, FPS);
function preLoadImages()
const promises = ;
for (i = 0; i < 1800; i++)
const imageUrl = '/image?dataset='+dataset+'&record-id='+record_id;
const promise = loadImage(imageUrl);
promises.push(promise);
return Promise.all(promises);
function loadImage(url)
return new Promise((resolve, reject) =>
const image = new Image();
image.addEventListener('load', () =>
resolve(url);
);
image.addEventListener('error', error =>
reject(error);
);
image.src = url;
);
I tried your code, but kept getting errors about trying to assign new values to constants. Then after taking out the constants the code finally ran without failing, but gave me the same result as what @andriusain had: only the last image was shown.
– user554481
Aug 21 at 12:07
This happens because the browser is still loading the images... I think your best bet is to programmatically load them first and once they are loaded then play the video... to do that maybe something like this will help:
var loadImages = function(images, callback)
var loadTotal = 0;
for (var i = 0; i < images.length; i++)
var image = new Image();
image.addEventListener('load', function()
loadTotal++;
if (loadTotal === images.length)
callback();
);
image.src = images[i];
var playVideo = function()
// do stuff...
var images = ['image1.jpg', 'image2.jpg'];
loadImages(images, playVideo);
With my very beginner javascript skills, the
// do stuff ...
part is pretty critical. I tried $('#mpeg-image')[0] = images[i];
, looping through all of the preloaded images, but that didn't seem to make a difference either.– user554481
Aug 21 at 4:01
// do stuff ...
$('#mpeg-image')[0] = images[i];
Well all you'd have to do there is what you were doing at first: loop through all your image URLs and change the image's node source in the DOM... What I'm doing in my code is making sure we wait until all sources are loaded and ready for display. but please make sure you accomodate my code to work for your situation...
images
should be an array of all your image sources...– andriusain
Aug 21 at 4:09
images
This idea is terrible.
JS is executed in single thread, while calling data from src is asynchronous method. Calling one by one and wait then definitely block the thread.
In this case you need promise to prepare all images first.
I make a workable example using google logo.
paths = ["https://www.google.com.hk/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png",
"https://www.google.com.hk/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png",
"https://www.google.com.hk/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png"]
var imgCount = paths.length;
function loadImage(url)
return new Promise((resolve, reject) =>
let img = new Image();
img.addEventListener('load', e => resolve(img));
img.src = url;
);
promises =
for (var i = 0; i < paths.length; i++)
path = paths[i];
promises.push(loadImage(path));
Promise.all(promises).then(imgs =>
imgs.forEach(e => document.getElementById('image-holder').appendChild(e)););
//What I done here is display then all, but you can make it like, replace one by one, or declare your own display function.
<div id="image-holder"></div>
By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.
Wow! It works!!! And the code is so simple! You've saved me from another week of headaches. Thank you so much.
– user554481
Aug 21 at 12:10