I have an HTML file field for selecting multiple images for upload. I want to get the clientWidth
and clientHeight
of the first image.
I used jQuery to create image element of the images. I appended the image element to the div
where the images are supposed to appear. I tried to find the clientWidth
and clientHeight
of the image but both the clientWidth
and clientHeight
of the image were 0
. On the other hand, naturalWidth
and naturalHeight
were correctly determined, How can I get the correct clientWidth
and clientHeight
of the image given that its element was created through jQuery?
$('#image-upload').change(function(event){
const files = this.files;
if (!files.length) return;
let imageElement = '';
$.each(files, function (index, file){
const reader = new FileReader();
reader.onload = function (event){
let imgSrc = event.target.result;
imageElement = $('<img>')
.attr('src', imgSrc)
.addClass('image')
.css({width: '100%', 'max-width': '100%', display: 'block'});
$('#div-container').append(imageElement);
if(index === 0){
$('.image').on('load', function () {
let image = document.getElementsByClassName('image')[0];
let naturalWidth = image.naturalWidth; //got correct width
let naturalHeight = image.naturalHeight; // got correct height
let renderedWidth = image.clientWidth; //gives 0
let renderedHeight = image.clientHeight; //gives 0
});
}
};
reader.readAsDataURL(file);
});
});
There is an obvious logical issue in your code: you do not have a good understanding of the concept of asynchrony. In your example, there are two asynchronous operations that are nested within each other:
$.each(files, function (index, file) { // sync
const reader = new FileReader();
reader.onload = function (event) { // async
let imgSrc = event.target.result;
imageElement = $('<img>')
.attr('src', imgSrc)
.addClass('image')
.css({width: '100%', 'max-width': '100%', display: 'block'});
$('#div-container').append(imageElement);
if(index === 0){
$('.image').on('load', function () { // async
let image = document.getElementsByClassName('image')[0];
let naturalWidth = image.naturalWidth; //got correct width
let naturalHeight = image.naturalHeight; // got correct height
let renderedWidth = image.clientWidth; //gives 0
let renderedHeight = image.clientHeight; //gives 0
});
}
};
reader.readAsDataURL(file);
}
);
Let me describe to you the general process of how the error occurs:
Select multiple images.
Iterate over the list of image files; this is synchronous.
Read the contents of each image file; this is asynchronous.
This is the first issue: due to asynchrony, it is possible that the second image in the list triggers the onload
event first and is added to the DOM as the first image element.
Then comes the second piece of erroneous code: you retrieve all the image elements that already exist in the DOM and add load
event listeners to them.
$('.image').on('load', function () {
//...
}
You cannot guarantee that there is only the image element corresponding to the first image in the list in the DOM at this point. If other images finish loading first and trigger the load
event while the first image is still loading, you will get incorrect clientWidth
and clientHeight
values.
To solve these problems, you can collect the contents of all the images and explicitly execute the corresponding code only after the first image element has finished loading:
$("#image-upload").change(function (event) {
const files = this.files;
if (!files.length) return;
const imageElementsHTML = [];
$.each(files, function (index, file) {
const reader = new FileReader();
reader.onload = function (event) {
let imgSrc = event.target.result;
imageElementsHTML[index] = $("<img>")
.attr("src", imgSrc)
.addClass("image")
.css({ width: "100%", "max-width": "100%", display: "block" });
if (imageElementsHTML.filter(Boolean).length === files.length) {
console.log(imageElementsHTML);
$("#div-container").append(imageElementsHTML);
$("#div-container .image:first").on("load", (event) =>
console.log(getImageDimensions(event.target))
);
}
};
reader.readAsDataURL(file);
});
function getImageDimensions(image) {
let naturalWidth = image.naturalWidth;
let naturalHeight = image.naturalHeight;
let renderedWidth = image.clientWidth;
let renderedHeight = image.clientHeight;
return { naturalWidth, naturalHeight, renderedWidth, renderedHeight };
}
});