2

I have contenteditable div. Every time that content of div is changed, function to wrap each word with span (for CSS) is called. I have 2 problems:

  • After setting innerHTML, the cursor appears at the beginning of the div, I would like to cursor stay in the same position,
  • Newlines are deleted.

I tried to find a cursor positioning solution (eg. here) but I couldn't solve the problem with Range or Selection. Here is fiddle.

const div = document.querySelector('div')

div.addEventListener('input', () => {
    let text = div.textContent
    text = '<span>' + text.replace(/ /g, '</span> <span>').replace(/\n/g, '</span><br /><span>') + '</span>'
    div.innerHTML = text
})
body {
  background-color: white;
}

body > div {
  background-color: black;
  color: white;
  font-family: Monospace;
  padding: 0.5rem;
  width: 20rem;
}
<div contenteditable="true">
Multiline text
<br />
Multiline
<br />
Text
</div>
Masuk Helal Anik
  • 2,155
  • 21
  • 29
Michal
  • 1,755
  • 3
  • 21
  • 53
  • 1
    would you be willing to share what CSS you need to apply per word? there might be a better way than to insert `span` tags on each word – Katherine R Apr 07 '19 at 09:41
  • Maintaining cursor position when DOM elements with the previous selection are being replaced is a bit complicated, you need to save the previous selection before setting new DOM, map the old position to new position based on what changes you are making to the DOM and reapply selection using selection.setStart and selection.setEnd. TinyMce has functionality called Bookmark which implements simillar functionality, you can refer to it – AvcS Apr 16 '19 at 11:04

1 Answers1

0

The below code solves one part of your problem of adding break lines in place of new line characters.

The problem was with using textContent for evaluation. Let's take an example, say your div was initialized with the below content:

<div contenteditable>
  abc
  <br/>
  def
</div>

The first time when someone triggers a change to that div, the textContent method would return

  abc def

Since, the information about the content having a breakpoint has been lost, you'd see your new lines being stripped away. The solution was to change it to innerHTML instead, which would let you manipulate the content without any losses.

const div = document.querySelector('div')

div.addEventListener('input', () => {
    let text = div.innerHTML
    
   text = '<span>' + text.replace(/ /g, '</span> <span>').replace(/\n/g, '</span><br/><span>') + '</span>'
//console.log(text)
   div.innerHTML = text
})
body {
  background-color: white;
}

body > div {
  background-color: black;
  color: white;
  font-family: Monospace;
  padding: 0.5rem;
  width: 20rem;

}
<div contenteditable="true">
Multiline text
<br />
Multiline
<br />
Text
</div>

For the second part of the problem, you could refer answers of similar questions on SO: How to set caret(cursor) position in contenteditable element (div)?

bitsapien
  • 1,753
  • 12
  • 24
  • When entering a second character innerHTML will already contain spans, which will cause the text to be wrapped in one extra span on typing each character – AvcS Apr 16 '19 at 10:58