Search code examples
javascripttestingtyping

How can i make my typing test start once a key is pressed, rather than having to press start button->click textbox->type?


My typing test is based on a random quote API I found on GitHub, and I want to make a test where the timer/test starts on the first key stroke.

Repository link: https://github.com/Tbscuddles/Tbswpmtest


Solution

  • When the test starts, listen for the keydown event on the <textarea> and start the timer:

    userInput.addEventListener('keydown', timeReduce, { once: true });
    

    Use once: true to only fire this once.

    Be sure to remove this listener in resetTest() too, in case it was never fired:

    userInput.removeEventListener('keydown', timeReduce, { once: true });
    

    Putting it all together:

    //Random quotes api
    const quoteApiUrl = "https://api.quotable.io/random?minLength=80&maxLength=100";
    const quoteSection = document.getElementById("quote");
    const userInput = document.getElementById("quote-input");
    
    let quote = "";
    let time = 60;
    let timer = "";
    let mistakes = 0;
    
    //Display random quotes
    const renderNewQuote = async() => {
      //Fetch content from quote api url
      const response = await fetch(quoteApiUrl);
      let data = await response.json();
      quote = data.content;
    
      //Array of chars in quote
      let arr = quote.split("").map((value) => {
        return "<span class='quote-chars'>" + value + "</span>";
      });
      quoteSection.innerHTML += arr.join("");
    };
    
    //Logic to compare input words with quote
    userInput.addEventListener("input", () => {
      let quoteChars = document.querySelectorAll(".quote-chars");
      quoteChars = Array.from(quoteChars);
    
      //Array of user input chars
      let userInputChars = userInput.value.split("");
      //Loop through each char in quote
      quoteChars.forEach((char, index) => {
        //Check chars with quote chars
        if (char.innerText == userInputChars[index]) {
          char.classList.add("success");
        }
        //If user hasn't entered anything or backspaced
        else if (userInputChars[index] == null) {
          if (char.classList.contains("success")) {
            char.classList.remove("success");
          } else {
            char.classList.remove("fail");
          }
        }
        //if user entered wrong char
        else {
          if (!char.classList.contains("fail")) {
            //increment and displaying mistakes
            mistakes++;
            char.classList.add("fail");
          }
          document.getElementById("mistakes").innerText = mistakes;
        }
    
        //Return true if all chars are correct
        let check = quoteChars.every((element) => {
          return element.classList.contains("success");
        });
    
        //End test if all chars are correct
        if (check) {
          displayResult();
        }
        // or end test if atleast 80% of char are correct
      });
    
    });
    
    //Update timer
    function updateTimer() {
      if (time == 0) {
        //End test if reaches 0
        displayResult();
      } else {
        document.getElementById("timer").innerText = --time + "s";
      }
    }
    
    //Set timer of the test
    const timeReduce = () => {
      time = 60;
      timer = setInterval(updateTimer, 1000);
    };
    
    //End test
    const displayResult = () => {
      //Display the result
      document.querySelector(".result").style.display = "block";
      clearInterval(timer);
      document.getElementById("stop-test").style.display = "none";
      userInput.disabled = true;
      let timeTaken = 1;
      if (time != 0) {
        timeTaken = (60 - time) / 100;
      }
      document.getElementById("wpm").innerText = (userInput.value.length / 5 / timeTaken).toFixed(2) + "wpm";
      document.getElementById("accuracy").innerText = Math.round(((userInput.value.length - mistakes) / userInput.value.length) * 100) + "%";
      wpmScore = (userInput.value.length / 5 / timeTaken).toFixed(2);
      document.getElementById("wpm").innerText = wpmScore + "wpm";
      document.getElementById("accuracy").innerText = Math.round(((userInput.value.length - mistakes) / userInput.value.length) * 100) + "%";
      document.getElementById("wpm-score").innerText = wpmScore;
    
    };
    
    //Start test
    const startTest = () => {
      mistakes = 0;
      timer = "60";
      userInput.disabled = false;
      document.getElementById("start-test").style.display = "none";
      document.getElementById("stop-test").style.display = "block";
      document.getElementById("reset-test").disabled = false;
      userInput.addEventListener('keydown', timeReduce, { once: true });
    }
    
    //Reset test
    function resetTest() {
      userInput.removeEventListener('keydown', timeReduce, { once: true });
      clearInterval(timer);
      time = 60;
      mistakes = 0;
      userInput.value = "";
      quoteSection.innerHTML = "";
      document.getElementById("mistakes").innerText = mistakes;
      document.getElementById("timer").innerText = time + "s";
      document.getElementById("reset-test").disabled = true;
      document.getElementById("start-test").disabled = false;
      document.getElementById("start-test").innerHTML = "Start Test";
      document.getElementById("stop-test").disabled = false;
      document.getElementById("stop-test").style.display = "none";
      document.getElementById("start-test").style.display = "block";
      document.getElementById("wpm").innerText = "";
      document.getElementById("accuracy").innerText = "";
      renderNewQuote();
    
    }
    
    // generate a new quote and render it  
    window.onload = () => {
      userInput.value = "";
      document.getElementById("start-test").style.display = "block";
      document.getElementById("stop-test").style.display = "none";
      userInput.disabled = true;
      renderNewQuote();
    }
    @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap');
    body {
      background-color: #fff;
    }
    
    * {
      color: #000;
    }
    
    title {
      font-family: 'Lucida Sans';
    }
    
    h1,
    h2,
    h3 {
      margin: 0;
    }
    
    h1 {
      font-size: 24px;
      text-align: center;
    }
    
    h2 {
      font-size: 18px;
      text-align: center;
    }
    
    h3 {
      font-size: 16px;
      text-align: left;
    }
    
    * {
      font-family: 'Poppins', sans-serif;
      box-sizing: border-box;
    }
    
    .container {
      background-color: #f0f0f0;
    }
    
    .container {
      width: 80vmin;
      padding: 50px 30px;
    }
    
    .container {
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
    }
    
    .container {
      border-radius: 10px;
      box-shadow: 0 20px 40px rgba(0, 0, 0, 0.15);
    }
    
    .stats {
      font-size: 18px;
      text-align: center;
      font-family: 'Segoe UI';
    }
    
    .stats span {
      font-weight: 600;
    }
    
    #quote {
      text-align: justify;
      margin: 30px 0;
    }
    
    textarea {
      width: 100%;
      border-radius: 5px;
      padding: 10px 5px;
      font-size: 18px;
    }
    
    button {
      float: right;
      margin-top: 20px;
      background-color: #000;
      color: #fff;
      border: none;
      padding: 10px 30px;
      border-radius: 5px;
      font-size: 14px;
    }
    
    .result {
      margin-top: 40px;
      display: none;
    }
    
    .result h3 {
      font-size: 22px;
      text-align: center;
      margin-bottom: 20px;
    }
    
    .wrapper {
      display: flex;
      justify-content: space-around;
      align-items: center;
    }
    
    .wrapper span {
      font-weight: 600;
    }
    
    .success {
      color: #44b267;
    }
    
    .fail {
      color: #e81c4e;
    }
    
    img {
      width: 105%;
      margin: 1em auto;
      max-width: 100005px;
      height: 1000px;
      object-fit: cover;
      border-radius: 10px;
    }
    <h1>Engelsk WPM Test</h1>
    <div class="container">
      <div class="stats">
        <p>Tid: <span id="timer">0s</span></p>
        <p>Feil: <span id="mistakes">0</span></p>
      </div>
      <div id="quote" onmousedown="return false" onselectstart="return false"></div>
    
      <textarea id="quote-input" rows="3" placeholder="Type here when test starts..."></textarea>
    
      <button id="start-test" onclick="startTest()">Start Test</button>
      <button id="stop-test" onclick="displayResult()">Stopp Test</button>
      <button id="reset-test" onclick="resetTest()" disabled>Prøv Igjen</button>
    
      <div class="result">
        <h3>Resultat</h3>
        <div class="wrapper">
          <p>Treffsikkerhet: <span id="accuracy"></span></p>
          <p>Hurtighet: <span id="wpm"></span></p>
        </div>
      </div>
    
    </div>
    <h2>Klarer du 200wpm?</h2>
    <h3>Laget av Tbscuddles</h3>
    
    
    <div class="galley">
      <img src="https://images4.alphacoders.com/109/1096950.jpg">
    </div>