Skip to content

Instantly share code, notes, and snippets.

@stanwmusic
Created March 26, 2017 06:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stanwmusic/4857a554e983eb5d776294727144c15e to your computer and use it in GitHub Desktop.
Save stanwmusic/4857a554e983eb5d776294727144c15e to your computer and use it in GitHub Desktop.
Typing Speedometer
- var ticks = 120;
- var tickLabels = 12;
- var tickInc = ticks / tickLabels;
main
.speedometer
.speedometer-inner
- for (var i = 0; i < ticks + 1; ++i) {
.tick
- for (var j = 0; j <= tickLabels; ++j) {
- if (i == tickInc * j) {
span
- }
- }
- }
.arrow
.unit
.wpm
span(id="h",class="_")
span(id="t",class="_")
span(id="o",class="_0")
form
label(for="ta")="Type something as fast as you can!"
textarea(id="ta",type="text",cols="36",rows="8",value="")
button(type="reset")="Reset"
(function(){
var oldStrLen = 0,
charsEachSec = [];
var getWPM = function() {
const arrow = document.getElementsByClassName("arrow")[0],
display = [
document.getElementById("h"),
document.getElementById("t"),
document.getElementById("o"),
],
strLen = document.getElementsByTagName("textarea")[0].value.length;
let wpm = 0;
// unless field is cleared, get WPM based on average characters typed per second
if (strLen > 0) {
let charsDurSec = strLen - oldStrLen,
charSum = 0,
wordLen = 5,
maxWords = 60;
charsEachSec.push(charsDurSec);
// use last n words for average
if (charsEachSec.length > maxWords) {
charsEachSec.shift();
}
for (var i in charsEachSec) {
charSum += charsEachSec[i];
}
// calculate WPM
let avgChars = charSum / charsEachSec.length,
wps = avgChars / wordLen,
wpmCalc = Math.round(wps * 60),
hardLimit = 999;
if (wpmCalc > 0 && wpmCalc <= hardLimit) {
wpm = wpmCalc;
} else if (wpmCalc > hardLimit) {
wpm = hardLimit;
}
} else {
charsEachSec = [];
}
// make old string length equal to newest one before calculating WPM again
oldStrLen = strLen;
// set ceiling for and rotate arrow
let maxWpm = 120,
arrowWpm = wpm < maxWpm ? wpm : maxWpm;
arrow.style.transform = "rotate(" + ((arrowWpm * 2) - 120) + "deg) translateY(-72%)";
// make WPM string, clean digits, redisplay digits
let wpmStr = wpm.toString();
for (var i in display) {
display[i].className = "_";
}
for (var i in wpmStr) {
display[display.length - 1 - i].className = "_" + wpmStr[wpmStr.length - 1 - i];
}
};
// runtime loop
var run = function() {
setTimeout(run, 1000);
getWPM();
};
run();
})();
$ticks: 120;
$tickLabels: 12;
$tickInc: $ticks / $tickLabels;
$wpm: 0; // initial speed
:root {
font-size: calc(8px + 0.2vw);
}
* {
box-sizing: border-box;
font-size: inherit;
margin: 0;
padding: 0;
}
body {
background: rgb(32,32,32);
font-family: "Play", sans-serif;
}
main {
margin: 2em auto;
width: 90%;
min-width: 300px
}
@media (min-width: 360px) {
main {
width: calc(40em + 0.2vw);
}
}
.speedometer, form > * {
margin: auto auto calc(16px + 0.2vw) auto;
}
label, textarea, button {
font: {
family: Helvetica, sans-serif;
size: 1.75em;
};
}
label, button {
color: #fff;
}
label, textarea {
line-height: 1.5em;
}
textarea, button {
border: 0;
border-radius: 0;
letter-spacing: 0.02em;
width: 100%;
}
textarea {
padding: 0.75em;
}
button {
background-color: rgb(60,90,172);
color: #fff;
padding: 1em 0;
&:active {
background-color: rgb(44,74,156);
}
}
.speedometer {
overflow: hidden;
width: 32em;
height: 24em;
}
.speedometer-inner {
background-image:
radial-gradient(
100% 100% at 50% 50%,
rgba(0,0,0,0) 7%,
rgba(0,0,0,0.4) 7.25%,
rgba(0,0,0,0) 8%,
rgba(0,0,0,0) 46.5%,
rgb(128,128,128) 46.5%,
rgb(255,255,255) 48.5%,
rgb(208,208,208) 49.75%,
transparent 50%
);
color: rgb(255,255,255);
position: relative;
width: 100%;
height: 32em;
> div {
position: absolute;
}
}
.tick, .unit, .wpm {
z-index: 0;
}
.tick {
background-image: linear-gradient(transparent 4%, rgb(255,255,255) 4%, rgb(255,255,255) 8%, transparent 8%);
top: 0;
left: calc(50% - 0.09em);
width: 0.18em;
height: 100%;
span {
display: block;
font-size: 2.2em;
text-align: center;
width: 3.6em;
}
@for $i from 1 through $ticks + 1 {
&:nth-of-type(#{$i}) {
transform: rotate(-121deg + (240 / ($ticks + 1)) * $i);
}
}
@for $i from 1 through $tickLabels + 1 {
&:nth-of-type(#{($i - 1) * $tickInc + 1}) span {
transform: translate(-50%,2em) rotate(120deg - (($ticks / $tickLabels * 2) * ($i - 1)));
&:before {
content: "#{($i - 1) * $tickInc}";
}
}
}
&:nth-of-type(5n + 1) {
background-image: linear-gradient(transparent 4%, rgb(255,255,255) 4%, rgb(255,255,255) 10%, transparent 10%);
width: 0.2em;
}
&:nth-of-type(10n + 1) {
background-image: linear-gradient(transparent 4%, rgb(255,255,255) 4%, rgb(255,255,255) 12%, transparent 12%);
width: 0.3em;
}
}
.arrow {
background-color: rgb(255,222,24);
border-radius: 50% 50% 0 0;
box-shadow: 0 0 1px 1px rgb(160,64,0) inset, 0 0 1px 1px rgba(0,0,0,0.4);
top: 33%;
left: calc(50% - 0.45em);
width: 0.9em;
height: 10.8em;
transform: rotate(0deg + (($wpm * 2) - 120)) translateY(-73%);
transition: transform 1s linear;
z-index: 1;
}
.unit {
font-size: 2.2em;
text-align: center;
top: 31%;
left: calc(50% - 1.5em);
width: 3em;
&:before {
content: "WPM";
}
}
.wpm {
background-color: rgb(255,255,255);
border-radius: 0.2em;
box-shadow: 0 (0.05em) (0.05em) rgba(0,0,0,0.5) inset;
color: rgb(0,0,0);
font-size: 3em;
line-height: 1.25em;
overflow: hidden;
top: 62%;
left: calc(50% - 1.5em);
padding: 0 0.4em;
height: 1.2em;
width: 3em;
> span {
/* Number sprites by lavarmsg from Vecteezy.com (https://www.vecteezy.com/vector-art/95999-digital-number-counter) */
background: {
image: url(https://static.vecteezy.com/system/resources/previews/000/095/999/original/vector-digital-number-counter.jpg);
size: 6em auto;
};
display: inline-block;
vertical-align: top;
height: 100%;
width: 0.7em;
transform: translateY(0.12em);
}
}
/* Number sprites */
$yPos1: -1.725em;
$yPos2: -2.87em;
._ {
background-position: -3.65em $yPos2;
opacity: 0.2;
}
._0 {
background-position: -0.725em $yPos1;
}
._1 {
background-position: -1.7em $yPos1;
}
._2 {
background-position: -2.675em $yPos1;
}
._3 {
background-position: -3.675em $yPos1;
}
._4 {
background-position: -4.65em $yPos1;
}
._5 {
background-position: -0.725em $yPos2;
}
._6 {
background-position: -1.7em $yPos2;
}
._7 {
background-position: -2.675em $yPos2;
}
._8 {
background-position: -3.65em $yPos2;
}
._9 {
background-position: -4.625em $yPos2;
}
<link href="https://fonts.googleapis.com/css?family=Play" rel="stylesheet" />

Typing Speedometer

A speedometer that measures your typing speed in real time. It works like those used by typing test websites such as typingtest.com. To make it work, I had to figure out how to turn the WPM formula ( (total characters / 5) / 1 minute ) into a real-time WPM formula. So after experimenting with the tests, this is what I came up with:

((total characters / (# of times characters were recorded each second)) / 5) * 60 seconds

I got the idea to create this demo after memories of playing some racing mini game in early versions of Mavis Beacon where your typing speed is your driving speed.

A Pen by Stan Williams on CodePen.

License.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment