referencing function inside a for loop in js - javascript

I have the following code:
function fn_get_natural_dim(){
var width = this.width;
var height = this.height;
var ratiow = width/600;
var ratioh = height/300;
$("#output").append(img_name,",");
if(ratiow>=ratioh)
{
height = height/ratiow;
$(slide_image).css("width","600px");
$(slide_image).css("height",height);
var margin = (300-height)/2;
$(slide_image).css("margin-top",margin);
}
else
{
width = width/ratioh;
$(slide_image).css("width",width);
$(slide_image).css("height","300px");
var margin = (600-width)/2;
$(slide_image).css("margin-left",margin);
}
}
var max_count = $(".slider").children().length;
for(var count=1;count<=max_count;count++)
{
var count_string = count.toString();
var img_name = "img" + count_string;
var slide_image = document.getElementById(img_name);
var img = new Image();
img.onload = fn_get_natural_dim;
img.src = $(slide_image).attr("src");
}
});
For each image (i have three at the moment), im getting its natural dimensions so that i can calculate the margin necessary to fit a 600x300 div.
but i only works for the very last one, ie the third one.
in other words, in the #output, it shows "img3,img3,img3," (called by $("#output").append(img_name,",");)
how do i make it show "img1,img2,img3,"??

You can do this:
img.onload = (function(img_name, slide_image) {
return function() {fn_get_natural_dim(img_name, slide_image); };
})(img_name, slide_image);
and change fn_get_natural_dim to take the image name as a parameter:
function fn_get_natural_dim(img_name, slide_image) {
The above code captures each successive value of img_name in a closure variable.

Edit
As Barmar pointed out, this solution isn't exactly correct. I'll leave it up as it's a fairly common mistake and is important to note the difference between his solution and my own.
Pass img_name into your fn_get_natural_dim function:
function fn_get_natural_dim( img_name ) { ... }
Then call it like:
img.onload = function() { fn_get_natural_dim(img_name); };

Related

How can I create a function in which a value of variable changes after used once?

function catGenerate(){
var image = document.createElement('img');
var division = document.getElementById('flexID');
pic1 = 300;
image.src = "https://placekitten.com/200/" + String(pic1) ;
division.appendChild(image);
pic1++;
}
I want to add pic1 value by one when once the function is used, how can I do it?
I wouldn't encourage using global variables. This is a classic closure example, your generator function should return a function like this:
function catGenerator() {
// internal state
let pic1 = 300;
return function doSomething() {
var image = document.createElement('img');
var division = document.getElementById('flexID');
image.src = "https://placekitten.com/200/" + pic1;
division.appendChild(image);
pic1 += 1;
}
}
Then use it like this:
const generator = catGenerator();
generator(); // pic1 = 300
generator(); // pic1 = 301
... etc
This is a good blog post that explains how this works: https://medium.com/#prashantramnyc/javascript-closures-simplified-d0d23fa06ba4

JavaScript Image().width returns 0

I need to get width and height of images with display:none and I'm getting both width and height 0.
here's my code:
for (i = 0; i <= slideCount; ++i) {
imgPath = slideshow.eq(i).attr("src");
imgEle = new Image();
imgEle.src = imgPath;
imgEle.addEventListener("load", function () {
imgSize = [imgEle.width, imgEle.height];
if ((imgSize[0]/imgSize[1]) > (boxSize[0]/boxSize[1])) {
slideshow.eq(i).css("height", "100%");
}
else {
slideshow.eq(i).css("width", "100%");
}
});
}
I tried onload = funcion() {} approach as well as not checking if image is loaded, yet still I'm getting width and height as 0. What's more when i launched console on Chrome and referred to those fields I got proper values.
It is because of the wrong use of closure variable in a loop, when the load handler is executed the variable imgEle refers to the last image object reference, not the one on which the load event was called, so when the first element is loaded the last element might not have been loaded so imgEle.width/imgEle.height will return 0
Instead you can use this which will refer to the element on which the load event was called inside the load event handler
From what I can see, what you need is
slideshow.each(function () {
var $this = $(this);
var imgPath = $this.attr("src");
var imgEle = new Image();
imgEle.src = imgPath;
imgEle.addEventListener("load", function () {
var imgSize = [this.width, this.height];
if ((imgSize[0] / imgSize[1]) > (boxSize[0] / boxSize[1])) {
$this.css("height", "100%");
} else {
$this.css("width", "100%");
}
});
})
Note: the array index runs from 0...length-1 so your loop condition <= slideCount; might have to be relooked at
imgEle is overwritten each time your loop runs. Wrap an anonymous function around it to prevent this issue.
for(var i = 0; i <= slideCount; ++i) {
(function(i){
var imgPath = slideshow.eq(i).attr("src");
var imgEle = new Image();
imgEle.src = imgPath;
imgEle.addEventListener("load", function () {
imgSize = [imgEle.width, imgEle.height];
if ((imgSize[0]/imgSize[1]) > (boxSize[0]/boxSize[1])) {
slideshow.eq(i).css("height", "100%");
}
else {
slideshow.eq(i).css("width", "100%");
}
});
})(i);
}

How can I make this function loop with javascript?

function argsToArray(args) {
var r = []; for (var i = 0; i < args.length; i++)
r.push(args[i]);
return r;
}
argsToArray(document.getElementsByTagName('img')).forEach(function(img) {
img.src = img.src.split('VTlibOlte8YCb').join('X0X810D0' + Math.floor((Math.random()*10)+1));;
});
I tried adding setInterval(argsToArray,500); at the end but that seem to have broken things.
This is quite archaic and will probably crash the browser, but for this experiment it might just work.
function reloadPage()
{
location.reload();
}
setInterval(reloadPage,.5);
I assume from using native forEach that you're targeting IE9+, so instead of manually pushing the collection contents into an array you could just:
function argsToArray(args) {
return Array.prototype.slice.call(args)
}
The rest of the code looks perfectly workable, maybe there's something wrong with the split() or join() arguments. Please explain what are you trying to achieve here.
Adding setInterval(argsToArray,500) would just call your first function without any arguments, you should use an anonymous function or pass arguments into the setInterval/setTimeout function (see MDN).
So you want to do something like this?
window.onload=function() {
var imgs = document.images;
var tId = setInterval(function() {
for (var i=0;i<imgs.length;i++) {
var img = imgs[i];
var val = 'X0X810D0' + (Math.floor(Math.random()*10)+1);
img.src = img.src.replace(/VTlibOlte8YCb/g,val);
}
},1000);
}
which is designed replace the src of each image every second - but actually only once since there is no more VTlibOlte8YCb to replace after the first time
Here is one that does replace the value each time
Live Demo
window.onload=function() {
var imgs = document.images;
var oldVal = new RegExp(/VTlibOlte8YCb/g);
var val = 'X0X810D0' + (Math.floor(Math.random()*10)+1);
var tId = setInterval(function() {
for (var i=0;i<imgs.length;i++) {
var img = imgs[i];
val = 'X0X810D0' + (Math.floor(Math.random()*10)+1);
img.src = img.src.replace(oldVal,val);
oldVal = new RegExp("/"+val+"/g");
}
},200);
}

Replacing an old image with a new one. JavaScript

I have a function that places an image provided by the input of the user into the body of an html page. When a second input is received I want to replace this picture with the new one. I have attempted to do just this in the below function.
function show_image(src, alt) {
var img = document.createElement("img");
img.src = src;
img.width = 400;
img.height = 300;
img.alt = alt;
var counter;
var mynodes= new Array();
mynodes.push(img);
counter+=1;
if(counter==1){
// This next line will just add it to the <body> tag
document.body.appendChild(img);
}
else if(counter!=1)
{
var newNode=mynodes[counter-1];
var oldNode=mynodes[counter-2];
document.body.replaceChild(newNode,oldNode);
}
The variable counter is a local variable.
Each time the method is called counter is initialized to 0.
Same thing with your mynodesvariable. It is always going to have only one node in the array.
So you may want to change your logic here. Do you want help rewriting this function?
Your code is totally awkward. As far as I can tell, you could simply change the same img tag instead of changing the whole node each time. There's barely a reason for a function..
Click here for a live demo.
function changeImg(img, src, alt) {
//I doubt you even need a function for this.
img.src = src;
img.alt = alt;
}
var img = document.createElement('img');
//you could just use a css class instead...
//that's probably better anyway...why does your js care how it looks?
img.width = 400;
img.height = 300;
document.body.appendChild(img);
changeImg(img, 'http://static.jsbin.com/images/favicon.png', 'image');
var imgSrcInput = document.getElementById('imgSrc');
var imgAltInput = document.getElementById('imgAlt');
var button = document.getElementById('change');
button.addEventListener('click', function() {
changeImg(img, imgSrcInput.value, imgAltInput.value);
});
I put together an object oriented example. This makes your logic very reusable and friendly. Check out the code in action here (click).
window.onload = function() { //usage
var someElement = document.getElementById('someIdHere');
var anotherElement = document.getElementById('anotherIdHere');
imageReplacer.setup({
element: someElement,
initSrc: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRg-icdkibfDt8VE7FkaKZyVUh8SBR4YTGd-2Jz1wZeUVacv4YD8zrvwclN',
initAlt: 'Starting Image'
});
imageReplacer.setup({
element: anotherIdHere,
initSrc: 'https://encrypted-tbn1.gstatic.com/images?q=tbn:ANd9GcRrHWuOzgYPbsuSF9cYQo3ORkcdIC4-8xaszlCrI3f7arAC7vhV7HwMN_fG',
initAlt: 'Starting Image'
});
};
var imageReplacer = { //package up your app
setup : function(options) {
//create a new imageReplacer so that each one will operate independently. The "this" pointer will refer to each element's imageReplacer.
options.element.imageReplacer = Object.create(imageReplacer);
options.element.imageReplacer.makeReplacer(options);
},
makeReplacer : function(options) {
options.element.className = 'imageReplacer';
var markup = this.createMarkup();
this.changeImage(options.initSrc, options.initAlt);
this.addClick(this.button);
options.element.appendChild(markup);
},
createMarkup : function() {
var frag = document.createDocumentFragment();
this.srcInput = document.createElement('input');
this.srcInput.type = 'text';
this.srcInput.placeholder = 'Image Source';
this.altInput = document.createElement('input');
this.altInput.type = 'text';
this.altInput.placeholder = 'Image Alt';
this.button = document.createElement('button');
this.button.textContent = 'Change Image';
this.imgTag = document.createElement('img');
frag.appendChild(this.srcInput);
frag.appendChild(this.altInput);
frag.appendChild(this.button);
frag.appendChild(this.imgTag);
return frag;
},
addClick : function(button) {
var that = this;
button.addEventListener('click', function() {
that.changeImage(that.srcInput, that.altInput);
});
},
changeImage : function(src, alt) {
this.imgTag.src = src;
this.imgTag.alt = alt;
}
};
Instead of creating a function to dynamically change the picture(which did work), I decided to hide one picture on my index.html file. And then changed the src with $("#my_image").attr("src","img/fire.gif"); #my_image being the id of the image tag.

Why doesn't this Javascript image object get added to the image array?

I've been trying to create a small HTML5-based example, but I've ran into some problems. I want to render a rock on a <canvas> but this Javascript object won't work properly.
function ImageData() {
this.image_names = ["rock1"];
this.images = new Array();
this.loadResources = function () {
for (var i = 0; i < this.image_names.length; i++) {
var img = new Image();
img.onload = (function (a) {
a.images[a.image_names[i]] = img;
console.log(a.images); //log is successful, image is in array
})(this);
img.src = "images/" + this.image_names[i] + ".png";
}
}
}
It's use is to be as follows:
var a = new ImageData();
a.loadResources();
var rock1_image = a.images["rock1"]; //the "rock1.png" image
If you try accessing the object via console, you'll see that there is no image in the image array, after being certain the image loaded. I can't figure out why it's not working. I've looked over it multiple times.
EDIT: Here is my final, working result:
function ImageData() {
this.image_names = ["rock1"];
this.images = new Array();
this.loadResources = function (callback) {
for (var i = 0; i < this.image_names.length; i++) {
var img = new Image();
img.onload = (function (a, i) {
return function() {
a.images[a.image_names[i]] = img;
if (i == (a.image_names.length - 1)) callback();
};
})(this, i);
img.src = "images/" + this.image_names[i] + ".png";
}
}
}
I would venture to say that you are attempting to access it before it is added to your array. I would suggest adding a callback or something to your loadResources method to make sure that it's there before you attempt to access it.
this.loadResources = function (callback) {
for (var i = 0; i < this.image_names.length; i++) {
var img = new Image();
img.onload = (function (a, i) {
return function() {
a.images[a.image_names[i]] = img;
console.log(a.images); //log is successful, image is in array
callback();
};
})(this, i);
img.src = "images/" + this.image_names[i] + ".png";
}
}
then
var a = new ImageData();
var rock1_image;
a.loadResources(function() {
rock1_image = a.images["rock1"];
// do whatever you need to do with the image in here.
});
Also, what #Igor mentions. I totally missed that. I adjusted the code above so you could keep your this scope without having to set it elsewhere.
Updated to take care of the i issue brought up by #RobD.
Remove (this) after onload handler definition. You are actually calling this function right away, assigning undefined (since it returns nothing) as img.onload value.
In the code:
> function ImageData() {
> this.image_names = ["rock1"];
> this.images = new Array();
> this.loadResources = function () {
> for (var i = 0; i < this.image_names.length; i++) {
> var img = new Image();
> img.onload = (function (a) {
This function will be run immediately and does not return a value, so undefined will be assigned. So no point in the assignment, just run the code.
> a.images[a.image_names[i]] = img;
Images is an array that is used as a plain object. Better to use a plain object then.
> console.log(a.images); //log is successful, image is in array
> })(this);
The outer this must be passed in because of the IIFE. Remove the IIFE and there is no requirement to pass this. And if a reference to the instance is stored in the function, it won't matter how the function is called (i.e. you won't have to set this in the call).
> img.src = "images/" + this.image_names[i] + ".png";
> }
> }
> }
You might want something like:
function ImageData() {
this.image_names = ['rock1'];
this.images = {};
imageData = this;
this.loadResources = function (callback) {
for (var i = 0; i < imageData.image_names.length; i++) {
var img = new Image();
imageData.images[a.image_names[i]] = img;
img.src = 'images/' + this.image_names[i] + '.png';
if (callback) {
img.onload = callback;
}
}
}
}
var a = new ImageData();
a.loadResources(function(){console.log('loaded: ' + this.src);}); // loaded: …
window.onload = function() {
document.body.appendChild(a.images.rock1); // image appears in document
}
I haven't tested your code yet, but I guess this is the problem:
You are trying to access your image before it was loaded. You can use callback event handler after it was load like:
var data = new ImageData();
data.loadResources(function(imgObj){
// imgObj is the newly load image here. Have fun with it now!
})

Categories