javascript - Get the offset position of the caret in a textarea in pixels -
this question has answer here:
in project i'm trying offset position of caret in textarea
in pixels. can done?
before asking here, have gone through many links, tim down's, couldn't find solution works in ie8+, chrome , firefox. seems tim down working on this.
some other links have found have many issues not finding top offset of caret position.
i trying offset position of caret because want show auto-complete suggestion box inside textarea
positioning based on offset position of caret.
ps: can't use contenteditable div
because have written lots of code related textarea
.
here's approach using rangyinputs, rangy , jquery.
it copies whole text inside textarea
div
of same size. have set css ensure in every browser, textarea
, div
wrap content in same way.
when textarea
clicked, read out @ character index caret positioned, insert caret span
@ same index inside div
. doing ended having issue caret span
jumping previous line if user clicked @ start of line. fix check if previous character space
(which allow wrap occur), if true
, wrap in span
, , wrap next word (the 1 directly after caret position) in span
. compare top values between these 2 span
's, if differ, there wrapping going on, assume top
, left
value of #nextword
span
equivalent caret position.
this approach can still improved upon, i'm sure haven't thought of possibly go wrong, , if have, haven't bothered implementing fix of them don't have time @ moment, number of things need at:
it doesn't yet handle hard returns inserted enter(fixed)positioning breaks when entering multiple spaces in row(fixed)- i think hyphens allow content wrap occur well..
currently works same way across browsers here on windows 8 latest versions of chrome, firefox, ie , safari. testing has not been rigorous though.
here's jsfiddle.
i hope you, @ least might give ideas build on.
some features:
i have included
ul
positioned in right spot, , fixed firefox issuetextarea
selection not re-set original spot after dom manipulations.i have added ie7 - ie9 support , fixed multiple word selection issue pointed out in comments.
i have added support hard returns inserted enter , multiple spaces in row.
i have fixed issue default behaviour ctrl+shift+left arrow text selection method.
javascript
function gettextareaxandy() { // don't if key pressed left arrow if (e.which == 37) return; // save selection start var selection = $(this).getselection(); var index = selection.start; // copy text div $(this).blur(); $("div").text($(this).val()); // current character $(this).setselection(index, index + 1); currentcharacter = $(this).getselection().text; // 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 space or line break, find next word , wrap in span var linebreak = previouscharacter.match(/(\r\n|\n|\r)/gm) == undefined ? false : true; if (previouscharacter == ' ' || currentcharacter == ' ' || linebreak) { = index + 1; // start @ end of current space while (endchar != ' ' && end < $(this).val().length) { i++; $(this).setselection(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` @ position of 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 space, wrap in 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 selection start $(this).focus(); $(this).setselection(index, selection.end); // if top value of previous character span not equal top value of next word, // there must have been wrapping going on, previous character space, wrapping // have occured after space, safe assume left , top value of `#nextword` // indicate 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, there no wrapping, can take left , top value `#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 dummy text of printing , typesetting industry. lorem ipsum has been industry's standard dummy text ever since 1500s, when unknown printer took galley of type , scrambled make type specimen book.</textarea> <label></label> <ul> <li>why don't 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 , (-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/; }