Typing Test POC
Typing Test POC
by Csaba Hellinger
HTML
<div id="display"></div>
<textarea id="input">Hello, World!</textarea>
<p>
</p>
CSS
html, body, #input {
background: #333;
color: white;
}
#input {
margin-top: 1rem;
opacity: 0.2;
}
#display {
display: flex;
flex-wrap: wrap;
background: #222;
font: 42px monospace;
padding: 5px;
border-radius: 5px;
gap: 1px;
}
.word {
display: flex;
gap: 1px;
}
.char {
position: relative;
white-space: pre;
background: #333;
border-radius: 5px;
padding: 2px;
--border-left: 2px inset red;
}
.char.selected {
background: #357;
}
.char.cursor:before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 4px;
height: 100%;
background: #08f;
animation: blink 800ms infinite;
}
.char.end-cursor {
background: #08f;
animation: blink 800ms infinite;
}
@keyframes blink {
0% { opacity: 1; }
60% { opacity: 1; }
80% { opacity: 0; }
}
JavaScript
const input = document.getElementById('input');
const display = document.getElementById('display');
const updateDisplay = (text) => {
const { selectionStart, selectionEnd, selectionDirection } = input;
let charIndex = 0;
display.innerHTML = '';
const words = text.split(/\b/);
words.forEach(word => {
const wordSpan = document.createElement('span');
wordSpan.classList.add('word')
const chars = word.split('');
chars.forEach(char => {
const charSpan = document.createElement('span');
const isSelected = selectionStart <= charIndex && charIndex < selectionEnd;
const cursorIndex = selectionDirection === 'backward' ? selectionStart : selectionEnd;
const isCursor = cursorIndex === charIndex;
console.log({char, selectionStart, selectionEnd, selectionDirection });
charSpan.classList.add('char', isSelected && 'selected', isCursor && 'cursor');
charSpan.innerText = char;
wordSpan.appendChild(charSpan);
charIndex += 1;
});
display.appendChild(wordSpan);
});
if (selectionEnd === text.length) {
const cursorSpan = document.createElement('span');
cursorSpan.classList.add('char', 'end-cursor');
cursorSpan.innerText = ' ';
display.appendChild(cursorSpan);
}
};
const focus = () => {
input.focus();
input.selectionStart = input.value.length;
input.selectionEnd = input.value.length;
};
// TODO: debounce
input.oninput = (event) => updateDisplay(event.target.value);
input.onkeyup = (event) => updateDisplay(event.target.value);
display.onclick = focus;
focus();
updateDisplay(input.value);