The problem is breaking my mind. Can someone help me?
In the <script>
tag in my html file I have this:
window.ondragover = function(e){return false;}
window.ondragenter = function(e){return false;}
window.ondrop = function(e){
var files = e.target.files || e.dataTransfer.files;
for (var i = 0, file; file = files[i];i++){
var img = document.createElement('img');
img.height = 200;
img.width = 200;
img.style.background = 'grey';
document.body.appendChild(img);
var reader = new FileReader();
reader.onload = function(){
img.src = reader.result;
}
reader.readAsDataURL(file);
}
return false;
}
but when I drop several image files on the browser, only the last image file is loaded and displayed in the last img element, others stay grey.
The problem is breaking my mind. Can someone help me?
In the <script>
tag in my html file I have this:
window.ondragover = function(e){return false;}
window.ondragenter = function(e){return false;}
window.ondrop = function(e){
var files = e.target.files || e.dataTransfer.files;
for (var i = 0, file; file = files[i];i++){
var img = document.createElement('img');
img.height = 200;
img.width = 200;
img.style.background = 'grey';
document.body.appendChild(img);
var reader = new FileReader();
reader.onload = function(){
img.src = reader.result;
}
reader.readAsDataURL(file);
}
return false;
}
but when I drop several image files on the browser, only the last image file is loaded and displayed in the last img element, others stay grey.
Share Improve this question edited Jul 10, 2017 at 14:09 msanford 12.3k13 gold badges71 silver badges98 bronze badges asked Jul 10, 2017 at 13:04 Stas ShepelevStas Shepelev 1651 silver badge12 bronze badges 2-
1
Have a feeling this is going to be due to your use of
img
within the loop. Sincereader.onload
is async, the for loop has already pleted andimg
points to the last one – chazsolo Commented Jul 10, 2017 at 13:15 -
in the 1st loop
img
is asigned to a new element and then itssrc
is asigned toresults
of newreader
that was created in the same loop. next loop:img
var is reasigned to a new element and itssrc
is asigned to theresult
of another newreader
. is it wrong? – Stas Shepelev Commented Jul 11, 2017 at 4:09
1 Answer
Reset to default 11As @chazsolo mentioned:
Have a feeling this is going to be due to your use of img within the loop. Since reader.onload is async, the for loop has already pleted and img points to the last one
You can fix this by using let
instead of var
within the loop (let - MDN). This will give each img
and reader
a block scope within the loop, allowing the async reader method to still access the actual value from that specific loop run.
window.ondragover = function(e){return false;}
window.ondragenter = function(e){return false;}
window.ondrop = function(e){
var files = e.target.files || e.dataTransfer.files;
debugger;
for (var i = 0, file; file = files[i];i++){
let img = document.createElement('img');
img.height = 200;
img.width = 200;
img.style.background = 'grey';
document.body.appendChild(img);
let reader = new FileReader();
reader.onload = function(){
img.src = reader.result;
}
reader.readAsDataURL(file);
}
return false;
}
Update: var vs let
So why is it not working as suspected with var
?
I try to explain the difference of let
and var
with a few practical examples.
Variable declarations, wherever they occur, are processed before any code is executed.
This leads us to the following example (don't mind the error in the end, which is produced by the snipped plugin):
Declaration with var
/**
In this example, 'a' is declared after it is used. This doesn't matter, as the
declaration of 'a' will be processed before running the code. This means a is
initialized with 'undefined' but is valid. Also the scope of a lies within the
execution context, that's why it is even available outside of the loop.
**/
console.log("---------");
console.log("Example Declaration var");
console.log("---------");
for (var i = 0; i < 2; i++) {
console.log(a); // a is declared but undefined on the 1st run, afterwards it get redeclared and owns the value from the last run.
var a = i;
}
console.log(a); // a is even available out here as still same execution context.
We see, that on every re declaration of a
the value of the a
before, is kept. It is not a new "instance".
So what's happening if we use a async function within the loop?
Async function with var
/**
This example shows you the effects, if you use a async function within a loop.
As the loop will be executed way under 100 miliseconds (which is the time out
of the async function), c will have the same value for all exections of the
async mehtod, which is the value assigned by the last run of the loop.
**/
console.log("---------");
console.log("Example effects async var");
console.log("---------");
for (var i = 0; i < 2; i++) {
var c = i;
setTimeout(function() {
console.log(c); //var does redeclare, therefor c will be modified by the next loop until the last loop.
}, 100);
}
Exactly, always the same output (adapted to your problem, always the same img element and file)
Let's see what's happening with let
Declaration with let
/**
In this example, 'b' is declared after it is used with let. let will be processed
during runtime. This means 'b' will not be declared when used. This is an invalid
state. let will give a strict context within the loop. It will be not available
outside. let has a similar behavior as a declaration in Java.
**/
console.log("---------");
console.log("Example Declaration let");
console.log("---------");
for (var i = 0; i < 2; i++) {
try {
console.log(b); //b is not declared yet => exception
} catch (ex) {
console.log("Exception in loop=" + ex);
}
let b = i;
console.log("Now b is declared:"+b);
}
try {
console.log(b); // b is not available out here as the scope of b is only the for loop. => exception
} catch (ex) {
console.log("Exception outside loop=" + ex);
}
console.log("Done");
A lots of exceptions are thrown, as let
has a more specific scope. Which leads to more intentional coding.
Finally, we see what happens when we use let
and a async function within the loop.
Async function with let
/**
This example shows you the effects, if you use a async function within a loop.
As the loop will be executed way under 100 milliseconds (which is the time out
of the async function). let declares a new variable for each run of the loop,
which will be untouched by uping runs.
**/
console.log("---------");
console.log("Example effects async let");
console.log("---------");
for (var i = 0; i < 2; i++) {
let d = i;
setTimeout(function() {
console.log(d); //let does not redeclare, therefor d will not be modified by the next loop
}, 100);
}
Conclusion
In your example, you always end up with the last assigned img
element and the last assigned file
. Your doing the same operation as many times as you have file in your array for the only the last file.