I have an ad slide-show on my homepage. It consists of 2 images.
I preload these 2 images, making new Image() and setting .src to them.
I have a function giveNextName that return name of the next image (that should be in src attribute of the img element) (I do this, because soon, slideshow will consist of more than 2 images)
So the main code looks like:
BillBoard = {};
BillBoard.folder = "/pictures/cards/";
BillBoard.ext = "png";
BillBoard.$ = $("#billboard img");
BillBoard.pics = 2;
BillBoard.changeTime = 7000;
BillBoard.names = ["ad1","ad2"];
BillBoard.currentState = 0;
BillBoard.images = // array of preloaded Images
(function(){
var image, images = [];
for (var i=0; i<BillBoard.pics; i++) {
image = new Image ();
image.src = BillBoard.folder+BillBoard.names[i]+'.'+BillBoard.ext;
images.push (image);
}
return images;
}());
BillBoard.giveNextName = function(){/* getting the next name */};
// BillBoard change action
BillBoard.change = function(){
BillBoard.$.fadeOut(function(){
$(this).attr('src', BillBoard.giveNextName());
$(this).fadeIn();
});
}
// Starting BillBoard
window.setInterval(BillBoard.change, BillBoard.changeTime);
So, idea is simple. with window.setInterval I call BillBoard.change every n seconds. But, I don't know why, billboard becomes changing faster and faster, until there will be no picture at all (fadeIn doesn't have time to execute)
Where is my mistake?
UPD. Thanks to Yann Ramin for the link.
I shouldn't call BillBoard.change every n seconds via window.setInterval. Instead, I should add call of BillBoard.change in the callback of fadeOut().
I mean this code:
BillBoard.change = function(){
BillBoard.$.fadeOut(function(){
$(this).attr('src', BillBoard.giveNextName());
$(this).fadeIn();
window.setTimeout(BillBoard.change, BillBoard.changeTime);
});
}
// simply call the change function
// it will be calling itself every n seconds
BillBoard.start = (function(){
window.setTimeout(BillBoard.change, BillBoard.changeTime);
}());
See this for a likely culprit:
http://www.ozonesolutions.com/programming/2011/07/jquery-fadein-window-setinterval-a-bad-combination/
Related
I am trying to return a value inside a function but the variable is only changed inside a function. outside the function the variable remains the same.
I have tried the following:
//variable file_exist still has the value true in it.
img.onerror = function () {
file_exist = false;
};
//returns function(){ reutnr false;};
file_exist = img.onerror = function () {
return false;
};
I cant seem to get the value outisde of this function. Is there a way to get the value false?
full code:
var i = 1;
var file_exist = true;
while(file_exist){
var img = document.createElement("img");
img.src = "ws-img/"+id+"-"+i+".jpg";
//deleted some code since no need here for the exampled
img.onerror = function () {
file_exist = false;
console.log(file_exist);//shows false
};
console.log(file_exist);//shows true
i++;
}
This is not my preferred method, but it will work in your specific scenario...
var imageCount = 0;
function loadImage(onComplete) {
imageCount++;
var img = document.createElement("img");
img.onerror = function () {
// there was an error, so we're assuming there are no more images
onComplete();
};
img.onload = function() {
// the image has loaded - add it to the DOM?
// call this function again to load the next image
loadImage(onComplete);
};
img.src = "ws-img/" + id + "-" + imageCount +".jpg";
// cached images won't trigger onload
if (img.complete) img.onload();
}}
loadImage(function() {
alert("images have all loaded");
});
It creates the image element, as you were doing, for image #1, then on success it will increment the counter and run itself again. That will try and load image #2, and so on and so on. When it errors it stops and executes the callback function so you know all the images are loaded.
There are issues with this though. If an image doesn't load for any reason, it will stop and load no more images. A much better solution would be to either construct the page with all the images, using server-side code (PHP in your case), or if you do want to perform the image load at client-side create an API call that gives you a list of all the images. Javascript can make 1 call to get that data and then load all the images, safe in the knowledge that they do exist.
I have read countless of answers of this issue and I came up with the following, but it doesn't work either.
function fitToParent(objsParent, tagName) {
var parent, imgs, imgsCant, a, loadImg;
//Select images
parent = document.getElementById(objsParent);
imgs = parent.getElementsByTagName(tagName);
imgsCant = imgs.length;
function scaleImgs(a) {
"use strict";
var w, h, ratioI, wP, hP, ratioP, imgsParent;
//Get image dimensions
w = imgs[a].naturalWidth;
h = imgs[a].naturalHeight;
ratioI = w / h;
//Get parent dimensions
imgsParent = imgs[a].parentNode;
wP = imgsParent.clientWidth;
hP = imgsParent.clientHeight;
ratioP = wP / hP;
//I left this as a test, all this returns 0 and false, and they shouldn't be
console.log(w);
console.log(h);
console.log(ratioI);
console.log(imgs[a].complete);
if (ratioP > ratioI) {
imgs[a].style.width = "100%";
} else {
imgs[a].style.height = "100%";
}
}
//Loop through images and resize them
var imgCache = [];
for (a = 0; a < imgsCant; a += 1) {
imgCache[a] = new Image();
imgCache[a].onload = function () {
scaleImgs(a);
//Another test, this returns empty, for some reason the function fires before aplying a src to imgCache
console.log(imgCache[a].src);
}(a);
imgCache[a].src = imgs[a].getAttribute('src');
}
}
fitToParent("noticias", "img");
To summarise, the problem is the event onload triggers before the images are loaded (or that is how I understand it).
Another things to add:
I don't know at first the dimensions of the parent nor the child,
because they varied depending of their position on the page.
I don't want to use jQuery.
I tried with another function, changing the onload event to
window, and it worked, but it takes a lot of time to resize because
it waits for everything to load, making the page appear slower,
that's how I came to the conclusion the problem has something to do
with the onload event.
EDIT:
I made a fiddle, easier to look at the problem this way
https://jsfiddle.net/whn5cycf/
for some reason the function fires before aplying a src to imgCache
Well, the reason is that you are calling the function immedeatly:
imgCache[a].onload = function () {
}(a);
// ^^^ calls the function
You call the function and assign undefined (the return value of that function) to .onload.
If you want to use an IIFE to capture the current value of a, you have to make it return a function and accept a parameter to which the current value of a is assigned to:
imgCache[a].onload = function (a) {
return function() {
scaleImgs(a);
};
}(a);
Have a look again at JavaScript closure inside loops – simple practical example .
I draw several images with a function that performs something similar to:
context.drawImage(img, width / 2 * (-1), height / 2 * (-1), width, height);
I've read that I need to wait for the image to be loaded before I can draw it, with something like this:
img.onload = function() {
context.drawImage(img, width / 2 * (-1), height / 2 * (-1), width, height);
};
However this causes the image to be drawn but afterwards, nothing is drawn since I call up my draw function every few milliseconds as it's part of the 'animation' loop for a simple game.
Is there a way that I can wait for the onload event before I continue running code in my init() function?
I suppose something like:
var image_loaded = false;
img.onload = function() {
image_loaded = true;
};
if(image_loaded) {
animate();
}
Should work? Unless I need to add a timeOut function to keep calling the init() until image_loaded is true?
Live Demo
var imagesLoaded = [],
images = [
'http://www.zombiegames.net/inc/screenshots/The-Last-Stand-Union-City.png',
'http://www.zombiegames.net/inc/screenshots/Lab-of-the-Dead.png',
'http://www.zombiegames.net/inc/screenshots/Zombotron.png',
'http://www.zombiegames.net/inc/screenshots/Road-of-the-Dead.png'],
canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d");
canvas.width = 640;
canvas.height = 480;
function init(){
// just loops through the images.
if(imagesLoaded.length > 0){
ctx.drawImage(imagesLoaded[0], 0, 0, 640, 480);
imagesLoaded.push(imagesLoaded.shift());
}
setTimeout(init,500);
}
function preload(){
for(var i = 0; i < images.length; i++){
(function(value){
return function(){
var loadedImage = new Image();
loadedImage.src = images[value];
loadedImage.onload = function(){
imagesLoaded.push(loadedImage);
}
}();
})(i);
}
checkLoaded();
}
function checkLoaded(){
if(imagesLoaded.length === images.length){
console.log(imagesLoaded.length);
init();
} else{
setTimeout(checkLoaded,30);
}
}
preload();
Above is an example of how to preload images and wait to do anything else. Basically what you do is have all your images in an array, and add the onload event to each of them. As they load I through them into another array that holds all the loaded images. Once the length of the two arrays match all of the images are ready to use.
Another way would be to increment a counter as they load and check its value against the length of the array. When the counter variable is equal to the length of the images array it means they've all loaded and are ready to use.
I created a simple and small library to make the load of images easy.
Check the demo
See the library code below:
// Simple Image Loader Library
window.Loader = (function () {
var imageCount = 0;
var loading = false;
var total = 0;
// this object will hold all image references
var images = {};
// user defined callback, called each time an image is loaded (if it is not defined the empty function wil be called)
function onProgressUpdate() {};
// user defined callback, called when all images are loaded (if it is not defined the empty function wil be called)
function onComplete() {};
function onLoadImage(name) {
++imageCount;
console.log(name + " loaded");
// call the user defined callback when an image is loaded
onProgressUpdate();
// check if all images are loaded
if (imageCount == total) {
loading = false;
console.log("Load complete.");
onComplete();
}
};
function onImageError(e) {
console.log("Error on loading the image: " + e.srcElement);
}
function loadImage(name, src) {
try {
images[name] = new Image();
images[name].onload = function () {
onLoadImage(name);
};
images[name].onerror = onImageError;
images[name].src = src;
} catch (e) {
console.log(e.message);
}
}
function getImage(/**String*/ name){
if(images[name]){
return (images[name]);
}
else{
return undefined;
}
}
// pre-load all the images and call the onComplete callback when all images are loaded
// optionaly set the onProgressUpdate callback to be called each time an image is loaded (useful for loading screens)
function preload( /**Array*/ _images, /**Callback*/ _onComplete, /**Callback <optional>*/ _onProgressUpdate) {
if (!loading) {
console.log("Loading...");
loading = true;
try {
total = _images.length;
onProgressUpdate = _onProgressUpdate || (function(){});
onComplete = _onComplete || (function(){});
for (var i = 0; i < _images.length; ++i) {
loadImage(_images[i].name, _images[i].src);
}
} catch (e) {
console.log(e.message);
}
} else {
throw new Error("Acess denied: Cannot call the load function while there are remaining images to load.");
}
}
// percentage of progress
function getProgress() {
return (imageCount / total)*100;
};
// return only the public stuff to create our Loader object
return {
preload: preload,
getProgress: getProgress,
getImage: getImage,
images: images // have access to the array of images might be useful but is not necessary
};
})();
How it works
To make sure that images are loaded and they could be used by your application the library have the Loader.preload method.
The preload method will receive an array of objects, each object containing the name and the src properties of an image you want to load. Optionally you can setup the onComplete callback (to be called when all images are loaded) and the onProgressUpdate callback (to be called each time an image is loaded). The onProgressUpdate callback is useful if you want to create a loading screen for your application.
Use the Loader.getProgress() to obtain the percentage of images loaded at any time.
To obtain the reference of an image loaded, call Loader.getImage(name) where name is the name property (a String) of the image.
If you for some reason needs iterate over all images use Loader.images. It's the object containing all references for the images in its properties.
Use like this:
var images = [{name: "img1", src: "http://...a.."},
{name: "img2", src: "http://...b.."},
...
{name: "imgN", src: "http://...N.."}];
function onProgressUpdate(progress){
...
drawProgressBar(progress); // just an example of what you can do
...
}
function onComplete(){
...
// get an Image from Loader object
var texture = Loader.getImage("img2");
// or use this:
var texture = Loader.images.img2; // or still Loader.images["img2"]
...
// iterate over the images
for (var img in Loader.images){
console.log(Loader.images[img].src);
}
....
}
Loader.preload(images, onComplete, onProgressUpdate);
Check the demo if you didn't.
So on my new website in google chrome, I am trying to make the images switch every 5 seconds using a setInterval function. This seems to work, however I have the problem cannot set property src of null.
var images = new Array();
images[0] = "/images/grilled-chicken-large.png";
images[1] = "/images/sizzly-steak.png";
var img = document.getElementById("img");
function changeImage() {
for (var i = 0; i < 3; i++) {
img.src = images[i];
if (i == 2){i = i - 2;};
}
}
window.onload=setInterval("changeImage()", 5000);
I know the problem is that I am trying to get the element with an id of img before the page is done loading, but I've already got a window.onload, so i don't know what to do. Also, if i make an init() function containing the setInterval and the img variable, the page freezes.
Any ideas?
The problem is that you access element 2 of an array with only two elements (0 and 1).
But there is also a logical error in your script: Where do you store the index of the currently visible image? You need a global variable which holds that index.
var currentIndex = 0;
function changeImage() {
// Switch currentIndex in a loop
currentIndex++;
if (currentIndex >= images.length)
currentIndex = 0;
var img = document.getElementById("img");
img.src = images[currentIndex];
}
document.onload = function() {
setInterval(changeImage, 5000);
}
I've moved the img variable into the function so that it is assigned after five seconds when the document has loaded.
Just to clear things up: JavaScript is an event-based programming language. That means that the slideshow-change code is executed every time the interval fires, does some work (switch to the next slide) and then is done, so that the browser can render the page. If you iterate through the slides in a loop, there is no way for the browser to show the new slide because the script is still executing between the slides. The solution is what I've posted: Define a global variable that replaces the loop variable, and increment it every time the next slide is to be shown.
Here:
window.onload = function () {
var images = [
'http://placekitten.com/100/200',
'http://placekitten.com/200/100',
];
var img = document.getElementById( 'img' );
setInterval(function () {
img.src = img.src === images[0] ? images[1] : images[0];
}, 1000 );
};
Live demo: http://jsfiddle.net/wkqpr/
If you have more than two images, you can do this:
window.onload = function () {
var images = [
'http://placekitten.com/100/200',
'http://placekitten.com/200/100',
'http://placekitten.com/200/150',
'http://placekitten.com/150/300'
];
images.current = 0;
var img = document.getElementById( 'img' );
setInterval(function () {
img.src = images[ images.current++ ];
if ( images.current === images.length ) images.current = 0;
}, 1000 );
};
Live demo: http://jsfiddle.net/wkqpr/1/
Your for loop looks odd. Try that loop:
function changeImage(){
if(!img.attributes["index"]){img.attributes["index"]=0;return;};
img.attributes["index"]++;
img.src=images[img.attributes["index"]];
}
window.onload=function(){
window.img=document.getElementById("img");
setInterval("changeImage()",5000)
}
Should work.
You'll notice that I store the image index in an attribute of the image, as opposed to
a global variable. I think the global is slightly faster, but storing it in the image
means you can easily expand this to multiple images if needed.
Also, the problem with your browser freezing was that the following code:
for(var i=0;i<3;i++){
//Some more stuff
if(i==2){i-=2};
}
This loop is infinite, because whenever i reaches 2, i==2 becomes true, which means that iteration will reset i to 0 and start it all over again. The only way to prevent that would be a break; somewhere, which I don't see anywhere.
Tip: It is generally a bad idea to tamper with the index variable in for loops.
function changeImage()
{
for (var i = 0; i < 2; i++)
{
img.src = images[i];
}
}
window.onload=function()
{
setInterval(changeImage,5000);
}
Your last line in the for loop is completely useless and just complicate things,
Also, Your way of setting a window onload event is not standard!
EDIT: the src property is null because , obviously, your array size is only 2!!
So I am loading multiple images onto multiple canvases basically (one image per canvas/ctx - its a slideshow). I want to make sure that each image is loaded before it attempts to draw the image onto the canvas.
Here is the code...
In Example 1 i'm using 3 onload events (one for each image)
In Example 2 i'm using one onload event, but it is the last image that gets called in the for loop
Question: Can i use Example 2 and be confident to assume that if the last image is loaded, then the images before must be loaded as well?
Also---- I attempted this already, but can it all be done inside a for loop? I wasn't able to get it to work. See Example 3
Example 1 3 onload events
var img=[0,'slide1','slide2','slide3'];
for (var i=1;i<=3;i++) {
img[i] = new Image();
img[i].src = '/images/slide' + i + '.png'
}
img[1].onload = function() { // checks to make sure img1 is loaded
ctx[1].drawImage(img[1], 0, 0);
};
img[2].onload = function() { // checks to make sure img2 is loaded
ctx[2].drawImage(img[2], 0, 0);
};
img[3].onload = function() { // checks to make sure img3 is loaded
ctx[3].drawImage(img[3], 0, 0);
};
Example 2 only the last onload event
var img=[0,'slide1','slide2','slide3'];
for (var i=1;i<=3;i++) {
img[i] = new Image();
img[i].src = '/images/slide' + i + '.png'
}
img[3].onload = function() { // checks to make sure the last image is loaded
ctx[1].drawImage(img[1], 0, 0);
ctx[2].drawImage(img[2], 0, 0);
ctx[3].drawImage(img[3], 0, 0);
};
Example 3 one onload in the for loop to do all the onload events
var img=[0,'slide1','slide2','slide3'];
for (var i=1;i<=3;i++) {
img[i] = new Image();
img[i].src = '/images/slide' + i + '.png'
img[i].onload = function() { // checks to make sure all images are loaded
ctx[i].drawImage(img[i], 0, 0);
};
}
I assume I can't do example 3 because an image probably won't be loaded by the time the for loop runs the second time and rewrites the onload event for the next image thus erasing the onload event for the prior image. Correct?
You'll have scoping issues with the last for-loop because of closures. In order for something like this to work, you'll want to break out of the closure by encapsulating your functional assignment in it's own function. This article explains it well.
Ideally, your last for-loop would look something like this:
var images = [0,'slide1.png', 'slide2.png', 'slide3.png'];
// Assign onload handler to each image in array
for ( var i = 0; i <= images.length; i++ ){
var img = new Image();
img.onload = (function(value){
return function(){
ctx[value].drawImage(images[value], 0, 0);
}
})(i);
// IMPORTANT - Assign src last for IE
img.src = 'images/'+images[i];
}
Also, keep in mind that you'll need to assign the img.src attribute AFTER the onload handler has been defined for some flavors of IE. (this little tidbit cost me an hour of troubleshooting last week.)