Problem
The loop finishes executing before the timeouts callbacks have started, so when they are executed the i
variable is len - 1
for all of them.
ES6 Approach
If you are working with ES6
you can just change the var
to a let
.
So it would look like this:
for(let i = 0; i < len; i++) {
setTimeout(function() {
text[i].classList.remove("copied");
}, 1000);
}
ES5 Approach
If your application is only using ES5
then the only to solve your problem is by creating a new scope and insert the setTimeout
there.
Creating a new scope with a catch block
for(var i = 0; i < len; i++) {
try { throw i; } catch(i) {
setTimeout(function() {
text[i].classList.remove("copied");
}, 1000);
}
}
Creating a new scope with an IIFE
for(var i = 0; i < len; i++) {
(function(i) {
setTimeout(function() {
text[i].classList.remove("copied");
}, 1000);
})();
}
Creating a new scope with a function call
for(var i = 0; i < len; i++) {
loopCallBack(i);
}
function loopCallBack(i) {
setTimeout(function() {
text[i].classList.remove("copied");
}, 1000);
}
Full ES5 Example
function copyLink() {
var text = document.getElementsByClassName("text"),
len = text.length;
for (var i = 0; i < len; i++) {
(function(i) {
text[i].classList.add("copied");
setTimeout(function() {
text[i].classList.remove("copied");
}, 1000);
})(i);
}
}
.copied {
color: red;
}
<p class="link text">Text</p>
<p class="demo text">Some text</p>
<a href="javascript:copyLink()">Click me</a>
A Different Approach
Instead of creating a new scope for each iteration you could just add the for loop in the setTimeout
call-back
function copyLink() {
var text = document.getElementsByClassName("text"),
len = text.length;
for (var i = 0; i < len; i++) {
text[i].classList.add("copied");
}
setTimeout(function() {
for (var i = 0; i < len; i++) {
text[i].classList.remove("copied");
}
}, 1000);
}
.copied {
color: red;
}
<p class="link text">Text</p>
<p class="demo text">Some text</p>
<a href="javascript:copyLink()">Click me</a>