Given a textarea with a not fixed width font, I want to know on key up if the caret (as given by element.selectionEnd
) is in the first line or in the last line of the text.
To avoid bad answers, here are some solutions which don't work:
\n
: A sentence can be broken in two lines, because it's too long for the textarea's width.Here's a fiddle for the tests and to remove some ambiguity (yes, it's a textarea
element, and it's not only one line, etc.): http://jsbin.com/qifezupu/4/edit
Notes:
The best solution for now :
Can I see it working ?
I made a fiddle which makes it obvious how it works : http://jsbin.com/qifezupu/31/edit
How to use it :
I made a jQuery plugin : https://github.com/Canop/taliner.js
Here's the related demonstration page : http://dystroy.org/demos/taliner/demo.html
The code :
$.fn.taliner = function(){
var $t = this,
$d = $('<div>').appendTo('body'),
$s1 = $('<span>').text('a').appendTo($d),
$s2 = $('<span>').appendTo($d),
lh = $s1.height();
$d.css({
width: $t.width(),
height: $t.height(),
font: $t.css('font'),
fontFamily: $t.css('fontFamily'), // for FF/linux
fontSize: $t.css('fontSize'),
whiteSpace : 'pre-wrap',
wordWrap : 'break-word',
overflowY: 'auto',
position: 'fixed',
bottom: 0,
left: 0,
padding: 0,
zIndex: 666
});
var lh = $s1.height(),
input = this[0],
se = input.selectionEnd,
v = input.value,
res = {};
$s1.text(v);
res.linesNumber = $s1.height()/lh|0;
$s1.text(v.slice(0, se));
$s2.text(v.slice(se));
res.caretOnFirstLine = input.selectionEnd===0
|| ($s1.height()<=lh && $s1.offset().top===$s2.offset().top);
res.caretOnLastLine = $s2.height()===lh;
$d.remove();
return res;
}
Does it really work ?
There's still a problem. The only information we get in JavaScript regarding the caret position is element.selectionEnd
. And this is very poor.
Here are the possible caret positions on a two lines textarea :
| A | B | C |
| D | E |
As you can see, there are more possible caret positions than inter-character positions. More specifically you have two different caret positions between the C and the D but the DOM doesn't provide any way to distinguish between them.
This means that sometimes, if you click at the right of the end of the line, the library won't be able to tell if it's the end of the line or the start of the next line.
There are several methods of obtaining caret position (in pixels) inside a textarea:
Offset possition of the caret in a textarea in pixels
Careful, they might have issues with different browsers.
You can set a predefined line height or font size that you later use to obtain caret position.
I didn't tested any of the methods but let's assume that the function used to get caret position (in pixels) returns the y component with respect to the bottom of the caret.
You specify a line height of 15 px.
Let's say method returns (20, 45). Current line = y/15 = 45/15 = 3
JS Fiddle to demonstrate how it can be done based on Dan's solution:
Current line from caret coordinates
I added a span where the current line is displayed. Sadly, my tests on Chrome shows that this is not 100% accurate: When you select third line with mouse it says you are on 4th line.
Line:
<span id="line">0</span>
At the end of the update function (in HTML section) I added the following line:
//24 is the top padding of the textarea
//16 is the line-height
line.innerText = (coordinates.top - 24) / 16;
Another solution based on what @Flater said about adding up the width of the text. The idea (in summary) is:
Code:
$('#t').keyup(function(e){
first_line="true";
last_line="true";
text = $(this).val();
width = $(this).width();
var cursorPosition = $(this).prop("selectionStart");
txtBeforeCaret = text.substring(0, cursorPosition);
txtAfterCaret = text.substring(cursorPosition);
widthBefore = $('#holder').text(txtBeforeCaret).width();
widthAfter = $('#holder').text(txtAfterCaret).width();
match1 = txtBeforeCaret.match(/\n/);
if(txtAfterCaret!==null){match2=txtAfterCaret.match(/\n/g);}
if(widthBefore>width || match1){first_line="false";}
if(widthAfter>width || (match2!==null && match2.length)) {last_line="false";}
$('#f').html(first_line);
$('#l').html(last_line);
});
#holder is a div
created and hidden (to find the text width).
DEMO
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With