Appending and Preloading Images with JavaScript - javascript

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?

Related

JavaScript works when setTimeout() is used, but it isn't working when document.eventListener('DOMContentLoaded', x) is used on a WordPress page. Why?

I have a few lines of JavaScript code that pick up heading texts from separate sections and place them into their respective input fields. They are also executed on single pages using wp_enqueue_script.
It works absolutely fine when setTimeout() is used:
function passengerElevator() {
var getProductName = document.querySelectorAll('[data-id="6657316"]');
getProductName.forEach(function(item) {
var productName = item.querySelector('.lift');
var inputArea = item.querySelector('input[name=product]');
inputArea.value = productName.innerText;
});
var getProductName = document.querySelectorAll('[data-id="e9c06d5"]');
getProductName.forEach(function(item) {
var productName = item.querySelector('.lift');
var inputArea = item.querySelector('input[name=product]');
inputArea.value = productName.innerText;
});
setTimeout(function() { passengerElevator() },3000);
However, there is problem of page size (some pages have more than 10 input fields) and I don't want to set an astronomically high ms to delay the script. So I decided to fire it on DOMContentLoaded:
document.addEventListener("DOMContentLoaded", passengerElevator);
function passengerElevator() {
var getProductName = document.querySelectorAll('[data-id="6657316"]');
getProductName.forEach(function(item) {
var productName = item.querySelector('.lift'); // heading text (ex:Panoramic Lift)
var inputArea = item.querySelector('input[name=product]');
inputArea.value = productName.innerText; //ouput here
});
var getProductName = document.querySelectorAll('[data-id="e9c06d5"]');
getProductName.forEach(function(item) {
var productName = item.querySelector('.lift'); // Heading text (ex:Home Lift)
var inputArea = item.querySelector('input[name=product]');
inputArea.value = productName.innerText; // Output here
});
}
As you may have already guessed, it is not working. Is my code too messy to be executed faster or is there any other problem I am missing?
I know similar questions have been asked previously, however, no existing answer I found was able to help me.
It seems like you try to loop through elements that are still not loaded. Perhaps they are being appended to the page via Ajax, so DOMContentLoaded can't help there.
You can create your own check for those elements using setInterval, so use something like this:
let dataIdCheck = setInterval(() => {
if (document.querySelectorAll('[data-id="6657316"]').length > 0 && document.querySelectorAll('[data-id="e9c06d5"]').length > 0) {
clearInterval(dataIdCheck);
// your code here
}
}, 500);
This code will run every 500 milliseconds and check if those two elements exists, using .length. Once they do exists, we stop the interval and run the code.
I also suggest to do console.log('in') to check that our interval stop running once the elements are found.

Image array not being read

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;
}

function increment sync with video (or auto increment)

I'm busy with a webdoc that I'm partially creating on hype, the video are hosted on vimeo (so I need to use the vimeo api for some tasks like seekto) but my difficulties should be limited to js.
the objective is to display a given image at a given time interval of the video.
With my code below, I do get the string "test", "success" and "confirmed success" at the right time in my div id=popimgbox, and I can seek back and forth in the video and still get the right "answear", if I may say so.
Now, I have images that are all stored in the same folder, and all named popimgX.jpg, with X being a number.
I want
to store the URLs of my images in a variable let's say "popimgurl"
that my variable is updated (by a function???) in order to contain the URL of a given immage for a given interval of time of the video
to still be able seekto back and forth in the video and get the right URL at the right time
To do so I created a function increment, and a pair of variable. With the code below, my popimgurl variable is indeed updated once the video reach 3 seconds, but it do not increment only once... untill the video reach 6 seconds, when I want to update my popimgurl variable once again.
I tried to use for with js break and js closure but did not manage for some understandable reasons after thought;
I did quite some try with switch, but I'm stuck with the fact that the case must be string or single numerical value, not numerical interval or comparaison.
thank's in advance for your help :-)
var iframe = $('#vplayer_1')[0];
var player = $f(iframe);
var status = $('.status');
fired = 0;
//my try to sync increment
var dia = (function () {
var n = 0;
return function increment() {return n += 1;}
})();
function dian(){
popimgurl = '${resourcesFolderName}/popimg'+ dia() +'.jpg';
popimgloader = '<img src ="' + popimgurl + '">';
}
// When the player is ready, add listeners for pause, finish, and playProgress
player.addEvent('ready', function() {
status.text('ready');
player.addEvent('pause', onPause);
player.addEvent('finish', onFinish);
player.addEvent('playProgress', onPlayProgress);
});
// Call the API when a button is pressed
$('button').bind('click', function() {
player.api($(this).text().toLowerCase());
});
function onPause(id) {
status.text('paused');
}
function onFinish(id) {
status.text('finished');
}
function onPlayProgress(data, id) {
status.text(data.seconds + 's played');
//my chapters, when I want the img to change within popimgbox
if (data.seconds >= 1) {
popimgbox.innerHTML = "test";
}
if (data.seconds >= 3) {
// popimgbox.style.display = "success"
dian();
popimgbox.innerHTML = popimgurl;
}
if (data.seconds >= 6) {
// popimgbox.style.display = "confirmed success"
dian();
popimgbox.innerHTML = popimgurl;
}
}
PS1: disclamer, I'm a beginer coder, i do my best so excuse my french if my question isn't well formulated or if the answer is somewhere but I was unable to see/understand it
PS2 : i did quite a try with popcornjs, but not way to make it work with vimeoapi and within hype, quite frustrated ;-)
PS3: as this is my first post I would like to thank's you all for the great support available here; I owe you most ;-)
Finally I'll answer myself.
It's a solution that only stand for vimeo, as this is what I use to host my videos, but very little changes have to be done to work with the html5 <video> tag as well.
First you need to define your variables and your intervals:
var intervals =[11.56, 44.08, 115, 125, 127.92, 177.72];
var index;
Then you need to add an event listener timeupdate that return the elapsed time , filter intrevals according to the elapsed time data.seconds or seconds and define the value of index as the indexOf the last entry of your filtered array intervals
player.on('timeupdate', function(data) {
seconds = data.seconds;
index = intervals.indexOf(intervals.filter(function(nb) {
return seconds < nb;
})[0]);
if (diaIndex == -1) {
// do something if seconds > the higher value of your last interval
}
And that's it !
Now for
seconds = [0, 11.56[ --> index = 0
seconds = [11.56, 44.08[ --> index = 1
seconds = [44.08, 115[ --> index = 2
and so on
Now we can use index as a variable for instance to display a given image :
var imgNum = 0;
function diaplayImg(index) {
if(index === imgNum) {
return;
// this will avoid that the same image is releaded on every timeupdate events
}
else {
imgNum =+ index
document.getElementById('myImageWraper').innerHTML = "<img src='img" + imgNum+ ".png'>"
};
}
Don't forget, you need to call the function displayImage() in your timeupdate event listener, it will then be fired every ±250ms but the image won't be reloaded each time
PS : vimeo has realeased its new api between my question and my answer so the question use the old api while the answer use the new one

onerror event not being detected during image preloading?

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 ...
}

How to display loading status with preloader and multiple images?

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...

Categories

Resources