Words-Per-Minute Calculator
Wyatt Marshall
Posted on February 21, 2022
A super quick project to calculate typing speed in words-per-minute with spell checking.
This is using only PHP, Vanilla JS, HTML and CSS to make this simple app and I put it all on one page just for simplicity.
The main part of the HTML is a title, the timer display, and a form where the textarea lives for the input. (The second form just refreshes the page on submit, this is a quick way to just reset everything).
<-- CSS GOES HERE-->
<html lang="en">
<body>
<div>
<-- PHP CODE GOES HERE -->
<p>WPM CALCULATOR</p>
<p>Timer: <span id='timer'>0</span>s</p>
<form action="<?php $_SERVER['PHP_SELF'] ?>" method='post'>
<textarea name='test' id='test' cols='80' rows='10'>
</textarea>
<br>
<input type='hidden' name='hidden' id='hidden' value=''>
<button type='submit'>Submit</button>
</form>
<form action="<?php $_SERVER['PHP_SELF'] ?>" method='post'>
<button type='submit'>Reset</button>
</form>
</div>
</body>
</html>
<-- JS GOES HERE-->
This form is basically all we need for the majority of the functionality. We are going to add some PHP to handle the form and output some HTML conditionally once the form is submitted.
<?php
// $pspell is a Built-In PHP dictionary spell checker
$pspell = pspell_new("en");
// if $_POST has variables
if (isset($_POST)){
// explode ['test'] string into an array
$words = explode(" ", $_POST['test']);
// timer value set by Javascript
$timer = $_POST['hidden'] / 100;
// Variables for Word Count, timer (in seconds) and WPM
$wordCount = count($words);
$minutes = $timer / 60;
$wpm = $wordCount / $minutes;
}
// echo the WPM and format to 2 decimal places
echo "<p class='results'>WPM: " . number_format($wpm, 2, '.', '') . "</p>";
echo "<p class='results'>Words: " . count($words) . "</p>";
echo "<p class='results'>Time: {$timer}s</p>";
// spell check each word in the $words array and assign classes to display correct words as green and incorrect as red
if(count($words) > 1){
echo "<hr>";
foreach ($words as $word) {
if (pspell_check($pspell, $word)) {
echo "<span class='correct'>{$word}</span> ";
} else {
echo "<span class='wrong'>{$word}</span> ";
}
}
echo "<hr>";
}
?>
The PHP code here is setting up the pspell() function that takes a dictionary language as an argument (eg: "en" for English). When the form is submitted, the sentences typed into the textarea are "posted" to the $_POST array with the key of the 'test' as one string. After the form submits and the textarea value is in the $_POST array, we want to know how many words were typed, and we want to be able to spell check each word. To do this we can use the explode() function, which is a PHP built-in that will break apart a string at each space and put each word into an array index. With each word separated into an array, we can use a foreach loop to spell check each word with pspell() and then assign a CSS class to display the word in green if it is correct or red if its misspelled.
To find out how many words were typed in total, we can use count() to see how many items are in the array to get our word count.
the $timer variable is set by Javascript (that we will come to in a moment), but for now just know that this variable is also passed to the $_POST array with the key of ['hidden'] and this value is in 1/100 second, so we divide by 100 to get a full second.
$minutes and $wpm are just converting between seconds and minutes and then calculating how many Words-Per-Minute were typed.
Now that the functionality of the HTML form and PHP are setup, all we need now is a timer, a way to start it automatically and end it when we are finished typing. Things get a little more tricky here but not too complicated, stick with me.
For this, we will use some Javascript:
<script>
// Setup Variables
let Interval;
let timerHidden = document.getElementById('hidden');
let timerDiv = document.getElementById('timer');
let timerrrr = 0;
let input = document.getElementById('test');
// Textarea Event Listener
input.addEventListener('keypress', logKey);
// Check for first key entered then remove Event Listener
function logKey(e) {
input.removeEventListener('keypress', logKey);
startTimer();
}
// Start Timer
function startTimer() {
if (!Interval) {
Interval = setInterval(timerEval, 10);
}
}
// Display timer on page
// Place timer value in hidden form field
function timerEval(){
timerrrr++;
timerHidden.value = timerrrr;
timerDiv.innerHTML = timerrrr / 100;
}
// Stop Timer If ESC key is pressed
document.onkeydown = function(evt) {
evt = evt || window.event;
if (evt.key == "Escape") {
myStopFunction();
}
};
// Stop Timer
function myStopFunction() {
clearInterval(Interval);
}
</script>
First we are just setting up some variables, 'Interval' is our timer itself, 'timerHidden' is getting the hidden form field where we will submit the count of the timer to the $_POST array, and then 'timerDiv' is getting the div where the current time on the timer will be displayed to the user.
The textarea event listener is setup next which will fire when a key is pressed inside of the textarea and call the logKey() function. But because the logKey() function starts the timer, we don't want this getting called on each key press (which is what it's current doing), so first we remove the event listener from the input variable and then call the startTimer() function.
setInterval() in startTimer() calls the timerEval() function ever 10 milliseconds (1/100 of a second) and timerEval() updates the value inside of the hidden field with the current time in 10ms. Then we update the time displayed in the timerDiv to show the user what the current time on the clock is.
*NOTE: the timerDiv.innerHTML is being divided by 100 (the same as we did in the PHP above) but this is ONLY displaying the time in the timerDiv and is not being stored. The timerHidden.value is the current time in milliseconds and THIS is the value being passed to the $_POST array, therefore, we divide by 100 again in PHP. This is mainly a personal choice, you could use the already divided number and skip the math in the PHP but I preferred to keep it in ms when I pass it through.
So in all reality, we're done! At least, if you want to just click the submit button when you are finished typing then everything will work... BUT, it's a lot faster to hit another key to end the timer and that would give a more accurate Words-Per-Minute result. We can't really use the ENTER key, in case you want to type an ENTER in for a paragraph or something, so how about the ESC key? We really have no reason to ever hit that key when typing so it'll work just fine.
To do this we setup an onkeydown event on the whole document and then just check if the key that was pressed was the ESC key. If the ESC was pressed we simply call a function that stops the timer. Easy as that!
The only thing left is some styling to make it look a little less ugly:
<style>
@import url('https://fonts.googleapis.com/css2?family=Kanit:ital,wght@1,600&display=swap');
:root {
--main-bg: rgb(50, 50, 50);
}
*{
line-height: 1;
font-family: 'Kanit', sans-serif;
font-size: 20px;
color: white;
}
body {
max-width: 1024px;
margin: auto;
background-color: var(--main-bg);
max-height: 100vh;
}
div {
text-align: center;
margin: auto;
width: max-content;
}
textarea {
background-color: var(--main-bg);
border: 2px solid yellow;
border-radius: 5px;
resize: none;
width: 1024px;
height: 40vh;
padding: 10px;
}
textarea:hover {
outline: none;
}
button {
cursor: pointer;
margin: 10px;
border: 2px solid yellow;
border-radius: 5px;
background-color: var(--main-bg);
padding: 5px 10px;
transition: outline-offset 0.1s linear;
}
button:hover {
outline: 2px solid yellow;
outline-offset: 2px;
}
.correct {
color: green;
}
.wrong {
color: red;
}
.results {
display: inline-block;
padding: 5px;
border: 1px solid black;
margin: 10px;
border-radius: 5px;
}
</style>
This is a super quick project and focuses more on the functionality than styling. You can embed this into your own projects and make it look however you want. I hope this help!
Photo by Caryn from Pexels
Posted on February 21, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.