Search code examples
javascripthtmlcoordinatescaretgetcaretpos

How To Find INPUT And TEXTAREA Caret Position Coordinates Using JavaScript?


Me trying to find out caret position coordinates with respect to document(Whole Web Page) in a TextArea or Input of type="text" on JavaScript HTML DOM Events functions like onkeydown, onkeypress, onkeyup etc...

For this I created the below HTML...

<input dir="rtl" type="text" id="TEST_INPUT" onkeydown="myFunction(this)"></input>
<textarea dir="rtl" id="TEST_TEXTAREA" onkeyup="myFunction(this)"></textarea>

And running the below JavaScript...

<script type="text/javascript">
/* Main Function
----------------------------------------------- */
function myFunction(Desired_ID){
    // Extra Codes Here ETC...
    document.getElementById(Desired_ID.id).style.backgroundColor = "red";
    // Extra Codes Here ETC...
    var coords = getSelectionCoords(Desired_ID);
    alert(coords.left + ", " + coords.top);
    // Extra Codes Here ETC...
}

/* Get Caret XY Coordinate
----------------------------------------------- */
function getSelectionCoords(Desired_ELEMENT) {
// ------>>> What To Do Here Is The Problem To Cover All Browsers <<<------
return {left:x,top:y};
}
</script>

I searched in StackOverflow and found many one but everyone have some sort of problems while using or running that are shared below...

Where mine working DEMO is at https://jsfiddle.net/qjkkqdg2/ on which I am working out so can your share any tip or idea to go through it...???


Solution

  • After keep working and trying, I found my desired answer and here I am sharing it below in the form of code that you can see at https://jsfiddle.net/qjkkqdg2/1/ also...

    <script type="text/javascript">
    /* Main Function
    ----------------------------------------------- */
    function myFunction(Desired_ID){
        // Extra Codes Here ETC...
        document.getElementById(Desired_ID.id).style.backgroundColor = "red";
        // Extra Codes Here ETC...
        var coordinates = getCaretCoordinates(Desired_ID, Desired_ID.selectionStart);
        alert(coordinates.left + ", " + coordinates.top);
        // Extra Codes Here ETC...
    }
    
    /* Get Caret XY Coordinate
    ----------------------------------------------- */
    /* jshint browser: true */
    (function () {
    
    // The properties that we copy into a mirrored div.
    // Note that some browsers, such as Firefox,
    // do not concatenate properties, i.e. padding-top, bottom etc. -> padding,
    // so we have to do every single property specifically.
    var properties = [
      'direction',  // RTL support
      'boxSizing',
      'width',  // on Chrome and IE, exclude the scrollbar, so the mirror div wraps exactly as the textarea does
      'height',
      'overflowX',
      'overflowY',  // copy the scrollbar for IE
    
      'borderTopWidth',
      'borderRightWidth',
      'borderBottomWidth',
      'borderLeftWidth',
      'borderStyle',
    
      'paddingTop',
      'paddingRight',
      'paddingBottom',
      'paddingLeft',
    
      // https://developer.mozilla.org/en-US/docs/Web/CSS/font
      'fontStyle',
      'fontVariant',
      'fontWeight',
      'fontStretch',
      'fontSize',
      'fontSizeAdjust',
      'lineHeight',
      'fontFamily',
    
      'textAlign',
      'textTransform',
      'textIndent',
      'textDecoration',  // might not make a difference, but better be safe
    
      'letterSpacing',
      'wordSpacing',
    
      'tabSize',
      'MozTabSize'
    
    ];
    
    var isBrowser = (typeof window !== 'undefined');
    var isFirefox = (isBrowser && window.mozInnerScreenX != null);
    
    function getCaretCoordinates(element, position, options) {
      if(!isBrowser) {
        throw new Error('textarea-caret-position#getCaretCoordinates should only be called in a browser');
      }
    
      var debug = options && options.debug || false;
      if (debug) {
        var el = document.querySelector('#input-textarea-caret-position-mirror-div');
        if ( el ) { el.parentNode.removeChild(el); }
      }
    
      // mirrored div
      var div = document.createElement('div');
      div.id = 'input-textarea-caret-position-mirror-div';
      document.body.appendChild(div);
    
      var style = div.style;
      var computed = window.getComputedStyle? getComputedStyle(element) : element.currentStyle;  // currentStyle for IE < 9
    
      // default textarea styles
      style.whiteSpace = 'pre-wrap';
      if (element.nodeName !== 'INPUT')
        style.wordWrap = 'break-word';  // only for textarea-s
    
      // position off-screen
      style.position = 'absolute';  // required to return coordinates properly
      if (!debug)
        style.visibility = 'hidden';  // not 'display: none' because we want rendering
    
      // transfer the element's properties to the div
      properties.forEach(function (prop) {
        style[prop] = computed[prop];
      });
    
      if (isFirefox) {
        // Firefox lies about the overflow property for textareas: https://bugzilla.mozilla.org/show_bug.cgi?id=984275
        if (element.scrollHeight > parseInt(computed.height))
          style.overflowY = 'scroll';
      } else {
        style.overflow = 'hidden';  // for Chrome to not render a scrollbar; IE keeps overflowY = 'scroll'
      }
    
      div.textContent = element.value.substring(0, position);
      // the second special handling for input type="text" vs textarea: spaces need to be replaced with non-breaking spaces - http://stackoverflow.com/a/13402035/1269037
      if (element.nodeName === 'INPUT')
        div.textContent = div.textContent.replace(/\s/g, '\u00a0');
    
      var span = document.createElement('span');
      // Wrapping must be replicated *exactly*, including when a long word gets
      // onto the next line, with whitespace at the end of the line before (#7).
      // The  *only* reliable way to do that is to copy the *entire* rest of the
      // textarea's content into the <span> created at the caret position.
      // for inputs, just '.' would be enough, but why bother?
      span.textContent = element.value.substring(position) || '.';  // || because a completely empty faux span doesn't render at all
      div.appendChild(span);
    
      var coordinates = {
        top: span.offsetTop + parseInt(computed['borderTopWidth']),
        left: span.offsetLeft + parseInt(computed['borderLeftWidth'])
      };
    
      if (debug) {
        span.style.backgroundColor = '#aaa';
      } else {
        document.body.removeChild(div);
      }
    
      return coordinates;
    }
    
    if (typeof module != 'undefined' && typeof module.exports != 'undefined') {
      module.exports = getCaretCoordinates;
    } else if(isBrowser){
      window.getCaretCoordinates = getCaretCoordinates;
    }
    
    }());
    </script>
    

    And this code of id by https://github.com/component/textarea-caret-position. Thanks for him...

    LIMITATION:

    Its only giving coordinate with respect to element but I want to get it with respect to whole document...

    To fix this limitation, I added another function that will find out element coordinate with respect to whole document so just add the below function also...

    <script type="text/javascript">
    /* Get Element XY Coordinate
    ----------------------------------------------- */
    function getElementCoords(elem) { // crossbrowser version
        var box = elem.getBoundingClientRect();
    
        var body = document.body;
        var docEl = document.documentElement;
    
        var scrollTop = window.pageYOffset || docEl.scrollTop || body.scrollTop;
        var scrollLeft = window.pageXOffset || docEl.scrollLeft || body.scrollLeft;
    
        var clientTop = docEl.clientTop || body.clientTop || 0;
        var clientLeft = docEl.clientLeft || body.clientLeft || 0;
    
        var top  = box.top +  scrollTop - clientTop;
        var left = box.left + scrollLeft - clientLeft;
    
        return { top: Math.round(top), left: Math.round(left) };
    }
    </script>
    

    So finally our Main Function will be now as...

    <script type="text/javascript">
    /* Main Function
    ----------------------------------------------- */
    function myFunction(Desired_ID){
        // Extra Codes Here ETC...
        document.getElementById(Desired_ID.id).style.backgroundColor = "red";
        // Extra Codes Here ETC...
        var coordinates = getCaretCoordinates(Desired_ID, Desired_ID.selectionStart);
        var elementCoordinates = getElementCoords(Desired_ID);
        var topPosition = coordinates.top + elementCoordinates.top;
        var leftPosition = coordinates.left + elementCoordinates.left;
        alert(leftPosition + ", " + topPosition);
        // Extra Codes Here ETC...
    }
    </script>
    

    SO finally our last DEMO is at https://jsfiddle.net/qjkkqdg2/2/