:before with position: absolute causing line breaks and moving elements

Viewed 67

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>

0 Answers
Related