Typing and deleting letters of a word in the JS

Hi there codeispoetry,

was my effort barking up the wrong tree? :wonky:

coothead

2 Likes

No, not at all. Whenever I post I always read all the solutions to get insight, but since I had initially posted something, which @PaulOB refined in multiple stages, including getting the end result. so I started analyzing that. I have also downloaded your code into my text editor and was attempting to understand that in small bytes.

1 Like

Thanks Paul that’s great. :slight_smile: I’ve put the updated code into a new codepen.

Yes I even find that with some complicated CSS layouts. While you are developing it it all seems familiar but a few weeks later you wonder what and why you were doing it.:slight_smile:

I have commented my code, it may help…

<script>
(function( d ) {
   'use strict';
/* var c and i count respectively the letters of the word to be typed and then removed */
	var c = 0, i = 0, st, st1,speed = 350,
	 text = 'The Quick Brown Fox Jumps Over The Lazy Dog',
    con = d.querySelector( '#content' ),
 /* the text in the paragraph is for js disabled visitors */
    par = con.querySelector( 'p' );

/* remove all the text from the HTML paragraph */    
    par.textContent = '';
/* function to type each word the var text */
   function typing( c ) {	
 /* add (type) each letter of the leading word to the HTML paragraph */
      par.textContent += text.charAt( c );
 /* check for space after or the end of the letter */
      if ( ( text.charAt( c ) === ' ' ) || ( text.charAt( c ) === '') ){
 /* remove the word from the text */
             text = text.replace( par.textContent,'' ); 
 /* stop the search for the word */            
             clearTimeout( st ); 
 /* function to remove word (type backwards)  */     
             /* return not required */ removeText( i, c );
        }
/* search for next letter of the word */
      else {
           c ++;
    	     st = setTimeout( 
            function(){
              typing( c );
            }, speed );
          }         
	    }

function removeText( i, c ) { 

   if ( i <= c ) {  
/* remove the first letter of the word */
        par.textContent = 
        par.textContent.substring( 1 );
        i ++;
        st1 = setTimeout( 
          function(){ 
            removeText( i, c ); 
          }, speed );
       }

   else {
        clearTimeout( st1 ); 
        i = 0; 
        c = 0; 
/* repeat the  whole process go to the next word */
        typing( c );
       }
/* shut down the script */
    if ( c === text.length) {
         /* clearTimeout( st ); maybe not required */
         /* clearTimeout( st1 );  maybe not required */
/* this is just a little unessential addition that hides the container */
         con.classList.add( 'remove-container' );
         return;
       }
     }
/* initiate the typing sequence */
   typing( c );

}( document ));
</script>  

coothead

2 Likes

Thanks to your suggestion of using the same variable going up and down I have managed to reduce the code in half and use the same piece of code to add and delete the letters.

This is much simpler and more concise :slight_smile:

function textLoop() {
    myElement.innerHTML = convertArray.slice(0, pos).join("");
    pos += increment;
    if (pos === convertArray.length) {
      increment = -increment;
    }
    if (pos === 0) {
      increment = 1;
      counter++;
      if (counter === typeText.length) {
        counter = 0;
      }
      convertArray = typeText[counter].split("");
    }
    timeLoop = setTimeout(textLoop, 100);
  }
  textLoop();
3 Likes

Can we further nest it under if…else statement wherever possible?

Usually the opposite is done, to reduce the amount of if/else instead, to the point of removing them completely.

2 Likes

Hi there @Paul_Wilkins

I am completely lost in this code. Sleep quality is also good. Don’t know why I could not grasp the logical flow in its entirety. would it be possible to break down the logical flowchart into small bytes that can be chewed and digested?

Why what else do you need it to do?

As that was my code (with Paul’s methods added) here goes :slight_smile:

function textLoop() {
  // slice the array and output the letters starting from zero 
  // up to the range defined by the variable pos  
  myElement.innerHTML = convertArray.slice(0, pos).join("");
    pos += increment;
  // if pos equals the length of the array we have output the whole phrase
  // pos is then set to come back downwards by using a negative increment
  // i.e. pos goes from 1 - 15 (or however long the phrase is) and 
  // then  pos goes back down from 15 to zero
  if (pos === convertArray.length) {
    // reverse pos direction so it counts backwards 
    increment = -increment;
    }
  // when we reach here we have finished showing and deleting the first phrase
  // if pos is zero we know that the first phrase is finished so we must increment
  // counter and set up the next phrase in the array ready to slice again
  // counter is incremented so we can get ready to convert 
  // the next phrase into the convertArray variable
// note pos will only be zero when counting backwards as is starts at zero 
// and is incremented before we get here. Once it gets to say 15 
// (the end of the convertArray length) then it counts back down 
//and will only reach zero when increment is negative 
  
    if (pos === 0) {
      increment = 1;
      counter++;
      // when counter equals the original array length we must reset it to zero and start again
      if (counter === typeText.length) {
        counter = 0;
      }
      // this is the next phrase copied into convertArray
      convertArray = typeText[counter].split("");
    }
    timeLoop = setTimeout(textLoop, 100);
  }
  textLoop();

Basically I used slice array so that we don’t touch the original array and nearly all the work is carried out here in this one line.


 myElement.innerHTML = convertArray.slice(0, pos).join("");
    pos += increment;

The variable convertArray contains the first phrase and I slice it starting from the first letter and use pos as the range.

e.g pos = 0
slice(0,pos).
outputs
G

pos = 1
slice(0,pos).
outputs
Ge

pos = 2
slice(0,pos).
outputs
Geo

…and so on until the phrase is complete.

When the length of the array is reached the increment is made negative and we start going backwards from the length of the array to zero.

Once zero on the way back is reached then the counter is incremented to show the next phrase from the array and passed into convertArray once again to begin the process.

1 Like

So If for example, our first phrase is Abraham lincoln then actually, we are printing:
a
ab
abr
abra
.
.
.
.
Abraham lincoln,

but illusionary it appears that we are printing characters one by one?

Yes exactly :slight_smile:

1 Like

Now that I look at the code again we can get rid of convertArray altogether and just use the typeText array instead. That saves another 2 lines of code.

function textLoop() {
    myElement.innerHTML = typeText[counter].split("").slice(0, pos).join("");
    pos += increment;
    if (pos === typeText[counter].length) {
      increment = -increment;
    }
    if (pos === 0) {
      increment = 1;
      counter++;
      if (counter === typeText.length) {
        counter = 0;
      }
    }
    timeLoop = setTimeout(textLoop, 100);
  }
  textLoop();

Whether @Paul_Wilkins will think this line is too complicated is another matter :slight_smile:

myElement.innerHTML = typeText[counter].split("").slice(0, pos).join("");

In your original you were adding one character at a time as you were using innerHTML+= which is fine but you can’t remove a character from the innerHTML by saying -= as you were trying in your first post. You have to output all but the missing character :slight_smile:

1 Like

Looks good PaulOB : )

You could always split this part .split("").slice(0, pos).join(""); into it’s own function. Passing in the string and position.

Would simplify your main function a bit.

Down to preference but you can chain on separate lines. To me it makes it more evident that there are a series of steps involved.

e.g.

typeText[counter]
  .split('')
  .slice(0, pos)
  .join('');

edit: (Something like this??)

const getSegment = (strg, position) => (
  strg
    .split('')
    .slice(0, position)
    .join('')
)

myElement.textContent = getSegment(typeText[counter], pos)

Just a thought, while my machine is rendering :slight_smile:

2 Likes

Cheers makes sense :slight_smile:

2 Likes

I’ve aded that code into another codepen along with a cSS version.

The css version is actually very long winded and of course can’t be changed easily unlike the js version.

1 Like

That’s good man :+1:

2 Likes

Hi there,
I have another code in which HTML has section elements:

I was discussing the same JQuery/JS code on some another post. The whole JS code goes here:

$( document ).ready(function() {

  var sectionNum = 1;
  var length = $("section").length - 1;
  console.log(length);
  $("#prev").addClass("disabled");
  $("#submit").addClass("disabled");
  $("section").not("section:nth-of-type(1)").hide();
  $("section").not("section:nth-of-type(1)").css('transform','translateX(100px)');
   
  $(".button").click(function (evt) {
    var button = evt.target;
    var id = $(button).attr("id");
    if (id === "next") {
      $("#prev").removeClass("disabled");
      if (sectionNum >= length) {
        $(button).addClass("disabled");
        $('#submit').removeClass("disabled");
      }
      if (sectionNum <= length) {
        sectionNum++;
      }
    } else if (id === "prev") {
      $("#next").removeClass("disabled");
      $('#submit').addClass("disabled");
      if (sectionNum <= 2) {
        $(button).addClass("disabled");
      }
      if (sectionNum > 1) {
        sectionNum--;
      }
    }

    var currentSection = $("section:nth-of-type(" + sectionNum + ")");
    currentSection.fadeIn();
    currentSection.css('transform','translateX(0)');
    currentSection.prevAll('section').css('transform','translateX(-100px)');
    currentSection.nextAll('section').css('transform','translateX(100px)');
    $('section').not(currentSection).hide();
  });

});

The if … else part is:

  $(".button").click(function (evt) {
    var button = evt.target;
    var id = $(button).attr("id");
    if (id === "next") {
      $("#prev").removeClass("disabled");
      if (sectionNum >= length) {
        $(button).addClass("disabled");
        $('#submit').removeClass("disabled");
      }
      if (sectionNum <= length) {
        sectionNum++;
      }
    } else if (id === "prev") {
      $("#next").removeClass("disabled");
      $('#submit').addClass("disabled");
      if (sectionNum <= 2) {
        $(button).addClass("disabled");
      }
      if (sectionNum > 1) {
        sectionNum--;
      }
    }

Is there any scope here to reduce if else part w/o losing simplicity of causality of code flow?

We can consider the question of “which variables change” in the if statement. It only seems to be the sectionNum variable. Because of that, all of the if statement code can be moved out to a suitably named function, assigning the result to sectionNum. The same can occur with the else part of the code too.

Then we only need to consider what information each function needs, which looks to be just the button and the current sectionNum value.

if (id === "next") {
  sectionNum = nextSection(button, sectionNum);
} else if (id === "prev") {
  sectionNum = prevSection(button, sectionNum);
}

Those function names help to give us a top-level understanding of what the code is supposed to do, leaving the details to occur inside of each function, which are basically a copy of what’s already inside of the if statement.

    function nextSection(button, sectionNum) {
      $("#prev").removeClass("disabled");
      const length = $("section").length - 1;
      if (sectionNum >= length) {
        $(button).addClass("disabled");
        $('#submit').removeClass("disabled");
      }
      if (sectionNum <= length) {
        sectionNum++;
      }
      return sectionNum;
    }
    function prevSection(button, sectionNum) {
      $("#next").removeClass("disabled");
      $('#submit').addClass("disabled");
      if (sectionNum <= 2) {
        $(button).addClass("disabled");
      }
      if (sectionNum > 1) {
        sectionNum--;
      }
      return sectionNum;
    }
2 Likes