Scott Van Vliet

Less Talk, More Rock

TextArea Cursor Position with JavaScript

I’d recently searched around for some good-quality JavaScript snippets to determine the cursor position within an HTML TextArea, but haven’t had any luck.  So, like any fellow geek would do, I came up with my own solution.

 

The DOM in IE does not provide information regarding relative position in terms of characters; however, it does provide bounding and offset values for browser-rendered controls.  Thus, I used these values to determine the relative bounds of a character. Then, using the JavaScript TextRange, I created a mechanism for working with such measures to calculate the Line and Column position for fixed-width fonts within a given TextArea.

 

First, the relative bounds for the TextArea must be calculated based upon the size of the fixed-width font used.  To do this, the original value of the TextArea must be stored in a local JavaScript variable and clear the value.  Then, a TextRange is created to determine the Top and Left bounds of the TextArea.

 

var storedValue = textBox.value;

 

textBox.value = "";

textBox.select();

 

var caretPos = document.selection.createRange();

textBox.__boundingTop = caretPos.boundingTop;

textBox.__boundingLeft = caretPos.boundingLeft;

 

Next, in order to capture the bounding width of a single fixed-width character, the value of the TextArea is set to a single character, selected, and another TextRange is created.

 

textBox.value = " ";

textBox.select();

 

caretPos = document.selection.createRange();

textBox.__boundingWidth = caretPos.boundingWidth;

textBox.__boundingHeight = caretPos.boundingHeight;

 

textBox.value = storedValue;

 

The values obtained from the initialization will persist in the instance of the TextArea, textBox.  Also, in order for this initialization to occur at least once, it must be registered with the onLoad event.

 

<body onload="initPosition(document.forms[0].txtLayoutViewer)">

 

Next, the TextArea must be configured to capture the cursor position.  Mouse and keyboard activity will be captured as follows.

 

 <textarea name="txtLayoutViewer"

   onmouseup="updatePosition(this)"

   onmousedown="updatePosition(this)"

   onkeyup="updatePosition(this)"

   onkeydown="updatePosition(this)"

   onfocus="updatePosition(this)"

   rows="15"

   cols="75"></textarea>

 

When the updatePosition method is called, another TextRange is created to capture the cursor selection.  Then, using the values calculated during the initialization and those in the TextRange, the Line and Column values are calculated as follows.

 

var caretPos = document.selection.createRange();

 

var boundingTop = (caretPos.offsetTop + textBox.scrollTop) - textBox.__boundingTop;

var boundingLeft = (caretPos.offsetLeft + textBox.scrollLeft) - textBox.__boundingLeft; 

 

textBox.__Line = (boundingTop / textBox.__boundingHeight) + 1;

textBox.__Column = (boundingLeft / textBox.__boundingWidth) + 1;

 

As with the bounds captured during initialization, the Line and Column values persist in the instance of the TextArea.  They can then be used be other elements throughout the DOM. 

 

Below is the complete code listing.

 

<html>

<head>

    <script language="JavaScript" type="text/javascript">

    <!--

        function initPosition(textBox) {

            var storedValue = textBox.value;

            textBox.value = "";

            textBox.select();

 

            var caretPos = document.selection.createRange();

            textBox.__boundingTop = caretPos.boundingTop;

            textBox.__boundingLeft = caretPos.boundingLeft;

                    

            textBox.value = " ";

            textBox.select();

 

            caretPos = document.selection.createRange();

            textBox.__boundingWidth = caretPos.boundingWidth;

            textBox.__boundingHeight = caretPos.boundingHeight;

 

            textBox.value = storedValue;

        }

 

        function storePosition(textBox) {

            var caretPos = document.selection.createRange();

 

            var boundingTop = (caretPos.offsetTop + textBox.scrollTop) - textBox.__boundingTop;

            var boundingLeft = (caretPos.offsetLeft + textBox.scrollLeft) - textBox.__boundingLeft;

 

            textBox.__Line = (boundingTop / textBox.__boundingHeight) + 1;

            textBox.__Column = (boundingLeft / textBox.__boundingWidth) + 1;

        } 

 

        function updatePosition(textBox) {

            storePosition(textBox);

            document.forms[0].txtLine.value = textBox.__Line;

            document.forms[0].txtColumn.value = textBox.__Column;

        }

    //-->

    </script>

    <style type="text/css">

        body, td, tg, input, select {

            font-family: Verdana;

            font-size: 10px;

        }

    </style>

</head>

<body onload="initPosition(document.forms[0].txtLayoutViewer)">

    <form>

        <table cellspacing="0" cellpadding="3">

            <tr>

                <td colspan="3">

                    Change Font Size

                    <select onchange="this.form.txtLayoutViewer.style.fontSize = this.options[this.selectedIndex].value; initPosition(this.form.txtLayoutViewer);">

                        <option value="10">10px</option>

                        <option value="12">12px</option>

                        <option value="14">14px</option>

                        <option value="16">16px</option>

                        <option value="18">18px</option>

                        <option value="20">20px</option>

                        <option value="24">24px</option>

                        <option value="36">36px</option>

                    </select>

                </td>

            </tr>

            <tr>

                <td colspan="3">

                    <textarea name="txtLayoutViewer"

                        onmouseup="updatePosition(this)"

                        onmousedown="updatePosition(this)"

                        onkeyup="updatePosition(this)"

                        onkeydown="updatePosition(this)"

                        onfocus="updatePosition(this)"

                        rows="15"

                        cols="75">Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec ornare aliquam quam. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos hymenaeos. Pellentesque et quam in dui consequat tempor. Etiam lorem lectus, sollicitudin laoreet, tincidunt nec, pharetra in, magna. Mauris accumsan velit et augue. Cum sociis natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.</textarea></td>

            </tr>

            <tr>

                <td width="70%">

                    &nbsp;</td>

                <td width="10%">

                    Line <input type="text" name="txtLine" style="width: 25px" readonly></td>

                <td width="20%">

                    Column <input type="text" name="txtColumn" style="width: 25px" readonly></td>

            </tr>

        </table>

    </form>

</body>

</html>

 

Please note that this has only been tested on IE 6.  As always, I welcome your feedback and constructive criticism.

UPDATE 3/29/3005:

A friend of mine filled me in on a limitation to this approach.  If the TextArea extends beyond the screen, thus requiring scrolling, the calculated values will be incorrect due to the fact that the bounds used in the calculation change when the window has scrolled.  Moreover, if the text you are working with requires that you scroll beyond the viewable portion of the TextArea, the calculated values will reverse.  In order to fix this, the scolling index of the screen and/or TextArea must be obtained to offset the calcuation.  Thus, if you plan on using this approach ensure that the requirements are such that the TextArea does not require scrolling.  Otherwise, if you feel so inclined, email with your thoughts on obtaining the offset value required when scrolling.  Thanks!

UPDATE 4/12/2005:

Thanks to Mr. Noisy, the previous bug listed (TextArea scrolling) has been fixed, and now accounts for the scrollTop and scrollLeft properties of the TextArea.  I've updated the code in this post to reflect the changes.

Comments

sunrise said:

bug!
at the end of the text press enter
the line no chang to
9.133333333333332
-26.866666666666667
....
# March 25, 2005 4:59 AM

Scott Van Vliet said:

What version of IE are you using? I an using IE6 SP2, and I cannot reproduce the bug you are describing.
# March 25, 2005 9:50 AM

Erik Porter said:

Neato, thanks for sharing! :)
# March 25, 2005 10:34 AM

Mr Noisy said:

This is a very good script idea which I intended to use, however not being able to scroll beyond the visible window made it in-practical. So, being a fellow geek I played around with it and found that by replacing the variables "caretPos.boundingTop" with "(caretPos.offsetTop+textBox.scrollTop)" and "caretPos.boundingLeft" with "(caretPos.offsetLeft+textBox.scrollLeft)" I was able to get it to work beyond the visible area of the textarea.
# April 12, 2005 2:49 AM

aflorin said:

Check out the link below for a cleaner approach

http://www.faqts.com/knowledge_base/view.phtml/aid/1052/fid/130
# April 21, 2005 4:19 PM

Scott Van Vliet said:

aflorin,

Thanks for your message. Indeed the link you posted was good, the script therein does not provide the caret position by Line Number and Column; whereas, the solution I provided does -which is why I posted it ;)
# April 21, 2005 4:35 PM

Thay said:

Thanks scott for the code. when i copy and paste your code on an empty aspx, it works fine. but when i integrate that code into my aspx the column just shows in decimal number like 2.66666 which should be 2 only

# August 2, 2006 11:43 PM

jmnobre said:

Great approach! Does not feet all needs, but was a great start for what I am trying to do! :)

# August 6, 2006 3:29 AM

Chesso said:

You do realize that you can just check the selectionStart to find the caret position much like you would for the selected text?

Just check if the length of the selected text is less than 1, in which case nothing is selected and the start and end selection will simply return the position the caret is in.

Works fine for me in FireFox using this method. I use it for inserting opening and closing tags in my editor at the caret position.

# September 2, 2006 10:50 PM

Strifer said:

There's a much simpler way of getting selection start and end offsets in IE than using pixels and what not:

-----------------------------------------------------

// get current selection

var range = document.selection.createRange();

// make sure it's the textarea's selection

if (range.parentElement().id == 'my_textarea_id')

{

  // create a selection of the whole textarea

  var range_all = document.body.createTextRange();

  range_all.moveToElementText(my_textarea_obj);

  // calculate selection start point by moving

  // beginning of range_all to beginning of range

  for (var sel_start = 0; range_all.compareEndPoints('StartToStart', range) < 0; sel_start ++)

  {

     range_all.moveStart('character', 1);

  }

  // calculate selection end point by moving

  // end of range_all to end of range

  for (var sel_end = my_textarea_obj.innerHTML.length; range_all.compareEndPoints('EndToEnd', range) > 0; sel_end --)

  {

     range_all.moveEnd('character', -1);

  }

  // sel_start and sel_end hold now the offsets!

}

# September 4, 2006 12:45 PM

Strifer said:

Edit:

------

use: my_textarea_obj.value.length

instead of: my_textarea_obj.innerHTML.length

# September 4, 2006 2:03 PM

Strifer said:

Ups on more thing:

IE doesn't include line breaks (\n) in it's selection, so if you do my_textarea_obj.value.length it will always be longer than the selection if there's more than 1 line of text.

Solution:

---------

// get the number of line breaks in textarea

var line_break_count = 0;

var line_breaks = my_textarea_obj.value.match(/\n/g);

if (line_breaks != null)

  line_break_count = line_breaks.length;

And then use it in this line:

for (var sel_end = my_textarea_obj.innerHTML.length - line_break_count; range_all.compareEndPoints('EndToEnd', range) > 0; sel_end --)

Now it should work flawlessly!

# September 6, 2006 6:09 AM

Strifer said:

Argh IE will drive me nuts some day! I had to do one more adjustment. Anyway here's now the complete code for getting the textarea selection offsets cross-browser as well as the selected text:

----------------------------------------------------

// get selection in firefox, opera, ...

if (typeof(textarea.selectionStart) == 'number')

{

  // get start and end points of selected text

  textarea.sel_start = textarea.selectionStart;

  textarea.sel_end = textarea.selectionEnd;

  // get selected and surrounding text

  textarea.sel_text = bbt_textarea.value.substring(textarea.sel_start, textarea.sel_end);

  textarea.sel_text_pre = textarea.value.substring(0, textarea.sel_start);

  textarea.sel_text_post = textarea.value.substring(textarea.sel_end, textarea.value.length);

}

// get selection in IE

else if (document.selection)

{

  // make sure it's the textarea's selection

  var range = document.selection.createRange();

  if (range.parentElement().id == 'textarea_id')

  {

     // create a selection of the whole textarea

     var range_all = document.body.createTextRange();

     range_all.moveToElementText(textarea);

     // calculate selection start point by moving beginning of range_all to beginning of range

     for (var sel_start = 0; range_all.compareEndPoints('StartToStart', range) < 0; sel_start ++)

       range_all.moveStart('character', 1);

     // get number of line breaks from textarea start to selection start and add them to sel_start

     for (var i = 0; i <= sel_start; i ++)

     {

       if (textarea.value.charAt(i) == '\n')

         sel_start ++;

     }

     textarea.sel_start = sel_start;

     // create a selection of the whole textarea

     var range_all = document.body.createTextRange();

     range_all.moveToElementText(textarea);

     // calculate selection end point by moving beginning of range_all to end of range

     for (var sel_end = 0; range_all.compareEndPoints('StartToEnd', range) < 0; sel_end ++)

       range_all.moveStart('character', 1);

     // get number of line breaks from textarea start to selection end and add them to sel_end

     for (var i = 0; i <= sel_end; i ++)

     {

       if (textarea.value.charAt(i) == '\n')

         sel_end ++;

     }

     textarea.sel_end = sel_end;

     // get selected and surrounding text

     textarea.sel_text = range.text;

     textarea.sel_text_pre = textarea.value.substring(0, textarea.sel_start);

     textarea.sel_text_post = textarea.value.substring(textarea.sel_end, textarea.value.length);

  }

}

// this browser doesn't support javascript selections

else

{

  ...

}

----------------------------------------------------

I used the textarea object to store the selections in it:

selection start offset: textarea.sel_start

selection end offset: textarea.sel_end

selected text: textarea.sel_text

text left of selection: textarea.sel_text_pre

text right of selection: textarea.sel_text_post

# September 6, 2006 6:50 AM

Jake said:

Your solution is great.  It seems like this would work on any HTML element.

Here is my textarea solution: http://linebyline.blogspot.com/2006/11/textarea-cursor-position-in-internet.html

# November 10, 2006 4:21 PM

vov4ik said:

about your script:

I have had to use textarea.focus() before that portion of script for IE. Without such change I recieved "undefined" instead of all textarea.sel_* values.

# November 29, 2006 5:45 AM

Joju said:

Thanks Scott !!!

It works like a magic. it even work with non-fixed size fonts :-)

thanks for your efforts.

Joju

# January 8, 2007 11:19 PM

ajay said:

I have come up with very efficient approach to do this  

here inputEl  param is the text area or text field object and lastCharacter is the cursor position you want to set

function setCursorPosition(inputEl , lastCharacter) {

if (inputEl.setSelectionRange) {  

inputEl.focus();  

inputEl.setSelectionRange(selStart, selEnd);

} else if (inputEl.createTextRange) {  

var range = inputEl.createTextRange();

range.move("character", lastCharacter);  

range.select();  

}

}

# July 3, 2007 3:04 AM

hunters said:

<a href= http://index1.myfty.com >realtors associations</a>

# December 10, 2007 4:28 PM

Steve said:

Nice article. With some fix now it working great! ;)

# May 6, 2008 10:14 AM

Rupesh Kumar said:

Hi , I have tested this code it is working in IE. But it's not working for Mozila. How it work for mozila? any one can answer me?

# July 4, 2008 5:06 AM

nayana adassuriya said:

any of above solutions are not clear. plz can anybody can help to set cursor to the specific position together with cross browser capability.

# October 16, 2008 2:03 AM

Parker said:

The code that you have presented helped me a lot.I am facing a problem with this code.Whenever the textarea is

going beyond the visibility of the page the column position is being displayed as (2.666) in this way.Please help me in solving the code.This ie very Urgent for me.

# October 30, 2008 7:37 AM

harish lavangade said:

Very good but something is must be add...

harish

# January 9, 2009 6:51 AM

ghg said:

I need to deal with Unicode.

How about fire keydownEvent. and test the special character background??

# January 14, 2009 8:52 PM

ghg said:

very Good.

Thanks a lots!!!

# January 14, 2009 9:12 PM

ograniczenie ilosci linii w textarea | hilpers said:

Pingback from  ograniczenie ilosci linii w textarea | hilpers

# January 22, 2009 11:03 PM

javascript??????textarea?????????????????????????????????(IE, Firefox) | ??????????????? said:

Pingback from  javascript??????textarea?????????????????????????????????(IE, Firefox) | ???????????????

# March 15, 2009 10:04 AM

Omer Katz said:

Hmm. It apears that it doesn't work in Firefox. any reason why?

# March 24, 2009 7:54 AM

javascript??????textarea?????????????????????????????????(IE, Firefox) « OnlyLonely & Marshall’s said:

Pingback from  javascript??????textarea?????????????????????????????????(IE, Firefox) &laquo;  OnlyLonely &amp; Marshall&#8217;s

# May 4, 2009 11:54 AM
Leave a Comment

(required) 

(required) 

(optional)

(required)