Variable scope & event triggered anonymous functions - javascript

I'm creating an image zoom via the scroll wheel but am having trouble passing variables between functions.
Before I call the img_scroll() function I need to get the minimum and maximum dimensions of the image I want to zoom in and out of. to get the dimensions I need to wait for the image to load first.
So right now I wait for the image to load, call the dimensions function, then once the dimensions are loaded, I call the scroll functions. But right now I'm having trouble passing the variables down the chain to the scroll function.
zoom = 0.01;
$(document).ready(function(){
$("#img_wrapper").imagesLoaded(function(){
getImgSizes();
});
});
function getMinImgSize() {
if ($("#img_wrapper img").width() <= $("#img_wrapper img").height()) {
$("#img_wrapper img").css("width", "100%");
} else {
$("#img_wrapper img").css("height", "100%");
}
min_width = $("#img_wrapper img").width();
min_height = $("#img_wrapper img").height();
return {height: min_height, width: min_width};
}
function getImgSizes() {
var newImg = new Image();
var width, height;
imgSrc = $("#img_wrapper img")[0].src;
minDimensions = getMinImgSize();
newImg.onload = function(minDimensions) { //incorect way to pass varaible to anonymous function
originalHeight = newImg.height;
originalWidth = newImg.width;
maxDimensions = {height: originalHeight, width: originalWidth};
img_scroll(minDimensions, maxDimensions);
}
newImg.src = imgSrc; // this must be done AFTER setting onload
}
function img_scroll(minDimensions, maxDimensions){
var minDimensions = minDimensions;
var maxDimensions = maxDimensions;
$('#img_wrapper').mousewheel(function(event, delta, deltaX, deltaY) {
console.log(delta, deltaX, deltaY);
if (deltaY < 0) {
if ($("#img_wrapper img").width < maxDimensions.width) {
$("#img_wrapper img").width($("#img_wrapper img").width()*(1+zoom));
$("#img_wrapper img").height($("#img_wrapper img").height()*(1+zoom));
} else {
$("#img_wrapper img").width(maxDimensions.width);
$("#img_wrapper img").height(maxDimensions.height);
}
} else {
if ($("#img_wrapper img").width > minDimensions.width) {
$("#img_wrapper img").width($("#img_wrapper img").width()*(1-zoom));
$("#img_wrapper img").height($("#img_wrapper img").height()*(1-zoom));
} else {
$("#img_wrapper img").width(minDimensions.width);
$("#img_wrapper img").height(minDimensions.height);
}
}
});
}
Any help on how to pass the min & max dimension variables down the chain would be great.

Through the way closures work in Javascript, minDimensions is available to your anonymous function without being explicitly passed in. This should work:
function getImgSizes() {
var newImg = new Image();
var width, height;
var imgSrc = $("#img_wrapper img")[0].src;
var minDimensions = getMinImgSize();
newImg.onload = function() {
var originalHeight = newImg.height;
var originalWidth = newImg.width;
var maxDimensions = {height: originalHeight, width: originalWidth};
img_scroll(minDimensions, maxDimensions);
}
newImg.src = imgSrc; // this must be done AFTER setting onload
}
EDIT: Use var to prevent global variables.

Related

How to return function with img.onload?

I am currently building an application, in which a user can upload several images and work with them simultaneously. Since some processes performed poorly due to large image files, I want to resize them, before the user gets them.
In my resizer() function I try to resize using canvas. It works, but since the 'canvas.toDataURL()' is inside the img.onload function, I don't know how to return the value and parse it to the handleFiles() function.
Also...I tried some cases in which I got some code from the handleFiles() - like:
var preview = document.getElementById("img"+count);
var surface = document.getElementById("cubface"+count);
var count = counterImg();
preview.src = resizer(e.target.result, count);
surface.src = resizer(e.target.result, count);
And put them in the end of the img.onload function like
var preview = document.getElementById("img"+number);
var surface = document.getElementById("cubface"+number);
preview.src = canvas.toDataURL;
surface.src = canvas.toDataURL;
I got the resize, but I got only the last image from the loop to be processed.
So the questions are:
In the resizer() function, how to return the canvas.toDataURL value which is in img.onload?
Why does the loop cover only the last instance and not every image and how to solve that?
Full code:
JavaScript:
function resizer(base64, number){
// Max size for thumbnail
if(typeof(maxWidth) === 'undefined') maxWidth = 1200;
if(typeof(maxHeight) === 'undefined') maxHeight = 1200;
var img = new Image();
img.src = base64;
// Create and initialize two canvas
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
var canvasCopy = document.createElement("canvas");
var copyContext = canvasCopy.getContext("2d");
img.onload = function() {
// Determine new ratio based on max size
var ratio = 1;
if (img.width > maxWidth)
ratio = maxWidth / img.width;
else if (img.height > maxHeight)
ratio = maxHeight / img.height;
// Draw original image in second canvas
canvasCopy.width = img.width;
canvasCopy.height = img.height;
copyContext.drawImage(img, 0, 0);
// Copy and resize second canvas to first canvas
canvas.width = img.width * ratio;
canvas.height = img.height * ratio;
ctx.drawImage(canvasCopy, 0, 0, canvasCopy.width, canvasCopy.height, 0, 0, canvas.width, canvas.height);
return canvas.toDataURL();
};
return img.onload;
}
function handleFiles(files) {
for (var i = 0; i < files.length; i++) {
var file = files[i];
var count = counterImg();
var preview = document.getElementById("img"+count);
var surface = document.getElementById("cubface"+count);
var reader = new FileReader();
reader.onload = (function (preview, surface) {
return function (e) {
var newimage = resizer(e.target.result, count);
preview.src = newimage;
surface.src = newimage;
$('#image-cropper'+count).cropit('imageSrc', e.target.result);
}
})(preview, surface);
reader.readAsDataURL(file);
}
}
The variable count is not scoped properly.
There's a delay between the time you declare the function and the time you use it, therefore, each execution ends up with the same value. So the jquery selector will always return the same element. Which explains why only the last image is modified.
Here's a jsfiddle that demonstrate the execution.
https://jsfiddle.net/Lc6bngv5/1/
To fix this, simply pass count to the function that encapsulate your preview and surface :
reader.onload = (function (preview, surface, count) {
return function (e) {
var newimage = resizer(e.target.result, count);
preview.src = newimage;
surface.src = newimage;
$('#image-cropper'+count).cropit('imageSrc', e.target.result);
}
})(preview, surface, count);
For the second question :
The resize function returns a function. It does not return the result of that function. To get the url properly, I would use a callback function :
reader.onload = (function (preview, surface, count) {
return function (e) {
var newimage = resizer(e.target.result, count, function(url){
preview.src = url;
surface.src = url;
$('#image-cropper'+count).cropit('imageSrc', e.target.result);
});
}
})(preview, surface, count);
And you would have to do the following changes in resize :
function resizer(base64, number, cb){
...
img.onload = function() {
...
// return canvas.toDataURL();
cb(canvas.toDataURL());
};
}

wrong width of img tag

i try to build an image uploader for my app. So i need to resize the images.
But often i got the false width value in the img tag.
Not every time. But sometimes when i load the picture 3 times or sometimes if i change the picture then there is the old width in. But also not everytime.
$(document).on('pageshow', '#profil', function () {
$('#content').height(getRealContentHeight());
var max_height = getRealContentHeight();
var max_width = $(window).width();
var username = getUrlParameter('user');
var data;
var x, y, w, h;
var preview;
var reader;
var pic = document.getElementById('pic');
var jcrop_api;
$('#upload').bind("change", function () {
preview = new Image();
var file = document.querySelector('input[type=file]').files[0];
reader = new FileReader();
reader.onloadend = function () {
if (jcrop_api) {
jcrop_api.setImage(reader.result);
}
preview.src = reader.result;
document.getElementById('pic').src = preview.src;
alert(pic.height); //there is the false value...sometimes....
alert(pic.width);
var size = calculateAspectRatioFit(pic.width, pic.height, max_width, max_height);
alert("heigth:" + size.height);
alert("width:" + size.width);
// document.getElementById('pic').height = size.height;
// document.getElementById('pic').width = size.width;
jQuery(function ($) {
$('#pic').Jcrop({
aspectRatio: 1,
minSize: [50, 50],
maxSize: [300, 300],
onChange: showCoords,
onSelect: showCoords
}, function () {
jcrop_api = this;
});
});
function showCoords(c) {
x = c.x;
y = c.y;
w = c.w;
h = c.h;
};
function calculateAspectRatioFit(srcWidth, srcHeight, maxWidth, maxHeight) {
var ratio = Math.min(maxWidth / srcWidth, maxHeight / srcHeight);
return {width: srcWidth * ratio, height: srcHeight * ratio};
}
};
if (file) {
reader.readAsDataURL(file);
} else {
preview.src = "";
}
});
$('#continue').bind("click", function () {
//ajax upload
});
});
Could be one of two issues:
1) Are you setting your '#pic' element's height or width dimensions in css or inline? You may be getting the element's dimensions, not the image's.
2) You are setting 'src' on this img but not waiting until the image has been loaded. You might try:
$("#pic").on("load", function() {
//get dimensions now
});
Do you have to display the image at all? Otherwise you can load it in js using:
var img = new Image();
Then use the jQuery load event handler on this as above. Then set:
img.src = 'image url';
Can you try the following:
$("#pic").height();
$("#pic").width();
$("#pic").outerHeight();
$("#pic").outerWidth();
Instead of:
pic.height
pic.width
And see the right one that suits?

Using JQuery variable outside local scope

I am struggling with an issue that I can't define. I've pieced together this JQuery for uploading an image. When generating the preview, I'm using FileReader to get the image dimensions in order to control the resizing of the preview. As far as I can tell, that is working fine when I do console.log ($preview); inside img.onload = function() {};. But If I do that outside the function, it is undefined. I need access to the variable in the following functions. I've been studying scope, and from what I understand if I don't declare the variable inside the function like var variable = x; it shouldn't be local. I've also searched and searched the internet and SO for a solution but nothing seems to fit.
Is this an asynchronous issue or is it an issue with scope even though $preview is defined initially outside these functions? Can I return it from inside the function like in PHP?
var $preview;
if (previewsOn) {
if (isImgFile(ui.file)) {
var reader = new FileReader;
reader.onload = function() {
var img = new Image;
img.onload = function() {
if (img.width > 120 || img.height > 100) {
var maxWidth = 120;
var maxHeight = 100;
var ratio = 0;
var width = img.width;
var height = img.height;
if (width > maxWidth){
ratio = maxWidth / width;
$preview = $('<img/>').css("width", maxWidth);
$preview = $('<img/>').css("height", height * ratio);
height = height * ratio;
width = width * ratio;
}
if (height > maxHeight){
ratio = maxHeight / height;
$preview = $('<img/>').css("height", maxHeight);
$preview = $('<img/>').css("width", width * ratio);
width = width * ratio;
height = height * ratio;
}
} else {
$preview = $('<img/>');
}
};
img.src = reader.result;
};
reader.readAsDataURL(ui.file);
ui.readAs('DataURL', function(e) {
$preview.attr('src', e.target.result);
});
} else {
$preview = $('<i/>');
ui.readAs('Text', function(e) {
$preview.text(e.target.result.substr(0, 15) + '...');
});
}
} else {
$preview = $('<i>no preview</i>');
}
$($previewImg).empty(); $($preview).appendTo($previewImg);
$('<td/>').text(Math.round(ui.file.size / 1024) + ' KB').appendTo($row);
$('<td/>').append($pbWrapper).appendTo($row);
$('<td/>').append($cancelBtn).appendTo($row);
return $progressBar;
};
You assign value to $preview in .onload handler, but it is called some time later. According to your code following change could help:
var $preview;
if (previewsOn) {
if (isImgFile(ui.file)) {
$preview = $("<img/>");
(added initialization code after if statement)
Then replace lines like
$preview = $('<img/>').css("width", maxWidth);
with
$preview.css("width", maxWidth);
And remove
} else {
$preview = $('<img/>');
So variable will not be re-assigned.
answer to comment
Failure scenario timeline:
you code registers onload handler
you code continues execution, trying to do smth with $preview, which has no value yet
image finally loaded and onload handler triggered, assigning value to $preview

HTML5 Canvas flicker in FireFox 4

I'm working on a proof of concept on an HTML5 canvas. I was able to get this working like a charm in Chrome and IE9, but in Firefox 4 I'm getting constant flicker as it redraws the canvas. I've tried a few techniques mentioned on this site like double buffering but I'm still getting a large amount of flicker. Any insight on this would be appreciated!
<!DOCTYPE HTML>
<html>
<head>
<style type="text/css">
body
{
margin:0px;
padding:0px;
text-align:center;
}
canvas
{
outline:0;
border:1px solid #000;
margin-top: 10px;
margin-left: auto;
margin-right: auto;
}
</style>
<script type="text/javascript">
var canvasWidth = 640;
var thisXPos = 0;
var canvasHeight = 820;
var thisYPos = 0;
var canvas = null;
var context = null;
var gLoop = null;
var rain = [];
var rainImg = "images/raindrop.gif";
var bgImg = null;
var i;
for (i = 0; i < howManyLetters; i++)
{
rain.push([Math.floor(Math.random() * canvasWidth), Math.floor(Math.random() * canvasHeight),rainImg]);
}
var DrawRain = function()
{
for (i = 0; i < 10; i++)
{
thisXPos = rain[i][0];
thisYPos = rain[i][1];
imgSrc = rain[i][2];
letterImg = new Image();
letterImg.setAtX = thisXPos;
letterImg.setAtY = thisYPos;
letterImg.onload = function()
{
context.drawImage(this, this.setAtX, this.setAtY);
}
letterImg.src = imgSrc;
}
};
var MoveRain = function(e)
{
for (i = 0; i < 10; i++)
{
if ((rain[i][1] - 5) > canvasHeight)
{
randomnumber = Math.floor(Math.random()*26);
rain[i][0] = Math.random() * canvasWidth;
rain[i][1] = 0;
rain[i][2] = rainImg;
}
else
{
rain[i][1] += e;
}
}
};
var clear = function()
{
context.beginPath();
context.rect(0, 0, canvasWidth, canvasHeight);
context.closePath();
bgImg = new Image();
bgImg.src = "images/bg.png";
bgImg.onload = function()
{
context.drawImage(bgImg,0,0);
}
}
var GameLoop = function()
{
context.save();
clear();
MoveRain(1);
DrawRain();
context.restore();
gLoop = setTimeout(GameLoop, 10);
}
function loadGame()
{
canvas = document.getElementById("gameCanvas");
context = canvas.getContext("2d");
canvas.width = canvasWidth;
canvas.height = canvasHeight;
GameLoop();
}
</script>
</head>
<body onload="loadGame();">
<canvas id="gameCanvas"></canvas>
</body>
</html>
I have distilled your example down to this:
http://jsfiddle.net/sPm3b/6/
And it works very fast in Firefox and Chrome.
So we know that the problem lies in the images.
You need to optimize how they are created and loaded. Right now, each clear() creates a new image and waits for it to load! That image should be created only once, in your loadGame() and then reused over and over.
Same exact deal with letterImg in DrawRain(). Move the creation of it to loadGame()
That will probably fix the problem.
EDIT:
like this:
At the top add:
var letterImg = new Image();
var bgImg = new Image();
Then
function loadGame()
{
canvas = document.getElementById("gameCanvas");
context = canvas.getContext("2d");
canvas.width = canvasWidth;
canvas.height = canvasHeight;
bgImg.src = "images/bg.png";
letterImg.src = "images/raindrop.gif";
// optional: wait for them to load here
GameLoop();
}
Then drawRain, for example, would look like this:
var DrawRain = function()
{
for (i = 0; i < 10; i++)
{
thisXPos = rain[i][0];
thisYPos = rain[i][1];
context.drawImage(letterImg, thisXPosX, thisYPos); // letterImg was already constructed, no need to make it again
}
};
In complement to Simon Sarris response. I've used a 'double canvas' technique to avoid screen fickering with heavy canvas.
The way it works is always have 2 version of the canvas, one in DOM, one outside, and always draw on the one which is not in DOM. I use it with a redraw queue.
here's a part of a working code
(...)
clear: function() {
//rotating on 2 canvas, one for draw (outside DOM) one for show
var self = this;
if (null == self.canvasbackup) {
var tmpcanvas = self.canvas.clone(true);
self.canvasbackup = self.canvas;
self.canvas=tmpcanvas;
} else {
var tmpcanvas = self.canvasbackup;
self.canvasbackup = self.canvas;
self.canvas=tmpcanvas;
}
self.ctx = self.canvas[0].getContext('2d');
self.ctx.clearRect( 0, 0, self.options.width, self.options.height );
jQuery.each(self.elements,function(idx,elt){
// custom function: my elements need to know which canvas they depends on
elt.reconnectCanvas(self.canvas,self.ctx);
});
},
inDOM: function() {
var self = this;
if(null==self.canvasbackup) {
//1st time need to get all things in DOM
self.canvas.appendTo(self.div);
self.div.appendTo(self.container);
} else {
// remove current shown canvas
self.connectHuman();
self.canvasbackup.remove();
// loosing some events here...
self.canvas.appendTo(self.div);
// div is already in DOM, we are in redraw
}
},
redraw: function() {
var self = this;
self.clear();
jQuery.each(self.elements,function(idx,elt){
elt.draw();
elt.enddraw();
});
self.inDOM();
}
(...)

Getting image width on image load fails on IE

I have a image resizer function that resize images proportional. On every image load a call this function with image and resize if its width or height is bigger than my max width and max height. I can get img.width and img.height in FF Chrome Opera Safari but IE fails. How can i handle this?
Let me explain with a piece of code.
<img src="images/img01.png" onload="window.onImageLoad(this, 120, 120)" />
function onImageLoad(img, maxWidth, maxHeight) {
var width = img.width; // Problem is in here
var height = img.height // Problem is in here
}
In my highligted lines img.width don't work on IE series.
Any suggestion?
Thanks.
Don't use width and height. Use naturalWidth and naturalHeight instead. These provide the image unscaled pixel dimensions from the image file and will work across browsers.
Man, I was looking for this for 2 days. Thanks.
I'm using jQuery, but it doesnt matter. Problem is related to javascript in IE.
My previous code:
var parentItem = $('<div/>')
.hide(); // sets display to 'none'
var childImage = $('<img/>')
.attr("src", src)
.appendTo(parentItem) // sets parent to image
.load(function(){
alert(this.width); // at this point image is not displayed, because parents display parameter is set to 'none' - IE gives you value '0'
});
This is working in FF, Opera and Safari but no IE. I was getting '0' in IE.
Workaround for me:
var parentItem = $('<div/>')
.hide();
var childImage = $('<img/>')
.attr("src", src)
.load(function(){
alert(this.width); // at this point image css display is NOT 'none' - IE gives you correct value
childImage.appendTo(parentItem); // sets parent to image
});
This is how I solved it (because it's the only js on the site I didn't want to use a library).
var imageElement = document.createElement('img');
imageElement.src = el.href; // taken from a link cuz I want it to work even with no script
imageElement.style.display = 'none';
var imageLoader = new Image();
imageLoader.src = el.href;
imageLoader.onload = function() {
loaderElement.parentElement.removeChild(loaderElement);
imageElement.style.position = 'absolute';
imageElement.style.top = '50%';
imageElement.style.left = '50%';
// here using the imageLoaders size instead of the imageElement..
imageElement.style.marginTop = '-' + (parseInt(imageLoader.height) / 2) + 'px';
imageElement.style.marginLeft = '-' + (parseInt(imageLoader.width) / 2) + 'px';
imageElement.style.display = 'block';
}
It's because IE can't calculate width and height of display: none images. Use visibility: hidden instead.
Try
function onImageLoad(img, maxWidth, maxHeight) {
var width = img.width; // Problem is in here
var height = img.height // Problem is in here
if (height==0 && img.complete){
setTimeOut(function(){onImageLoad(img, maxWidth, maxHeight);},50);
}
}
var screenW = screen.width;
var screenH = screen.height;
//alert( screenW );
function checkFotoWidth( img, maxw )
{
if( maxw==undefined)
maxw = 200;
var imgW = GetImageWidth(img.src);
var imgH = GetImageHeight(img.src);
//alert(GetImageWidth(img.src).toString()); // img.width); // objToString(img));
if (imgW > maxw || (img.style.cursor == "hand" && imgW == maxw))
{
if (imgW > screenW) winW = screenW;
else winW = imgW;
if (imgH > screenH) winH = screenH;
else winH = imgH;
img.width=maxw;
img.style.cursor = "pointer";
img.WinW = winW;
img.WinH = winH;
//alert("winW : " + img.WinW);
img.onclick = function() { openCenteredWindow("Dialogs/ZoomWin.aspx?img=" + this.src, this.WinW, this.WinH, '', 'resizable=1'); }
img.alt = "Klik voor een uitvergroting :: click to enlarge :: klicken Sie um das Bild zu vergrössern";
//alert("adding onclick);
}
}
function GetImageWidth(imgSrc)
{
var img = new Image();
img.src = imgSrc;
return img.width;
}
function GetImageHeight(imgSrc)
{
var img = new Image();
img.src = imgSrc;
return img.height;
}
I'd try this:
function onImageLoad(img, maxWidth, maxHeight) {
var width, height;
if ('currentStyle' in img) {
width = img.currentStyle.width;
height = img.currentStyle.height;
}
else {
width = img.width;
height = img.height;
}
// whatever
}
edit — and apparently if I were to try that I'd learn it doesn't work :-) OK, well "width" and "height" definitely seem to be attributes of <img> elements as far as IE is concerned. Maybe the problem is that the "load" event is firing for the element at the wrong time. To check whether that's the case, I would then try this:
function onImageLoad(img, maxWidth, maxHeight) {
var width, height;
var i = new Image();
i.onload = function() {
width = i.width; height = i.height;
// ... stuff you want to do ...
};
i.src = img.href;
}
getDisplay().getImage().setUrl(imgURL);
final Image img = new Image(imgURL);
int w=img.getWidth();
int h=img.getHeight();

Categories

Resources