component / textarea-caret-position

xy coordinates of a textarea or input's caret
http://jsfiddle.net/dandv/aFPA7
MIT License
595 stars 82 forks source link

does it support contenteditable? #17

Open giorgio79 opened 10 years ago

giorgio79 commented 10 years ago

I notice the readme mentions textarea only.

jonathanong commented 10 years ago

negative. i think it's easier if you use contenteditable since you can do select range i believe. textareas and inputs don't have that luxury

anotherCoward commented 5 years ago

It can easily support it with a few changes and adding something like this, right at the beginning of the function:

function getCaretCoordinates(element, position, options) {
/*
isBrowser check..
*/
if (!element) {
  return getCaretCoordinates(document.activeElement || document.documentElement, position, options);
}
if (!element.isConnected) // in case of getCaretCoordinates(document.createElement('input'));
  return {
    top: 0,
    left: 0,
    height: 0,
    node: element
  }
if (options && options.absolute) {
  let relative = getCaretCoordinates(element, position),
    erect = element.getBoundingClientRect();
  return {
    top: erect.top + relative.top,
    left: erect.left + relative.left,
    height: relative.height, // remember kids, drugs are bad
    node: element
  }
}
let specials = ~['INPUT', 'TEXTAREA'].indexOf(element.nodeName);
if (position == null || !specials) {
  if (specials) {
    return getCaretCoordinates(element, element.selectionDirection == 'forward' ? element.selectionEnd : element.selectionStart);
  } else {
    if (window.getSelection().rangeCount) {
      let range = window.getSelection().getRangeAt(0),
        rect = range.getBoundingClientRect();
      // using the start or endcontainer is... uhm yeah... difficult...? :D
      let height = range.startContainer.nodeType == 1 ? getComputedStyle(range.startContainer).lineHeight : getComputedStyle(range.startContainer.parentNode).lineHeight;
      if (isNaN(height)) {
        let node = range.startContainer;
        if (range.startContainer.nodeType != 1) {
          node = node.parentNode;
        }
        let current = node.style.lineHeight;
        node.style.lineHeight = '1em';
        height = parseInt(getComputedStyle(node).lineHeight);
        node.style.lineHeight = current != null ? current : '';
        if (!node.getAttribute('style').length) { // clean up if empty
          node.removeAttribute('style');
        }
      }
      let erect = //thihihi
        element.getBoundingClientRect();
      return {
        top: rect.top - erect.top,
        left: rect.left - erect.left,
        height: height,
        node: element
      }
    } else {
      return {
        top: 0,
        left: 0,
        height: 0,
        node: element
      }
    }
  }
}
/*
 ... rest of the code
*/
}

This is compatible with all mayor browsers (add this range check for IE8, if you wanna support it).

One function that works for both types and returns the same output, is be really helpful. 👍

With this changes you could do:

getCaretPosition(); // returns the caret position, depending on the activeElement if any.
getCaretPosition(element); // returns the caret position inside that element
getCaretPosition(element, index); // same as before
getCaretPosition(null, null, { position: absolute }); // returns the absolute position of the caret depending on the current selected element

Greetings