I am making a typing program. Each letter is in its own div, which is broken into words.
For example, the word other would be written as:
<div class="word">
<div class="letter" id="l184"></div>
<div class="letter" id="l185">o</div>
<div class="letter" id="l186">t</div>
<div class="letter" id="l187">h</div>
<div class="letter" id="l188">e</div>
<div class="letter" id="l189">r</div>
</div>
The letter with the cursor before it also has the class cursor.
.cursor:before {
position: absolute;
width: 2px;
height: 30px;
background: var(--accent);
content: ' ';
}
Sometimes, when typing the first word of a line, the completed part of the word, which has the cursor after it, is bumped up to the line before it. This does not happen when the :before has no content. Please help me figure out why something with position: absolute is moving elements around. Thank you!
EDIT: Snippet
EDIT 2: The glitch only works with a certain combination of words, so if you cannot reproduce the glitch, please try again.
let dict = ['test', 'stack', 'overflow'];
let index = 0, words, wrong = 0, last;
let running;
let sec = 0, min = 0;
const text = document.querySelector('#text');
const st = document.querySelector('#sec');
const mt = document.querySelector('#min');
function genTest() {
words = "";
for (let i = 0; i < 100; i++) {
words += dict[randomRange(0, 2)];
if (i !== 99) words += " ";
}
let html = `<div class='word'>`;
let i = 0;
words.split('').forEach(l => {
if (l === ' ') html += `</div><div class="word">`;
if (i === 0) html += `<div class="letter curs-fade" id='l${i}'>${l}</div>`;
else html += `<div class="letter" id='l${i}'>${l}</div>`;
i++;
});
last = i;
html += '</div>'
text.innerHTML = html;
}
function initTest() {
running = false;
genTest();
st.classList = "";
mt.classList = "";
l(0).classList.add('cursor');
}
function start() {
running = true;
st.classList.add('txt-active');
setInterval(() => {
sec++;
if (sec >= 60) {
sec %= 60;
min++;
if (min === 1) {
mt.classList.add('txt-active');
}
}
mt.innerHTML = min.toString() + ':';
st.innerHTML = ((sec < 10) ? '0' : '') + sec.toString();
}, 1000);
}
document.addEventListener('keydown', (e) => {
let key = e.key;
const cl = l(index);
if (wrong > 0 && key === 'Backspace') {
wrong--;
w(wrong).remove();
return;
}
if (key.match(/^[a-zA-Z"'\s]+$/) && key.length === 1) {
if (index === 0 && !running) {
start();
}
if (cl.innerHTML === key.toLowerCase() && wrong === 0) {
cl.classList.add('correct');
index++;
} else if (key !== ' ') {
let w = document.createElement('DIV');
w.classList.add('letter');
w.classList.add('incorrect');
w.id = "w" + wrong;
w.innerHTML = key;
wrong++;
if (index > 0) l(index - 1).appendChild(w);
else {
let n = l(index);
n.parentNode.insertBefore(w, n);
}
}
cl.classList.remove('cursor');
l(index).classList.add('cursor');
}
});
function l(index) {
return document.getElementById('l' + index);
}
function w(index) {
return document.getElementById('w' + index);
}
function randomRange(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1)) + min;
}
initTest();
@import url('https://fonts.googleapis.com/css2?family=Roboto+Mono:ital,wght@0,100;0,200;0,300;0,400;0,500;0,600;0,700;1,100;1,200;1,300;1,400;1,500;1,600;1,700&display=swap');
* {
font-family: 'Roboto Mono', monospace;
}
body, html {
margin: 0;
width: 100%;
height: 100%;
overflow: hidden;
}
body {
background-color: #0f0f0f;
--accent: yellow;
--gray: #ababab;
--dark-gray: #8f8f8f;
user-select: none;
-moz-user-select: none;
-ms-user-select: none;
-webkit-user-select: none;
}
.header {
margin: 10px 150px;
}
.title {
font-size: 50px;
font-weight: 200;
color: white;
}
#text {
color: var(--gray);
margin: 20px 150px;
text-align: left;
}
.timer {
color: var(--dark-gray);
font-size: 50px;
font-weight: 300;
text-align: center;
margin: 100px 0 0;
}
.txt-active {
color: white;
transition: color .7s ease;
}
#min, #sec {
display: inline;
}
.word {
display: inline;
}
.letter {
display: inline;
font-weight: 200;
font-size: 24px;
}
.correct {
color: white;
}
.incorrect {
color: #d26f6f;
}
.cursor:before {
position: absolute;
width: 2px;
height: 30px;
background: var(--accent);
content: ' ';
}
.curs-fade:before {
animation: cursor-fade alternate-reverse .8s infinite;
}
@keyframes cursor-fade {
80% {opacity: 1}
0% {opacity: 0}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="styles/style.css">
<title>Typeracer++</title>
</head>
<body>
<div class="header">
<div class="title">typeracer++</div>
</div>
<div class="test">
<div class="timer"><div id="min">0:</div><div id="sec">00</div></div>
<div id="text"></div>
</div>
<script type="text/javascript" src="scripts/game.js"></script>
</body>
</html>
