Create a Typing Speed Effect with VueJS

Chris Ganga
πŸ‘οΈ 2,079 views
πŸ’¬ comments

VueJS is commonly being adopted as a Javascript framework, and many developers are trying it out to see what it can actually do.

In this article, we are going to build a Typing Speed Effect, which is a tiny application small enough to wet your apetite for writing applications with VueJS.

The complete application can be found in this jsfiddle.

Table of Contents

    Project Setup

    Since this project is mainly for getting started with Vue, we will not complicate things by using the Vue CLI. If you are however familiar with the Vue CLI, an equivalent setup will involve typing the the following command.

    vue init simple simple-vue

    But for those of us who want to keep it simple, let's create a directory called vue-typer, and inside it add 2 files (index.html, script.js) with the following content.

    vue-typer/index.html

    <!DOCTYPE html>
    <html lang="en">
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <meta http-equiv="X-UA-Compatible" content="ie=edge">
      <link rel="stylesheet"
            href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0/css/bootstrap.min.css"
            integrity="sha384-Gn5384xqQ1aoWXA+058RXPxPg6fy4IWvTNh0E263XmFcJlSAwiGgFAW/dAiS6JXm"
            crossorigin="anonymous">
      <title>Document</title>
    </head>
    <body>
      <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script>
      <script src="script.js"></script>
    </body>
    </html>

    This is html file with Bootstrap and VueJS linked through CDNs. Next, we'll modify this file a bit, to include a div where Vue will render a template.

    <body>
      <div id="app">
        {{ title }}
      </div>
      /* ... */

    The {{ title }} syntax is where Vue will evaluate it's title state variable. We then go ahead and instantiate the VueJS application in the vue-typer/script.js.

    vue-typer/script.js

    new Vue({
      el: '#app',
      data: {
        title: 'Vue Typer'
      }
    })

    This is how a basic Vue instance is created. It takes in an object with a some properties, but we'll cover them as we go. In this instance, we define the el property, which represent the dom element the vue instance will affect, and a data property which represents state variables for the vue instance.

    You can open the index.html file directly in your browser, and this is what will be displayed.

    The title property of the data was evaluated by the {{ title }} template in the html.

    Typing Speed Effect Logic

    We need to make sure we can do the following:-

    1. Provide a random Paragraph.
    2. Enable a user to type in the paragraph.
    3. Have a UI indicator when the user is typing.

    Random Paragraph

    This is the simplest part of the application. We will provide one paragraph as a constant, but these can ideally be pulled from an API. You can find the paragraph in this pastebin

    new Vue({
      el: '#app',
      data: {
        title: 'Vue Typer',
        originalParagrapgh: PARAGRAPH
      }
    });
    
    const PARAGRAPH = `NIH (Not Invented Here) isn’t a 4-letter word. We’re all presumably programmers because we like programming....`

    Make sure you paste in the whole paragraph from the pastebin, oh, and read it too.

    Let's update the html to reflect the new paragraph

    <!--  -->
    <div id="app" class="container mt-2">
        <h1>{{ title }}</h1>
        <div class="row">
          <div class="col-8">
            <div class="paragraph">
              {{ originalText }}
            </div>
            <div class="typer"></div>
          </div>
        </div>
    </div>
    <!--  -->

    Above is standard html and bootstrap. The mt-2 class is Bootstrap 4's spacing feature, and refers to the following class.

    .mt-1 {
      margin-top: 1 !important;
    }

    You can read more about this here: bootstrap spacing.

    Refreshing the browser should give this:

    Typing Speed Logic

    This is the quite not so straight forward part of this article.

    We first of all need a TextArea so that we can type. Edit the html

    <div id="app" class="container mt-2">
      <h1>{{ title }}</h1>
        <div class="row">
          <div class="col-8">
            <div class="paragraph">
              {{ originalText }}
            </div>
            <div class="typer mt-3">
              <textarea class="form-control"
                        rows="10"
                        placeholder="start typing here"></textarea>
            </div>
          </div>
      </div>
     </div>

    We've added a textarea. Refreshing the browser

    Now, we can focus on the Logic. Here's the approach we'll take.

    1. The HTML within the paragraph div needs to be dynamic. We therefore need to find a way for vue to keep editing the actual html whenever we type.
    2. When we type in, we compare the text we've typed in with the original text. If we have a mismatch, we capture the index, and add in html that represents a typo, or an error in the typing.
    3. These errors and valid UI will are best represented by span. When the user types in correctly, the original text turns green, and when the user types in a typo, it turns red. This can be achieved with css classes.

    Add the following CSS within the head tag of our html file

    <!-- -->
    <style>
        .correct {
          color: rgb(63,81,181);
          font-size: 22px;
        }
    
        .typo {
          color: #f00;
          font-size: 22px;
        }
      </style>
      <title>Vue Typer</title>
     <!-- -->

    We will apply these two classes when typing whenever we have either a correct input or a typo.

    Next, we will render the original text as HTML instead of accessing it via the syntax {{ originalText }}. We are doing this since we have to edit the html directly when typing.

    Vue uses v-html attribute to render html. Since this particular html will involve a lot of logic, it would make sense for it to be a Computed Property.

    Update the VueJS instance

    // ...
    new Vue({
      el: '#app',
      data: {
        title: 'Vue Typer',
        originalText: PARAGRAPH
      },
      computed: {
        outputHTML: function() {
          let html = `<span class="correct">This is correct</span>`;
          html += `<span class="typo">This is wrong </span>`;
    
          return html;
        }
      }
    });

    Above we've added a ComputedProperty called outputHTML, which generates a html. We then add the two CSS classes we added earlier, so that we can see them in the browser. The last change is made on paragraph div in the index.html

    <!-- -->
    <div class="paragraph" v-html="outputHTML">
    </div>
    <!-- -->

    This is what is displayed in the browser.

    The idea now, is to change the HTML here to reflect what we are typing.

    The logic for comparing what is typed and the original involves comapring the values at each index. For instance.

    If originalText is Scotch on the Rocks, and the user starts typing, every time they type in a letter, we check the length of what is typed, and use that as an index to identify if what was typed is correct.

    So, typing Sc which has a length of 2, will compare what is typed to Scotch on the Rocks[1], which is the second index(length of what is typed), and if they don't match, will mark that index as the starting point of the error.

    To start us of, we will add two data properties:

    • typedText: which will hold the typed in value. This will be a two way binding.
    • typoIndex: which will represent the index which the typo began.
    const PARAGRAPH = `NIH (Not Invented Here) isn’t a 4-letter word. ....`;
    // ...
    new Vue({
      el: '#app',
      data: {
        // ...
        typedText: '',
        typoIndex: -1
      },
      computed: {
           // ...
        }
      }
    });

    Of course, when we start, the typoIndex is -1 since we do not have a type. We then need to bind the typedText to <text-area> so that we have the real time value within the vue instance as we type.

    <!-- -->
    <div class="typer mt-3">
      <textarea class="form-control"
                id="exampleFormControlTextarea1"
                rows="10"
                placeholder="start typing here"
                v-model="typedText"></textarea>
    </div>
    <!-- -->

    We've added v-model property that enables two way binding for VueJS applications. v-model="typedText".

    Next, since we need to know the value that has been typed, we will add a Watch Property to the Vue instance so that we get the value as it changes. We will add the logic we discussed earlier to the watch function,

    // const PARAGRAPH
    new Vue({
      el: '#app',
      // ....
      computed: {
         outputHTML: function() {}
      },
      watch: {
        typedText: function(value) {
          for (let i = 0; i < value.length; i++) {
            if (value[i] !== this.originalText[i]) {
              this.typoIndex = i;
              break;
            }
            this.typoIndex = -1;
          }
        }
      }
    });
    

    The watcher takes in a key property which is the data property (state variable) we are watching. It's value is function which takes in one parameter, representing the current value of the variable.

    We then loop through each value of the typed in text, and when we get that the value at any index is not equal to the value at the originalText, we mark it as a typo index. This typoIndex is where we will start the error span.

    The next logic is the outputHTML computed property. Update the outputHTML computed code to this.

    // ....
    
    computed: {
        outputHTML: function() {
          let newHTML = '<span class="correct">'
          if (this.typoIndex === -1) {
            // we do not have a typo index
            newHTML += this.originalText.substr(0, this.typedText.length)
            newHTML += '</span>'
            newHTML += this.originalText.substr(this.typedText.length)
    
            return newHTML
          }
    
          // else we have a typo index
          newHTML += this.originalText.substr(0, this.typoIndex)
          newHTML += '</span>'
          newHTML += '<span class="typo">'
          newHTML += this.originalText.substring(this.typoIndex, this.typedText.length)
          newHTML += '</span>'
          newHTML += this.originalText.substr(this.typedText.length)
    
          return newHTML
        }
      },
    
    /// ...

    Let's walk through the logic:

    1. A starting HTML would look something like this: <span class="correct"></span>NIH(Not Invented....". We haven't typed in anything yet, so the "correct" class is just on an empty span.
    2. If the typoIndex is -1, it's original value, it means we do not have a typo, so we our html needs to be <span class="correct">NIH(Not Invented</span> Here.... where here... is the rest of the Original Text. We use Strings.substr, which usually takes in the start index, and the length to get a substring. If the last parameter is not provided, it returns the rest of the original string.
    3. If typoIndex is not equal to -1, it means we have a typo somewhere, and the html needs to be. <span class="correct>NIH</span><span class="typo">(Not Invented)</span> the rest of the html"

    Notice that when computing the typo span content, we use String.substring, which takes in an endIndex instead of length, since we have the index based on what we are typing.

    You can refresh the browser and start typing.

    Conclusion

    While this article did not cover the usual getting started side of VueJS, like methods, it enables you quickly get your hands dirty and see how simple Vue can be used within your application.

    I hope you enjoyed reading. As a bonus, you could add a Timer, and disable everything when the timer ends.

    Happy Coding