Last active
December 15, 2024 18:17
-
-
Save nothingismagick/642861242050c1d5f3f1cfa7bcd2b3fd to your computer and use it in GitHub Desktop.
Small script to detect caret pixel position in contenteditable div
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Get the caret position in all cases | |
* | |
* @returns {object} left, top distance in pixels | |
*/ | |
getCaretTopPoint () { | |
const sel = document.getSelection() | |
const r = sel.getRangeAt(0) | |
let rect | |
let r2 | |
// supposed to be textNode in most cases | |
// but div[contenteditable] when empty | |
const node = r.startContainer | |
const offset = r.startOffset | |
if (offset > 0) { | |
// new range, don't influence DOM state | |
r2 = document.createRange() | |
r2.setStart(node, (offset - 1)) | |
r2.setEnd(node, offset) | |
// https://developer.mozilla.org/en-US/docs/Web/API/range.getBoundingClientRect | |
// IE9, Safari?(but look good in Safari 8) | |
rect = r2.getBoundingClientRect() | |
return { left: rect.right, top: rect.top } | |
} else if (offset < node.length) { | |
r2 = document.createRange() | |
// similar but select next on letter | |
r2.setStart(node, offset) | |
r2.setEnd(node, (offset + 1)) | |
rect = r2.getBoundingClientRect() | |
return { left: rect.left, top: rect.top } | |
} else { // textNode has length | |
// https://developer.mozilla.org/en-US/docs/Web/API/Element.getBoundingClientRect | |
rect = node.getBoundingClientRect() | |
const styles = getComputedStyle(node) | |
const lineHeight = parseInt(styles.lineHeight) | |
const fontSize = parseInt(styles.fontSize) | |
// roughly half the whitespace... but not exactly | |
const delta = (lineHeight - fontSize) / 2 | |
return { left: rect.left, top: (rect.top + delta) } | |
} | |
} |
Excellent! Thank you very much!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
To handle long selections (e.g. contenteditable content higher than the viewport and selection range spanning over longer distances than the height of the viewport) you have to figure out the caret position within the selection (it's either start or end of the selection).
In practice, you can do this by using
window.getSelection()
andfocusNode
andfocusOffset
.Then you can build a range out of those and use
Range.getBoundingClientRect()
to figure out the pixel offsets within the viewport, but beware bug https://bugs.chromium.org/p/chromium/issues/detail?id=1398728And an interesting question is what is the true pixel location of the caret if the caret position is between two consequtive
<table>
elements where one is very small and another is higher than the viewport? The above bug is basically about the same issue but it will show up more often than simply between consequtive tables.