0

I have some text that types a message. I have seen this done many different ways, but I like how simplistic this approach is.

The thing it doesn't have is a caret at the end of each line, I tried to add one myself via css, but the caret gets aligned to the far right of each line instead of stopping at the end of the text. Also, the caret should only show on the line it's on. The one I created shows, but then each new line also gets a caret, so by the end I'm left with this big giant cursor which works...but definitely not what I had anticipated.

I thought about adding display: inline to the <p> tags, but I want each new p tag to be on a new line.

const paragraphs = document.querySelectorAll('#str p'); // Select all <p> tags within #str
let delay = 0;

paragraphs.forEach(p => {
  const text = p.textContent;
  p.textContent = ''; // Clear the text content initially

  let index = 0;

  function typeWriter() {
    if (index < text.length) {
      p.textContent += text.charAt(index);
      index++;
      setTimeout(typeWriter, 90); // Adjust typing speed here
    }
  }

  setTimeout(typeWriter, delay); // Delay between paragraphs
  delay += text.length * 90; // Increase delay based on text length
});
#str {
  display: flex;
  flex-direction: column;
  width: 100%;
  padding: 20px;
  height: auto;
  font-family: sans-serif;
  font-size: 40px;
  margin: 20px auto;
  position: relative;
  color: #fff;
  font-weight: bold;
}

#str p {
  border-right: .15em solid orange;
  overflow: hidden;
  position: relative;
  animation: blink-caret .5s step-end infinite;
}

@keyframes blink-caret {
  from,
  to {
    border-color: transparent
  }
  50% {
    border-color: orange
  }
}
<div id="str">
  <p>Something Cool</p>
  <p>Shows</p>
  <p>Up In here!</p>
</div>

6
  • 2
    Can you explain what you mean by a caret? I tried running your code, see: codepen.io/kikosoft/pen/dyBPYjR but I see no caret. Something orange should probably appear, but it doesn't. Note that you can also make your code runnable here on Stack Overflow using the <> button. Commented Jul 9 at 20:09
  • @KIKOSoftware dictionary.cambridge.org/dictionary/english/caret
    – Paulie_D
    Commented Jul 9 at 20:32
  • 1
    @Paulie_D that doesn't really tell us much, given that the cursor indicator as I type this is a pipe, not a caret symbol. Commented Jul 9 at 20:34
  • I've updated the post to use a runnable snippet, which clearly shows a blinking orange element to the right of each paragraph. Commented Jul 9 at 20:38
  • A Caret is just "a cursor on a screen that shows where text should be entered", a pipe is standard but the symbol ^ is also called a caret.
    – Paulie_D
    Commented Jul 9 at 20:47

1 Answer 1

2

Here is a solution with a couple modifications:

Instead of styling the paragraphs, you can use an :after pseudo element with display: inline-block. Combined with other conditions, you could use this selector:

#str p:not(:empty):not(.complete:not(:last-child)):after
  • :not(:empty) will prevent the caret from showing when the paragraph has not started yet
  • :not(.complete:not(:last-child)) will prevent the caret from showing when the paragraph is complete, except if it's the last one

In your JS, you can add the complete class to the paragraph once it's complete:

p.classList.add('complete');

const paragraphs = document.querySelectorAll('#str p'); // Select all <p> tags within #str
let delay = 0;

paragraphs.forEach(p => {
  const text = p.textContent;
  p.textContent = ''; // Clear the text content initially

  let index = 0;

  function typeWriter() {
    if (index < text.length) {
      p.textContent += text.charAt(index);
      index++;
      setTimeout(typeWriter, 90); // Adjust typing speed here
    } else {
      p.classList.add('complete');
    }
  }

  setTimeout(typeWriter, delay); // Delay between paragraphs
  delay += text.length * 90; // Increase delay based on text length
});
#str {
  padding: 5px;
  font-family: sans-serif;
  font-size: 15px;
  margin: 5px auto;
  position: relative;
  color: #000;
  font-weight: bold;
}

#str p:not(:empty):not(.complete:not(:last-child)):after {
  content: "";
  display: inline-block;
  height: 1em;
  border-right: .15em solid orange;
  position: relative;
  animation: blink-caret 0.5s step-end infinite;
}

@keyframes blink-caret {
  from,
  to {
    border-color: transparent
  }
  50% {
    border-color: orange
  }
}
<div id="str">
  <p>Something Cool</p>
  <p>Shows</p>
  <p>Up In here!</p>
</div>

1
  • This is a great solution, I really appreciate your suggestions! Thank you for the help! Commented Jul 9 at 22:40

Not the answer you're looking for? Browse other questions tagged or ask your own question.