Monday, May 20, 2024
 Popular · Latest · Hot · Upcoming
28
rated 0 times [  31] [ 3]  / answers: 1 / hits: 24326  / 11 Years ago, thu, april 25, 2013, 12:00:00

In my project I'm trying to get the offset position of the caret in a textarea in pixels. Can this be done?



Before asking here, I have gone through many links, especially Tim Down's, but I couldn't find a solution which works in IE8+, Chrome and Firefox. It seems Tim Down is working on this.



Some other links which I have found have many issues like not finding the top offset of the caret position.



I am trying to get the offset position of the caret because I want to show an auto-complete suggestion box inside the textarea by positioning it based on the offset position of the caret.



PS: I can't use a contenteditable div because I have written lots of code related to a textarea.


More From » jquery

 Answers
-1

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(/(rn|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/;
width: 295px/;
}
span#caret
{
display: inline-block/;
}

[#78633] Wednesday, April 24, 2013, 11 Years  [reply] [flag answer]
Only authorized users can answer the question. Please sign in first, or register a free account.
tylerdamiena

Total Points: 139
Total Questions: 90
Total Answers: 118

Location: Liechtenstein
Member since Wed, Dec 8, 2021
3 Years ago
tylerdamiena questions
;