Skip to content

Instantly share code, notes, and snippets.

@jh3y
Last active February 26, 2023 23:44
Show Gist options
  • Star 9 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jh3y/6c066cea00216e3ac860d905733e65c7 to your computer and use it in GitHub Desktop.
Save jh3y/6c066cea00216e3ac860d905733e65c7 to your computer and use it in GitHub Desktop.
get text cursor position
/**
* returns x, y coordinates for absolute positioning of a span within a given text input
* at a given selection point
* @param {object} input - the input element to obtain coordinates for
* @param {number} selectionPoint - the selection point for the input
*/
const getCursorXY = (input, selectionPoint) => {
const {
offsetLeft: inputX,
offsetTop: inputY,
} = input
// create a dummy element that will be a clone of our input
const div = document.createElement('div')
// get the computed style of the input and clone it onto the dummy element
const copyStyle = getComputedStyle(input)
for (const prop of copyStyle) {
div.style[prop] = copyStyle[prop]
}
// we need a character that will replace whitespace when filling our dummy element if it's a single line <input/>
const swap = '.'
const inputValue = input.tagName === 'INPUT' ? input.value.replace(/ /g, swap) : input.value
// set the div content to that of the textarea up until selection
const textContent = inputValue.substr(0, selectionPoint)
// set the text content of the dummy element div
div.textContent = textContent
if (input.tagName === 'TEXTAREA') div.style.height = 'auto'
// if a single line input then the div needs to be single line and not break out like a text area
if (input.tagName === 'INPUT') div.style.width = 'auto'
// create a marker element to obtain caret position
const span = document.createElement('span')
// give the span the textContent of remaining content so that the recreated dummy element is as close as possible
span.textContent = inputValue.substr(selectionPoint) || '.'
// append the span marker to the div
div.appendChild(span)
// append the dummy element to the body
document.body.appendChild(div)
// get the marker position, this is the caret position top and left relative to the input
const { offsetLeft: spanX, offsetTop: spanY } = span
// lastly, remove that dummy element
// NOTE:: can comment this out for debugging purposes if you want to see where that span is rendered
document.body.removeChild(div)
// return an object with the x and y of the caret. account for input positioning so that you don't need to wrap the input
return {
x: inputX + spanX,
y: inputY + spanY,
}
}
@defmech
Copy link

defmech commented Jul 5, 2018

In my use case I found I had to fix the position of the div as I was getting wonky offsetTop values.

// Make sure DIV is fixed position top left;
div.style.position = "fixed";
div.style.top = "0px";
div.style.left = "0px";
div.style.opacity = 0; // Just in case! ;)

@quantuminformation
Copy link

Span Y is way too high

image

@quantuminformation
Copy link

When the div is appended, it puts it a the bottom of the page

image

@quantuminformation
Copy link

@defmech thx your thing fixed it, maybe fork the gist?

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