Within my work, I am utilizing a contentEditable span
where I aim to position an element with position: absolute
on the same line as the cursor. However, issues arise when text wrapping occurs - causing abnormal behavior at the start and end of wrapped lines.
In both instances, when the cursor is positioned at the start of the second line, the y offset
from getBoundingClientRect()
matches the offset of the first line. Yet, upon moving one space further into the second line, the y offset
aligns correctly with that line.
The code snippet below showcases this behavior specifically in Firefox. While Chrome appears to handle it better, my complete implementation encounters imprecise behavior in Chrome as well, which I managed to resolve. In contrast, Firefox exhibits inconsistent behavior - the last position of the first line displays an offset
equal to the first line, while the initial position on the second line mirrors the first line's offset
, before functioning properly thereafter.
Explore the example by navigating to the last position on the first line. Notice how the value of CURRENT_TOP
displayed in the console remains at 16
. Upon shifting right once to move the cursor onto the next line, it still reads 16
. Subsequently, moving right once more will display 36
.
const textEl = document.getElementById("myText")
textEl.addEventListener("keyup", (event) => {
const domSelection = window.getSelection();
if (domSelection && domSelection.isCollapsed && domSelection.anchorNode) {
let offsetNewLine = 0;
const domRange = domSelection.getRangeAt(0);
const rect = domRange.getBoundingClientRect();
const rects = domRange.getClientRects();
const newRange = document.createRange();
const newRangeNextOffset = domSelection.anchorNode.textContent.length < domSelection.anchorOffset + 1 ? domSelection.anchorOffset : domSelection.anchorOffset + 1
newRange.setStart(domSelection.anchorNode, newRangeNextOffset);
newRange.setEnd(domSelection.anchorNode, newRangeNextOffset);
const nextCharacterRect = newRange.getBoundingClientRect();
console.log(`CURRENT_TOP: ${rect.y}, NEXT_CHAR_TOP: ${nextCharacterRect.y}`);
}
})
.text-container {
width: 500px;
display: inline-block;
border: 1px solid black;
line-height: 20px;
padding: 5px;
}
<span id="myText" class="text-container" contentEditable="true">Go on the last position in the first row and come it to first position in the second row</span>