Resizing dynamic images not working more than one time - javascript

I've got a little problem here. I've been trying to do an image gallery with JavaScript but there's something that I got a problem with. I can get the image to resize when the first image load, but as soon as I load another image, it won't resize anymore! Since the user will be able to upload a lot of different size pictures, I really need to make it work.
I've checked for ready-to-use image gallery and such and nothing was doing what I need to do.
Here's my javascript:
function changeCurrentImage(conteneur)
{
var img = conteneur.getElementsByTagName("img");
var imgUrl = img[0].src;
var imgFirstPart = imgUrl.substring(0, imgUrl.lastIndexOf('.') - 9);
var imgLastPart = imgUrl.substring(imgUrl.lastIndexOf('.'));
var currentImg = document.getElementById('currentImage');
currentImg.src = imgFirstPart + "borne" + imgLastPart;
resize(document.getElementById('currentImage'), 375, 655);
}
function resize(img, maxh, maxw) {
var ratio = maxh/maxw;
if (img.height/img.width > ratio){
// height is the problem
if (img.height > maxh){
img.width = Math.round(img.width*(maxh/img.height));
img.height = maxh;
}
} else {
// width is the problem
if (img.width > maxw){
img.height = Math.round(img.height*(maxw/img.width));
img.width = maxw;
}
}
};
Here's the HTML (using ASP.Net Repeater):
<asp:Repeater ID="rptImages" runat="server">
<HeaderTemplate>
</HeaderTemplate>
<ItemTemplate>
<a href="#">
<div id="thumbnailImageContainer1" onclick="changeCurrentImage(this)">
<div id="thumbnailImageContainer2">
<img id="thumbnailImage" src="<%# SiteUrl + Eval("ImageThumbnailPath")%>?rn=<%=Random()%>" alt="Photo" onload="resize(this, 60, 105)" />
</div>
</div>
</a>
</ItemTemplate>
<FooterTemplate>
</FooterTemplate>
</asp:Repeater>

Most likely the image is not yet downloaded so img.height and img.width are not yet there. Technically you don't need to wait till the whole image is downloaded, you can poll the image in a timer until width and height are non-zero. This sounds messy but can be done nicely if you take the time to do it right. (I have an ImageLoader utility I made for this purpose....has only one timer even if it is handling multiple images at once, and calls a callback function when it has the sizes) I have to disagree with Marcel....client side works great for this sort of thing, and can work even if the images are from a source other than your server.
Edit: add ImageLoader utility:
var ImageLoader = {
maxChecks: 1000,
list: [],
intervalHandle : null,
loadImage : function (callback, url, userdata) {
var img = new Image ();
img.src = url;
if (img.width && img.height) {
callback (img.width, img.height, url, 0, userdata);
}
else {
var obj = {image: img, url: url, callback: callback,
checks: 1, userdata: userdata};
var i;
for (i=0; i < this.list.length; i++) {
if (this.list[i] == null)
break;
}
this.list[i] = obj;
if (!this.intervalHandle)
this.intervalHandle = setInterval(this.interval, 30);
}
},
// called by setInterval
interval : function () {
var count = 0;
var list = ImageLoader.list, item;
for (var i=0; i<list.length; i++) {
item = list[i];
if (item != null) {
if (item.image.width && item.image.height) {
item.callback (item.image.width, item.image.height,
item.url, item.checks, item.userdata);
ImageLoader.list[i] = null;
}
else if (item.checks > ImageLoader.maxChecks) {
item.callback (0, 0, item.url, item.checks, item.userdata);
ImageLoader.list[i] = null;
}
else {
count++;
item.checks++;
}
}
}
if (count == 0) {
ImageLoader.list = [];
clearInterval (ImageLoader.intervalHandle);
delete ImageLoader.intervalHandle;
}
}
};
Example usage:
var callback = function (width, height, url, checks, userdata) {
// show stuff in the title
document.title = "w: " + width + ", h:" + height +
", url:" + url + ", checks:" + checks + ", userdata: " + userdata;
var img = document.createElement("IMG");
img.src = url;
// size it to be 100 px wide, and the correct
// height for its aspect ratio
img.style.width = "100px";
img.style.height = ((height/width)*100) + "px";
document.body.appendChild (img);
};
ImageLoader.loadImage (callback,
"http://upload.wikimedia.org/wikipedia/commons/thumb/" +
"1/19/Caerulea3_crop.jpg/800px-Caerulea3_crop.jpg", 1);
ImageLoader.loadImage (callback,
"http://upload.wikimedia.org/wikipedia/commons/thumb/" +
"8/85/Calliphora_sp_Portrait.jpg/402px-Calliphora_sp_Portrait.jpg", 2);

With the way you have your code setup, I would try and call your resize function from an onload event.
function resize() {
var img = document.getElementById('currentImage');
var maxh = 375;
var maxw = 655;
var ratio = maxh/maxw;
if (img.height/img.width > ratio){
// height is the problem
if (img.height > maxh){
img.width = Math.round(img.width*(maxh/img.height));
img.height = maxh;
}
} else {
// width is the problem
if (img.width > maxw){
img.height = Math.round(img.height*(maxw/img.width));
img.width = maxw;
}
}
};
function changeCurrentImage(conteneur)
{
var img = conteneur.getElementsByTagName("img");
img.onload = resize;
var imgUrl = img[0].src;
var imgFirstPart = imgUrl.substring(0, imgUrl.lastIndexOf('.') - 9);
var imgLastPart = imgUrl.substring(imgUrl.lastIndexOf('.'));
var currentImg = document.getElementById('currentImage');
currentImg.src = imgFirstPart + "borne" + imgLastPart;
}
I would play around with that. Maybe use global variables for your maxH/W and image ID(s);

#Comments: No, I can't do that server side since it would refresh the page everytime someone click on a new image. That would be way too bothersome and annoying for the users.
As for the thumbnails, those image are already saved in the appropriate size. Only the big image that shows is about 33% of its size. Since we already have 3 images PER uploaded images, I didn't want to upload a 4th one for each upload, that would take too much server space!
As for the "currentImage", I forgot to add it, so that might be helful lol:
<div id="currentImageContainer">
<div id="currentImageContainer1">
<div id="currentImageContainer2">
<img id="currentImage" src="#" alt="" onload="resize(this, 375, 655)" />
</div>
</div>
</div>
#rob: I'll try the ImageLoader class, that might do the trick.

I found an alternative that is working really well. Instead of changing that IMG width and height, I delete it and create a new one:
function changeCurrentImage(conteneur)
{
var thumbnailImg = conteneur.getElementsByTagName("img");
var thumbnailImgUrl = thumbnailImg[0].src;
var newImgUrl = thumbnailImgUrl.replace("thumbnail", "borne");
var currentImgDiv = document.getElementById('currentImageContainer2');
var currentImg = currentImgDiv.getElementById("currentImage");
if (currentImg != null)
{
currentImgDiv.removeChild(currentImg);
}
var newImg = document.createElement("img");
newImageDiv = document.getElementById('currentImageContainer2');
newImg.id = "currentImage";
newImg.onload = function() {
Resize(newImg, 375, 655);
newImageDiv.appendChild(newImg);
}
newImg.src = newImgUrl;
}
Also, in case people wonder, you MUST put the .onload before the .src when assigning an a new source for an image!

Related

How to detect, that drawing a canvas object is finished?

I have following JS code (found here, on stackoverflow, and a little-bit modded), which resize image on client side using canvas.
function FileListItem(a) {
// Necesary to proper-work of CatchFile function (especially image-resizing).
// Code by Jimmy Wärting (https://github.com/jimmywarting)
a = [].slice.call(Array.isArray(a) ? a : arguments)
for (var c, b = c = a.length, d = !0; b-- && d;) d = a[b] instanceof File
if (!d) throw new TypeError('expected argument to FileList is File or array of File objects')
for (b = (new ClipboardEvent('')).clipboardData || new DataTransfer; c--;) b.items.add(a[c])
return b.files
}
function CatchFile(obj) {
// Based on ResizeImage function.
// Original code by Jimmy Wärting (https://github.com/jimmywarting)
var file = obj.files[0];
// Check that file is image (regex)
var imageReg = /[\/.](gif|jpg|jpeg|tiff|png|bmp)$/i;
if (!file) return
var uploadButtonsDiv = document.getElementById('upload_buttons_area');
// Check, that it is first uploaded file, or not
// If first, draw a div for showing status
var uploadStatusDiv = document.getElementById('upload_status_area');
if (!uploadStatusDiv) {
var uploadStatusDiv = document.createElement('div');
uploadStatusDiv.setAttribute('class', 'upload-status-area');
uploadStatusDiv.setAttribute('id', 'upload_status_area');
uploadButtonsDiv.parentNode.insertBefore(uploadStatusDiv, uploadButtonsDiv.nextSibling);
// Draw sub-div for each input field
var i;
for (i = 0; i < 3; i++) {
var uploadStatus = document.createElement('div');
uploadStatus.setAttribute('class', 'upload-status');
uploadStatus.setAttribute('id', ('upload_status_id_commentfile_set-' + i + '-file'));
uploadStatusDiv.append(uploadStatus);
}
}
var canvasDiv = document.getElementById('canvas-area');
var currField = document.getElementById(obj.id);
var currFieldLabel = document.getElementById(('label_' + obj.id));
// Main image-converting procedure
if (imageReg.test(file.name)) {
file.image().then(img => {
const canvas = document.createElement('canvas')
canvas.setAttribute('id', ('canvas_' + obj.id));
const ctx = canvas.getContext('2d')
const maxWidth = 1600
const maxHeight = 1200
// Calculate new size
const ratio = Math.min(maxWidth / img.width, maxHeight / img.height)
const width = img.width * ratio + .5|0
const height = img.height * ratio + .5|0
// Resize the canvas to the new dimensions
canvas.width = width
canvas.height = height
// Drawing canvas-object is necessary to proper-work
// on mobile browsers.
// In this case, canvas is inserted to hidden div (display: none)
ctx.drawImage(img, 0, 0, width, height)
canvasDiv.appendChild(canvas)
// Get the binary (aka blob)
canvas.toBlob(blob => {
const resizedFile = new File([blob], file.name, file)
const fileList = new FileListItem(resizedFile)
// Temporary remove event listener since
// assigning a new filelist to the input
// will trigger a new change event...
obj.onchange = null
obj.files = fileList
obj.onchange = CatchFile
}, 'image/jpeg', 0.70)
}
)
// If file is image, during conversion show status
function ShowConvertConfirmation() {
if (document.getElementById('canvas_' + obj.id)) {
document.getElementById(('upload_status_' + obj.id)).innerHTML =
'<font color="#4CAF50">Konwertowanie pliku ' + file.name + ' zakończone!</font>';
return true;
}
else {
document.getElementById(('upload_status_' + obj.id)).innerHTML =
'<font color="#4CAF50">Konwertowanie pliku ' + file.name + ' zakończone!</font>';
return false;
}
}
// Loop ShowConvertConfirmation function untill return true (file is converted)
var convertConfirmationLoop = setInterval(function() {
var isConfirmed = ShowConvertConfirmation();
if (!isConfirmed) {
ShowConvertConfirmation();
}
else {
// Break loop
clearInterval(convertConfirmationLoop);
}
}, 2000); // Check every 2000ms
}
// If file is not an image, show status with filename
else {
document.getElementById(('upload_status_' + obj.id)).innerHTML =
'<font color="#4CAF50">Dodano plik ' + file.name + '</font>';
//uploadStatusDiv.append(uploadStatus);
}
}
Canvas is drawn in hidden div:
<div id="canvas-area" style="overflow: hidden; height: 0;"></div>
I am only detect, that div canvas-area is presented and basing on this, JS append another div with status.
Unfortunatelly on some mobile devices (mid-range smartphones), message will be showed before finish of drawing (it is wrong). Due to this, some uploaded images are corrupted or stay in original size.
How to prevent this?
Everything that should happen after the image has loaded, should be executed within the then callback, or called from within it.
It is important to realise that the code that is not within that callback will execute immediately, well before the drawing has completed.

Javascript - if boolean true not working

So, i created the following function to check the file uploaded by user is
1) Image only
2) Size less than maxSize KBs
3) Dimensions less than maxWidth and maxHeight
All else is working fine except that the condition where I check dimensions. The value in dimensions is indeed the correct value but the condition if(dimensions) doesn't run even when dimensions=true.
Is there something I am doing wrong?
var maxThumbnailWidth = '1050';
var maxThumbnailHeight = '700';
var maxThumbnailSize = '60';
function imageFileChecks(file, type) // type here refers to either Image or Banner or Thumbnail
{
var maxSize;
var maxWidth;
var maxHeight;
var dimensions = false;
if (type == 'image') {
maxSize = maxImageSize;
maxWidth = maxImageWidth;
maxHeight = maxImageHeight;
}
if (type == 'banner') {
maxSize = maxBannerSize;
maxWidth = maxBannerWidth;
maxHeight = maxBannerHeight;
}
if (type == 'thumbnail') {
maxSize = maxThumbnailSize;
maxWidth = maxThumbnailWidth;
maxHeight = maxThumbnailHeight;
}
//First check file type.. Allow only images
if (file.type.match('image.*')) {
var size = (file.size / 1024).toFixed(0);
size = parseInt(size);
console.log('size is ' + size + ' and max size is ' + maxSize);
if (size <= maxSize) {
var img = new Image();
img.onload = function() {
var sizes = {
width: this.width,
height: this.height
};
URL.revokeObjectURL(this.src);
//console.log('onload sizes', sizes);
console.log('onload width sizes', sizes.width);
console.log('onload height sizes', sizes.height);
var width = parseInt(sizes.width);
var height = parseInt(sizes.height);
if (width <= maxWidth && height <= maxHeight) {
dimensions = true;
console.log('dimensions = ', dimensions);
}
}
var objectURL = URL.createObjectURL(file);
img.src = objectURL;
if (dimensions) {
alert('here in dimensions true');
sign_request(file, function(response) {
upload(file, response.signed_request, response.url, function() {
imageURL = response.url;
alert('all went well and image uploaded!');
return imageURL;
})
})
} else {
return errorMsg = 'Image dimensions not correct!';
}
} else {
return errorMsg = 'Image size not correct!';
}
} else {
return errorMsg = 'Image Type not correct!';
}
}
<div class="form-group">
<label class="col-md-6 col-xs-12 control-label">Thumbnail</label>
<div class="col-md-6 col-xs-12">
<input type="file" id="thumbnail" class="file" required>
<br>
</div>
</div>
<script type="text/javascript">
document.getElementById('thumbnail').onchange = function() {
var file = document.getElementById('thumbnail').files[0];
if (!file) {
console.log("ji");
return;
}
var type = 'thumbnail';
var thumbnailURL = imageFileChecks(file, type);
}
</script>
This seems like an async issue -- your if(dimensions) statement is running before your img.onload function finishes, in which case dimensions would be equal to false when you get to that part in your code, despite the img.onload function and its logic executing correctly.
You could try nesting the if(dimensions) condition in the img.onload function.
You set your dimension property inside the img.onload callback function.
This will not be executed directly. Then you check the value directly below, which will not be set yet. This is the nature of JavaScript: async functions being queued up to run at some time (example when an image finished loading).
To solve your problem, you need to make the rest of your function execute after img load. This can be done with either callback functions or promises.
I would read up on the asynchronous behavior a bit. Sorry for not providing a link, but should be plenty out there!
#William is right.You can handle it like that
function loadImage(src,callback){
var img = new Image();
img.onload = function() {
if (callback) {
callback(img);
}
}
img.src = src;
}

How to resize and display images in JavaScript

I am attempting to read images from a list of paths (10 items long) returned via an ajax function, resize each one and then display one after the other on the page. However, the below code only displays the first image (resized) and none of the others. I am fairly sure the resizing works as the printed sizes look correct. Below is my code in JavaScript:
// Helper function
function scaleSize(maxW, maxH, currW, currH){
var ratio = currH / currW;
if(currW >= maxW && ratio <= 1) {
currW = maxW;
currH = currW * ratio;
} else if(currH >= maxH) {
currH = maxH;
currW = currH / ratio;
}
return [currW, currH];
}
function get_similar_images(image_name) {
console.log(image_name)
$.ajax({
type: "POST",
contentType: "application/json; charset=utf-8",
url: "/get_similar",
dataType: "json",
async: true,
data: JSON.stringify({name: image_name}),
success: function(data) {
console.log(data);
image_list = data['images']
category = data['category']
for (var i=0; i<image_list.length; i++) {
var img = document.createElement("img");
img.src = "static/products/"+category+"/"+image_list[i];
var actualH;
var actualW;
var newH;
var newW;
img.onload = function(){
actualW = this.width;
actualH = this.height;
console.log(actualW, actualH)
var newSize = scaleSize(300, 300, actualW, actualH);
console.log(newSize)
img.width = newSize[0];
img.height = newSize[1];
document.getElementById('imageDiv').appendChild(img)
};
}
},
error: function(xhr, status, error) {
// console.log(xhr.responseText);
}
})
}
If we simplify your for loop like that :
var img = document.createElement("img");
img.src = image_list[0];
img.onload = function() {
console.log(img);
};
var img = document.createElement("img");
img.src = image_list[1];
img.onload = function() {
console.log(img);
};
You will understand where your mistake comes from. onload is an event, which means it's running asynchronously from your your code. You're updating img value with a new image element (and a new href attribute) before the first load event is triggered. Thus the onload event is actually called after the last update of the img variable, which actually happen when your reach the last loop of your for.
Why ?
Because a for loop doesn't create a new scope for the variable img, so each time you touch the img var, even if you're putting var in front, the original img is updated.
You should then use a function, because a function actually create a new scope for variables that are declared (var myVar) inside it :
function injectAndResize(imageUrl) {
var img = document.createElement("img");
img.src = imageUrl;
var actualH;
var actualW;
var newH;
var newW;
img.onload = function() {
actualW = this.width;
actualH = this.height;
var newSize = scaleSize(300, 300, actualW, actualH);
this.width = newSize[0];
this.height = newSize[1];
document.getElementById('imageDiv').appendChild(img);
};
}
for (var i = 0; i < image_list.length; i++) {
injectAndResize(image_list[i]);
}
There are a few issues in your code.
1) Image width and height are CSS properties and may be undefined in your img.onload handler. Use this.naturalWidth instead.
2) img.src = 'url'; triggers image load. Place it after img.onload.
success: function(data) {
console.log(data);
image_list = data.images;
category = data.category;
for (var i=0; i<image_list.length; i++) {
var img = document.createElement("img");
img.onload = function(){
var actualW = this.naturalWidth;
var actualH = this.naturalHeight;
console.log(actualW, actualH)
var newSize = scaleSize(300, 300, actualW, actualH);
console.log(newSize)
this.width = newSize[0];
this.height = newSize[1];
//also think what to do with images which are already there
//probably remove
document.getElementById('imageDiv').appendChild(this)
};//img.onload
img.src = "static/products/"+category+"/"+image_list[i];
}//for
},//success

Getting width of image (by URL) outside function w/ JavaScript

I'm trying to get the width of two images based on their URLs (so, actual image size). I'm able to do that in such a way so as to pop up an alert based on the results, but anything outside of the actual function results in an undefined value for the widths of the two images.
var w1 = "";
var w2 = "";
var img1 = new Image();
img1.onload = function() { w1 = this.width;};
img1.src = "URL1";
var img2 = new Image();
img2.onload = function() { w2 = this.width;};
img2.src = "URL2";
alert (w1 + " and " + w2);
How do I use w1 and w2 outside of the functions that set them to this.width?
Ideally I'd like to compare them and later on use the larger one. But I can't even get them to show with their correct values in the alert (above).
Thanks in advance!
Adendum:
var img1 = new Image();
var img2 = new Image();
var one, two;
img1.src = "img1.jpg";
img2.src = "img2.jpg";
img1.onload = function() {img2.onload = function() {compareWidth(img1.width, img2.width);}}
var finalchoice;
var compareWidth = function (width1 ,width2){
if(width1 > width2){
finalchoice = img1.src;
alert(finalchoice);
return finalchoice;
}
else{
finalchoice = img2.src;
alert(finalchoice);
return finalchoice;
}
}
Idea being then to use final choice later in the .js file.
The alert (w1 + " and " + w2);
statment is executed before the image is loaded that's why the result is displaying undefined.
To compare the width of two images you can use this code.
<!DOCTYPE html>
<html>
<head>
<body>
</body>
<script>
var img1 = new Image();
img1.src = "w3javascript.gif";
img1.onload = function() {
document.body.appendChild(img1); //this is optional you can remove it
compareWidth(this.width);
}
var img2 = new Image();
img2.src = "author.jpg";
img2.onload = function() {
document.body.appendChild(img2); //this is optional you can remove it
compareWidth(this.width);
}
var w1;
var compareWidth = function (width){
//here you can do operation with width and height
if(typeof w1 == 'undefined'){
w1 = width;
}else{
if(w1 < width){
alert('do one thing');
}else{
alert('do other thing');
}
}
}
</script>
</html>
i would recommend using jQuery...
Just load jQuery ... > like <script src="{jquery-lib-path}"></script>
and do something like
var imgWidth = img.width():

Only make certain images fluid

This code is from here http://unstoppablerobotninja.com/entry/fluid-images
It takes all the images from a page and makes them fluid. It works great and i am sure many of you know it already.
My question is how can i apply this code only to certain images on the page, either through an ID or through a CLASS. something like this:
<img alt="" src="fluid_image.png" id="makeitfuild"/>
I don't want all my pictures to be fluid but still i want to use the code to certain images.
Please help as i don't know what i need to modify in the code
var imgSizer = {
Config : {
imgCache : []
,spacer : "/path/to/your/spacer.gif"
}
,collate : function(aScope) {
var isOldIE = (document.all && !window.opera && !window.XDomainRequest) ? 1 : 0;
if (isOldIE && document.getElementsByTagName) {
var c = imgSizer;
var imgCache = c.Config.imgCache;
var images = (aScope && aScope.length) ? aScope : document.getElementsByTagName("img");
for (var i = 0; i < images.length; i++) {
images.origWidth = images.offsetWidth;
images.origHeight = images.offsetHeight;
imgCache.push(images);
c.ieAlpha(images);
images.style.width = "100%";
}
if (imgCache.length) {
c.resize(function() {
for (var i = 0; i < imgCache.length; i++) {
var ratio = (imgCache.offsetWidth / imgCache.origWidth);
imgCache.style.height = (imgCache.origHeight * ratio) + "px";
}
});
}
}
}
,ieAlpha : function(img) {
var c = imgSizer;
if (img.oldSrc) {
img.src = img.oldSrc;
}
var src = img.src;
img.style.width = img.offsetWidth + "px";
img.style.height = img.offsetHeight + "px";
img.style.filter = "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + src + "', sizingMethod='scale')"
img.oldSrc = src;
img.src = c.Config.spacer;
}
// Ghettomodified version of Simon Willison's addLoadEvent() -- http://simonwillison.net/2004/May/26/addLoadEvent/
,resize : function(func) {
var oldonresize = window.onresize;
if (typeof window.onresize != 'function') {
window.onresize = func;
} else {
window.onresize = function() {
if (oldonresize) {
oldonresize();
}
func();
}
}
}
}
You'll want to change the following line:
var images = (aScope && aScope.length) ? aScope : document.getElementsByTagName("img");
to something that will get your images by a class name. There are a few ways to do that: you can use jQuery, in which case it simply becomes
var images = (aScope && aScope.length) ? aScope : $("#myClassName");
or, you can look up any of the questions about how to get elements simply by class using only javascript (How to getElementByClass instead of GetElementById with Javascript? and How to Get Element By Class in JavaScript? both quickly jump to mind).

Categories

Resources