A typing test looks impressive, demos brilliantly in interviews, and is secretly simple: compare two strings, character by character.
The core logic
const text = "The quick brown fox jumps over the lazy dog";
let started = null;
// render each char as a span
textEl.innerHTML = [...text].map(c => `<span>${c}</span>`).join("");
const spans = textEl.querySelectorAll("span");
input.addEventListener("input", () => {
started ??= Date.now();
const typed = input.value;
let correct = 0;
spans.forEach((span, i) => {
const ch = typed[i];
span.className = ch == null ? "" : ch === text[i] ? "ok" : "err";
if (ch === text[i]) correct++;
});
// live stats
const minutes = (Date.now() - started) / 60000;
const wpm = Math.round((typed.length / 5) / minutes); // standard: 5 chars = 1 word
const accuracy = Math.round((correct / typed.length) * 100) || 100;
statsEl.textContent = `${wpm} WPM ยท ${accuracy}% accuracy`;
if (typed.length === text.length) finish(wpm, accuracy);
});.ok { color: #22c55e; }
.err { color: #ef4444; text-decoration: underline; }
span:nth-child(var(--pos)) { /* caret: border-left on the next char */ }Upgrades
- Random paragraphs from an array or quotes API; 30/60-second modes
- Best WPM in localStorage; a WPM-over-time chart at the end
- Disable paste:
input.onpaste = (e) => e.preventDefault()๐