How to Get the (X, Y) Pixel Coordinates of the Caret in Text Boxes

How do I get the (x, y) pixel coordinates of the caret in text boxes?

I've looked for a textarea caret coordinates plugin for meteor-autocomplete, so I've evaluated all the 8 plugins on GitHub. The winner is, by far, textarea-caret-position from Component.

Features

  • pixel precision
  • no dependencies whatsoever
  • browser compatibility: Chrome, Safari, Firefox (despite two bugs it has), IE9+; may work but not tested in Opera, IE8 or older
  • supports any font family and size, as well as text-transforms
  • the text area can have arbitrary padding or borders
  • not confused by horizontal or vertical scrollbars in the textarea
  • supports hard returns, tabs (except on IE) and consecutive spaces in the text
  • correct position on lines longer than the columns in the text area
  • no "ghost" position in the empty space at the end of a line when wrapping long words

Here's a demo - http://jsfiddle.net/dandv/aFPA7/

Sample Image

How it works

A mirror <div> is created off-screen and styled exactly like the <textarea>. Then, the text of the textarea up to the caret is copied into the div and a <span> is inserted right after it. Then, the text content of the span is set to the remainder of the text in the textarea, in order to faithfully reproduce the wrapping in the faux div.

This is the only method guaranteed to handle all the edge cases pertaining to wrapping long lines. It's also used by GitHub to determine the position of its @ user dropdown.

Caret position in pixels in an input type text (not a textarea)

Using an invisible faux element

You can accomplish this by using an absolutely positioned invisible (using visibility not display) faux element that has all the same CSS properties as your input text box (fonts, left borders and left padding).

This very short and easy to understand JSFiddle is a starting point how this script should be working.

It works in Chrome and Firefox as is. And it seems it should be working in IE9+ as well.

Internet Explorer 8 (and down) would need some additional code to get caret position from start of text within input text box. I've even added a meter at the top to show a line every 10 pixels so you can see whether it measures correctly or not. Mind that lines are at 1, 11, 21,... pixel positions.

What this example does it actually takes all the text in text box up to caret position and puts it inside the faux element and then measures its width in pixels. This gets you offset from left of text box.

When it copies text to faux element it also replaces normal spaces with non-breaking ones so they actually get rendered otherwise if you'd position caret right after space you'd get wrong position:

var faux = $("#faux");
$("#test").on("keyup click focus", function(evt) {
// get caret offset from start
var off = this.selectionStart;

// replace spaces with non-breaking space
faux.text(this.value.substring(0, off).replace(/\s/g, "\u00a0"));
});​

Mind that faux's right dimensions

  • padding
  • border
  • margin

have been removed in order to get correct value, otherwise element would be too wide. Element's box has to end right after the text it contains.

Caret's position in pixels from start of input box is then easily gotten from:

faux.outerWidth();

The problem

There is one problem though. I'm not sure how to handle situation when text within text box is scrolled (when too long) and caret isn't at the very end of text but somewhere in between... If it's at the end then caret position is always at maximum position possible (input width less right dimensions - padding, border, margin).

I'm not sure if it's possible to get text scroll position within text box at all? If you can then even this problem can be solved. But I'm not aware of any solution to this...

Hope this helps.

Get the offset position of the caret in a textarea in pixels

Here's an approach using rangyinputs, rangy and jQuery.

It basically copies the whole text from inside the textarea into a div of the same size. I have set some CSS to ensure that in every browser, the textarea and the div wrap their content in exactly the same way.

When the textarea is clicked, I read out at which character index the caret is positioned, then I insert a caret span at the same index inside the div. By only doing that I ended up having an issue with the caret span jumping back to the previous line if the user clicked at the start of a line. To fix that I check if the previous character is a space (which would allow a wrap to occur), if that is true, I wrap it in a span, and I wrap the next word (the one directly after the caret position) in a span. Now I compare the top values between these two span's, if they differ, there was some wrapping going on, so I assume that the top and the left value of the #nextword span are equivalent to the caret position.

This approach can still be improved upon, I'm sure I haven't thought of everything that could possibly go wrong, and even if I have, then I haven't bothered implementing a fix for all of them as I don't have the time to do so at the moment, a number of things that you would need to look at:

  • it doesn't yet handle hard returns inserted with Enter (fixed)
  • positioning breaks when entering multiple spaces in a row (fixed)
  • I think hyphens would allow a content wrap to occur as well..

Currently it works exactly the same way across browsers here on Windows 8 with the latest versions of Chrome, Firefox, IE and Safari. My testing has not been very rigorous though.

Here's a jsFiddle.

I hope it will help you, at the very least it might give you some ideas to build on.

Some Features:

  • I have included a ul for you which is positioned in the right spot, and fixed a Firefox issue where the textarea selection was not re-set back to its original spot after the DOM manipulations.

  • I have added IE7 - IE9 support and fixed the multiple word selection issue pointed out in the comments.

  • I have added support for hard returns inserted with Enter and multiple spaces in a row.

  • I have fixed an issue with the default behaviour for the ctrl+shift+left arrow text selection method.

JavaScript

function getTextAreaXandY() {

// Don't do anything if key pressed is left arrow
if (e.which == 37) return;

// Save selection start
var selection = $(this).getSelection();
var index = selection.start;

// Copy text to div
$(this).blur();
$("div").text($(this).val());

// Get current character
$(this).setSelection(index, index + 1);
currentcharacter = $(this).getSelection().text;

// Get previous character
$(this).setSelection(index - 1, index)
previouscharacter = $(this).getSelection().text;

var start, endchar;
var end = 0;
var range = rangy.createRange();

// If current or previous character is a space or a line break, find the next word and wrap it in a span
var linebreak = previouscharacter.match(/(\r\n|\n|\r)/gm) == undefined ? false : true;

if (previouscharacter == ' ' || currentcharacter == ' ' || linebreak) {
i = index + 1; // Start at the end of the current space
while (endchar != ' ' && end < $(this).val().length) {
i++;
$(this).setSelection(i, i + 1)
var sel = $(this).getSelection();
endchar = sel.text;
end = sel.start;
}

range.setStart($("div")[0].childNodes[0], index);
range.setEnd($("div")[0].childNodes[0], end);
var nextword = range.toHtml();
range.deleteContents();
var position = $("<span id='nextword'>" + nextword + "</span>")[0];
range.insertNode(position);
var nextwordtop = $("#nextword").position().top;
}

// Insert `#caret` at the position of the caret
range.setStart($("div")[0].childNodes[0], index);
var caret = $("<span id='caret'></span>")[0];
range.insertNode(caret);
var carettop = $("#caret").position().top;

// If preceding character is a space, wrap it in a span
if (previouscharacter == ' ') {
range.setStart($("div")[0].childNodes[0], index - 1);
range.setEnd($("div")[0].childNodes[0], index);
var prevchar = $("<span id='prevchar'></span>")[0];
range.insertNode(prevchar);
var prevchartop = $("#prevchar").position().top;
}

// Set textarea selection back to selection start
$(this).focus();
$(this).setSelection(index, selection.end);

// If the top value of the previous character span is not equal to the top value of the next word,
// there must have been some wrapping going on, the previous character was a space, so the wrapping
// would have occured after this space, its safe to assume that the left and top value of `#nextword`
// indicate the caret position
if (prevchartop != undefined && prevchartop != nextwordtop) {
$("label").text('X: ' + $("#nextword").position().left + 'px, Y: ' + $("#nextword").position().top);
$('ul').css('left', ($("#nextword").position().left) + 'px');
$('ul').css('top', ($("#nextword").position().top + 13) + 'px');
}
// if not, then there was no wrapping, we can take the left and the top value from `#caret`
else {
$("label").text('X: ' + $("#caret").position().left + 'px, Y: ' + $("#caret").position().top);
$('ul').css('left', ($("#caret").position().left) + 'px');
$('ul').css('top', ($("#caret").position().top + 14) + 'px');
}

$('ul').css('display', 'block');
}

$("textarea").click(getTextAreaXandY);
$("textarea").keyup(getTextAreaXandY);

HTML

<div></div>
<textarea>Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book.</textarea>
<label></label>
<ul>
<li>Why don't you type this..</li>
</ul>

CSS

body {
font-family: Verdana;
font-size: 12px;
line-height: 14px;
}
textarea, div {
font-family: Verdana;
font-size: 12px;
line-height: 14px;
width: 300px;
display: block;
overflow: hidden;
border: 1px solid black;
padding: 0;
margin: 0;
resize: none;
min-height: 300px;
position: absolute;
-moz-box-sizing: border-box;
white-space: pre-wrap;
}
span {
display: inline-block;
height: 14px;
position: relative;
}
span#caret {
display: inline;
}
label {
display: block;
margin-left: 320px;
}
ul {
padding: 0px;
margin: 9px;
position: absolute;
z-index: 999;
border: 1px solid #000;
background-color: #FFF;
list-style-type:none;
display: none;
}
@media screen and (-webkit-min-device-pixel-ratio:0) {
span {
white-space: pre-wrap;
}
}
div {
/* Firefox wrapping fix */
-moz-padding-end: 1.5px;
-moz-padding-start: 1.5px;
/* IE8/IE9 wrapping fix */
padding-right: 5px\0/;
width: 295px\0/;
}
span#caret
{
display: inline-block\0/;
}

Find caret position in textarea in pixels

This "raw code" works at least in IE.

Using the code you can put your <TEXTAREA> where ever you want in page, and the <DIV id="db"> will follow it. Even despite of the scrolling position of the page. You can fix the position of <DIV> by changing the literal numbers at d.style...6-statements.

<body>
<div id="db" style="position:absolute;left:-20px;top:-20px;border:1px solid red;">V</div>
<br><br><br>
<form id="props">
<textarea id="ta" style="width:200px;height:100px;" onkeyup="moveDiv();"></textarea>
</form>

<script>
<!--
var b=document.body;
var d=document.getElementById('db');
var a=document.getElementById('ta');

function moveDiv(){
var sel=document.selection;
var targ=sel.createRange();
d.style.top=a.offsetTop+a.clientTop-d.clientHeight-6;
d.style.left=targ.offsetLeft+b.scrollLeft-6;
return;
}
// -->
</script>
</body>

Positioning of the <DIV> is not quite exact, but gets better when using fixed-width font in <TEXTAREA>.

Finding the position of the caret in a TextBox

Found this article describing how to do what I need done. Turns out you can set both the PlacementTarget of the textbox and the PlacementRectangle from GetRectFromCharacterIndex and it will work.

How can I find the cursor location (X/Y, not line/column) in an HTML textarea?

With an editable html input (in an iframe like CKeditor or Rich Text Editor or even better: jsfiddle) you could insert an empty span element at the caret position and get the position of that element to display your dropdown.

It might seem complex but I can't think of any other way to do this.

It has some extra possibilities when used for a code editor, you could color-code the text and format code like jsfiddle does, and maybe even build some kind of code-auto-complete for keywords etc.



Related Topics



Leave a reply



Submit