Code_Ninja
Code_Ninja

Reputation: 1877

Why does the last character keeps on printing repeatedly?

I have written the following to animate the text in a div, but I cannot find how does the last character gets printed repeatedly.

var textClass = $(".first-text");
var text = textClass.text();
textClass.text("");
for (var i in text) {
  $(textClass).animate({
    opacity: 0.25
  }, 200, function() {
    $(textClass).append(text.charAt(i));
  });
}
p:not(:first-child) {
  display: none;
}

p {
  margin: 0 auto;
  font-size: 24px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="animate-text">
  <p class="first-text">HTML</p><br>
</div>

If I try to the alert the value of i or text.charAt(i), I always get the desired output, but when I try to append the same in a div, I always get the same last letter that is printed repeatedly. I cannot find where I am mistaken. I cannot the find the bug in my logic.

If anyone could enlighten me on my mistake in the above code, I would be glad to hear it.

Here is the link to my fiddle where I tried this code.

Thanks in advance.

Upvotes: 4

Views: 125

Answers (4)

Blue
Blue

Reputation: 22911

You've stumbled into a bit of learning when it comes to closures. When i loops through, and eventually gets run inside the function, it's only looking at the last character, because that's what i was overwritten to before the first animate() actually fires.

You can counteract this by manually creating a closure yourself, wrapping it in a function and passing it in, to preserve the variable at the time of the loop.

For more information on closures, check out: What is a 'Closure'?

var textClass = $(".first-text");
var text = textClass.text();
textClass.text("");
for (var i in text) {
  (function (char) {
    $(textClass).animate({
      opacity: 0.25
    }, 200, function() {
      $(textClass).append(text.charAt(char));
    });
  })(i)
}
p:not(:first-child) {
  display: none;
}

p {
  margin: 0 auto;
  font-size: 24px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="animate-text">
  <p class="first-text">HTML</p><br>
</div>

Alternatively, you can use new let or const syntax, which defines i for the scope of the block (Which essentially creates a closure around your if block.)

var textClass = $(".first-text");
var text = textClass.text();
textClass.text("");
for (const i in text) {
  $(textClass).animate({
    opacity: 0.25
  }, 200, function() {
    $(textClass).append(text.charAt(i));
  });
}
p:not(:first-child) {
  display: none;
}

p {
  margin: 0 auto;
  font-size: 24px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="animate-text">
  <p class="first-text">HTML</p><br>
</div>

Upvotes: 6

jsstackguru
jsstackguru

Reputation: 74

It seems to have a variable declaration in your script.

var textClass = $(".first-text");
var text = textClass.text();
textClass.text("");
for (const i in text){
  $(textClass).animate({
    opacity: 0.25
  }, 200, function(){
    $(textClass).append(text.charAt(i));
  });
}

Please review the following JSFiddle link. http://jsfiddle.net/tp3juw54/19/

Upvotes: 1

Mamun
Mamun

Reputation: 68923

You can either create a closure or use let or const to declare the variable i inside for loop which will preserve the current value of i in each iteration:

var textClass = $(".first-text");
var text = textClass.text();
textClass.text("");
for (const i in text) {
  $(textClass).animate({
    opacity: 0.25
  }, 200, function() {
    $(textClass).append(text.charAt(i));
  });
}
p:not(:first-child) {
  display: none;
}

p {
  margin: 0 auto;
  font-size: 24px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="animate-text">
  <p class="first-text">HTML</p><br>
</div>

Upvotes: 2

mplungjan
mplungjan

Reputation: 178350

Using the let and const instead of var you get a better scoping and do not need to create a closure. Also no need to keep doing $(textClass) - you can cache the object

const $textClass = $(".first-text");
const text = $textClass.text();
$textClass.text("");
for (let i in text) {
  $textClass.animate({
    opacity: 0.25
  }, 200, function() {
     $textClass.append(text.charAt(i));
  });
}
p:not(:first-child) {
  display: none;
}

p {
  margin: 0 auto;
  font-size: 24px;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="animate-text">
  <p class="first-text">HTML</p><br>
</div>

Upvotes: 1

Related Questions