Get the offset position of the caret in a textarea in pixels [duplicate]

You can create a separate (invisible) element and fill it with textarea content from start to the cursor position. Textarea and the “clone” should have matching CSS (font properties, padding/margin/border and width). Then stack these elements on top of each other.

Let me start with a working example, then walk through the code: http://jsfiddle.net/g7rBk/

Updated Fiddle (with IE8 fix)

HTML:

<textarea id="input"></textarea>
<div id="output"><span></span></div>
<div id="xy"></div>

Textarea is self-explanatory. Output is a hidden element to which we’ll pass text content and make measures. What’s important is that we’ll use an inline element. the “xy” div is just an indicator for testing purposes.

CSS:

/* identical styling to match the dimensions and position of textarea and its "clone"
*/
#input, #output {
    position:absolute;
    top:0;
    left:0;
    font:14px/1 monospace;
    padding:5px;
    border:1px solid #999;
    white-space:pre;
    margin:0;
    background:transparent;
    width:300px;
    max-width:300px;
}
/* make sure the textarea isn't obscured by clone */
#input { 
    z-index:2;
    min-height:200px;
}

#output { 
    border-color:transparent; 
}

/* hide the span visually using opacity (not display:none), so it's still measurable; make it break long words inside like textarea does. */
#output span {
    opacity:0;
    word-wrap: break-word;
    overflow-wrap: break-word;
}
/* the cursor position indicator */
#xy { 
    position:absolute; 
    width:4px;
    height:4px;
    background:#f00;
}

JavaScript:

/* get references to DOM nodes we'll use */
var input = document.getElementById('input'),
    output = document.getElementById('output').firstChild,
    position = document.getElementById('position'),

/* And finally, here it goes: */
    update = function(){
         /* Fill the clone with textarea content from start to the position of the caret. You may need to expand here to support older IE [1]. The replace /\n$/ is necessary to get position when cursor is at the beginning of empty new line.
          */
         output.innerHTML = input.value.substr( 0, input.selectionStart ).replace(/\n$/,"\n\001");

        /* the fun part! 
           We use an inline element, so getClientRects[2] will return a collection of rectangles wrapping each line of text.
           We only need the position of the last rectangle.
         */
        var rects = output.getClientRects(),
            lastRect = rects[ rects.length - 1 ],
            top = lastRect.top - input.scrollTop,
            left = lastRect.left+lastRect.width;
        /* position the little div and see if it matches caret position :) */
        xy.style.cssText = "top: "+top+"px;left: "+left+"px";
    }

[1] Caret position in textarea, in characters from the start

[2] https://developer.mozilla.org/en/docs/DOM/element.getClientRects

Edit: This example only works for fixed-width textarea. To make it work with user-resizable textarea you’d need to add an event listener to the resize event and set the #output dimensions to match new #input dimensions.

Leave a Comment