Skip to content

Instantly share code, notes, and snippets.

@nothingismagick
Last active April 16, 2024 10:09
Show Gist options
  • Star 30 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save nothingismagick/642861242050c1d5f3f1cfa7bcd2b3fd to your computer and use it in GitHub Desktop.
Save nothingismagick/642861242050c1d5f3f1cfa7bcd2b3fd to your computer and use it in GitHub Desktop.
Small script to detect caret pixel position in contenteditable div
/**
* 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) }
}
}
@yairEO
Copy link

yairEO commented Nov 22, 2019

Awesome, gonna test is now! I was just asking this on Stackoverflow yesterday

@mhdhamouday
Copy link

Thanks!

@adrienpsl
Copy link

Thanks!

@ww-arthur
Copy link

Blessed be you!
Thank you very much.

@bruprod
Copy link

bruprod commented Nov 6, 2021

Awesome! Thank you very much!

@sunnykandev
Copy link

You did a lovely job!!!
Thank you :*

@areksliwa-modento
Copy link

big, thanks it works, love you :)

@mikkorantalainen
Copy link

mikkorantalainen commented Dec 8, 2022

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() and focusNode and focusOffset.

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=1398728

And 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.

@oscxc
Copy link

oscxc commented May 11, 2023

Excellent! Thank you very much!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment