Text align by word length - javascript

I have been working on a project that has a similar result as the spritz app. Basically, it takes a string, turns each word into an array and then displays each output one at a time. The problem is that I can't seem to find a way to align each word based on the word length (see link).
Here is a demo of the spritz output that I am looking for: http://imgur.com/a/UlZ6W
Does anyone know how to center an output based on the the length of a word?
IE:
If array length is 1 letter, center letter
If array length is 2 - 4, center second letter
if array length is 5 - 7, center third letter
ect.

A start that might could work for you.
http://jsfiddle.net/kimiliini/ap64C/
Use an element to measure width. Calculate width of word to middle w/wo center letter.
For example having the element:
<span id="tw"></span>
and CSS:
#tw {
position: absolute;
visibility: hidden;
white-space: nowrap;
font: bold 18px Arial;
}
One can:
// get element
tw = document.getElementById('tw');
// set text
tw.textContent = 'foo';
// get width
tw.offsetWidth;
Having this as base we can split word into three groups:
Letters belonging to the left of center letter.
Letters for left + center.
Letters after center.
Example. Having the word starting and a box where we want to center at offset 110 pixels from left.
length = 8
left = sta
center = r
right = ting
width_left = 15
width_left + width_center = 19
mid = (19 - 15) / 2
box margin left = 110 - (width_left + mid)
Simple, should be refactored code, sample:
// span's for left, center and right
var tx = {
L: document.getElementById('txt_L'),
C: document.getElementById('txt_C'),
R: document.getElementById('txt_R')
};
// Box to that holds span's, to set margin left on.
var b = document.getElementById('bw');
function width(w) {
tw.textContent = w;
return tw.offsetWidth;
}
function middle(w) {
var n = w.length,
// Center char calculation. 1=1, 2-5=2, 6-8=3, ...
c = ~~((n + 1) / 3) + 1,
z = {};
z.a = width(w.substr(0, c - 1));
z.b = width(w.substr(0, c));
z.c = (z.b - z.a) / 2;
b.style.marginLeft = ~~(110 - (z.a + z.c)) + 'px';
tx.L.textContent = w.substr(0, c - 1);
tx.C.textContent = w.substr(c - 1, 1);
tx.R.textContent = w.substr(c);
}

Some Background On Your Problem
I believe you're going to find this either very difficult to solve or else are going to have to railroad you users quite dramatically.
Words don't have lengths, because characters aren't all of the same size, and even the space between letters is different The exception is of course monospaced fonts (and they aren't really an exception they just behave like one).
To achieve what you're after, you need to know the precise size of each letter, and for real accuracy would need to know the precise size of each letter in relation to it's adjacent letters. But that's typography nitty-gritty and probably not what you want to do.
If it were me, I'd artificially do it with tags, whose width you could specify (say, your largest character + 1 px). Then word length will always be equivalent to some multiple of the parent element.
The other option would be to render the text as image via HTML5 canvas, which could be made to autofit the word and then would have, itself, a width property.
A Solution to Consider
Try something like:
<style>
li.char {
display:inline-block;
text-align:center;
width:4px;
}
li.char.center { color:red}
</style>
<body>
...
<div style="text-align:center">
<ul id="spritztext"> </ul>
</div>
<script>
var my_string = "strings are cool";
var center = ceil( my_string.len() / 2);
/* rounding up/down is a bad solution to finding the center character, for what it's worth. examine the words 'will' vis 'limb' in a proportional font and you'll see why*/
var count = 0;
$(my_string).each(function() {
var class = count == center ? "char center" : "char";
count++
var char_element ="<li class='" + class +"'>" + my_string[count] + "</li>";
$("#spritztext").append(char_element)
});
TLDR: text isn't graphics; you need a monospaced font, a rendering strategy, or a hack via parent elements

One solution may be divide the word into two parts and use a table-like two column layout to show the word. Like this:
<table style="border-collapse: collapse; width: 200px;"><tr>
<td style="text-align: right; width: 100px; padding: 0;">wo</td>
<td style="text-align: left; width: 100px; padding: 0;">rd</td>
</tr></table>
This doesn't exactly align a letter center at one position, but could align left or right edge of a letter exactly at some position.
To refine the solution, you may need to get letter/character width on demand (By creating a temp 'span' node with inner text of the letter, then get the offsetWidth). Then adjust the margin of the two parts to achieve the exactly goal.

Related

How to find css unit for this number

I have a input type text
<input type="text">
Basically I am using javascript ClientRect to get caret details. ClientRect looks like this
[object ClientRect]
{
[functions]: ,
__proto__: { },
bottom: 540.7999877929687,
constructor: { },
height: 24,
left: 1034.5399169921875,
right: 1034.5399169921875,
top: 516.7999877929687,
width: 0
}
This is generated on everytext input.
left: 1034.5399169921875,
left: 1065.5399169921875,
left: 1078.5399169921875,
I want to convert this number to CSS units like px/%/rem/vh. So that I can put dynamic css. How to do it?
Try accessing the left position of your input and subtract the left position of your caret. This should give you an approximate width of the text in the input, if that's what you are looking for. You'll need to add an id or create a selector for your text input.
var inputElementRect = document.getElementById('YOURINPUTID').getBoundingClientRect()
var width = inputElementRect.left - caretRect.left
Those values are px by default .. so just add suffix as px to that value and use it.
<input type="text">
to get that value
let text = document.querySelector('input');
let values = text.getBoundingClientRect();
let top_value = values.top + 'px';
let bottom_value = values.bottom + 'px';
let width_value = values.width + 'px';
let height_value = values.height + 'px';
console.log('top: '+ top_value);
console.log('bottom: '+ bottom_value);
console.log('width: '+ width_value);
console.log('height: '+ height_value);
here properties other than width and height are relative to the view port ( top, bottom, left, right ) ,
so if scroll this values will changes ..
to get the perfect values even if scroll add this values with window.scrollX , window.scrollY or can use window.pageXOffset , window.pageYOffset
So if I understand the question correctly, you have position values for the cursor inside of the input and you want to convert it into different types of CSS units, presumably so you can do something to the input or related things
The first thing to understand is that ClientRect positions are relative to the viewport. So as vhutchinson pointed out, if you want the width of text you need to compare to the input's "left" value as defined by getBoundingClientRects. That's a good start, but if you're not just influencing left but also care about top, you need to account for scrolling. If your window/page is the only scrolling container, you should be able to do this simply by adding window.scrollY to top, and window.scrollX to left to understand your offset relative to the window.
All of these units are pixels by default... if you want to convert to rem it's pretty straightforward, 1 rem = the font-size of your root element, so to convert to rem you can do something like
var remBase = parseInt(window.getComputedStyle(document.body).getPropertyValue('font-size'), 10);
var remValue = (myComputedPixelValue / remBase) + "rem";
Doing VW is similar using the answer in Get the browser viewport dimensions with JavaScript for cross-browser window dimensions, you'd end up with something that looks like
var viewportWidth = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
var vwValue = (myComputedPixelValue / viewportWidth) + "vw";
Percentages are trickier, because you'd need to compute it based on the parent of the element you're applying the css value to, but the general idea follows the same principle.

Limit input to textarea without unnecessary constraints

I am looking for a method to limit input to a textarea in a way that enables user input to stay within a specified area WITHOUT unnecessary constraints to maximum number of characters.
The obvious method is maxlength, but that means maxlength will be determined by how much space an all caps input would take up, and that will require a maxlength that is unnecessarily low.
There are also various JS / jQuery solutions that limit the amount of lines possible to input in a text area (e.g. Limit number of lines in textarea and Display line count using jQuery), but these solutions, as far as I have been able to find, are dependent on the ENTER key, but doesn’t work if you copy-paste a piece of text into the text area.
To clarify
<!doctype html>
<head>
<style>
.one{
height: 60px;
width: 55px;
background-color: pink;
font-size: 16px;
}
</style>
</head>
<body>
<div class="one">i i i i i i i i i i i i i i i i i i</div>
<br>
<div class="one">M M M M M M M M M</div>
</body>
</html>
Div one can contain 18 lowercase “i” but only 9 capital “M”, so in order to ensure input would never exceed div one, the maxlength would have to be set to 9. The general result would be that the Div one area would generally not be fully used.
The above is merely a simple example to quickly explain which output I desire. The solution should ofc be tied to an input via a form textfield.
Ideas?
Cheers
I can't see this being an easy problem to solve. There is probably someone out there with more expertise than I, but the only way I can immediately think of to do this is to create a Map of characters and to assign them a width. Using this map, you could compare any new input against the map and determine how long/wide the input is.
Example pseudo code, this probably won't work as is;
// YOUR CHAR MAP WITH WIDTHS ASSIGNED TO LETTERS
var charMap = {
i: 1,
M: 2
}
// MAX AMOUNT OF CHARACTERS
var charLimit = 18;
// ASSUME THE TEXTAREA IS EMPTY, COULD GET ACTUAL WIDTH HERE INSTEAD
var currentChars = 0;
$('textarea').on('change', function{
// GET ALL NEW INPUT
var chars = $(this).val();
// FOR LIMITING THE INPUT, THIS IS THE FINAL STRING THAT WILL END UP IN THE TEXTBOX
var limitedUserInput = '';
// CHECK EACH CHAR ONE BY ONE, COMPARING ITS WIDTH USING THE MAP
for(var x = 0; x < chars.length; x++){
currentChars += chars[x];
// IF SIZE LIMIT NOT HIT, ADD CHARACTER TO OUTPUT STRING
if(currentChars < charLimit){
limitedUserInput += chars[x];
}
}
// DISPLAY THE OUTPUT IN THE TEXT AREA (TRIMMING ANY EXTRA CONTENT)
$(this).html = limitedUserInput;
})
This would allow you to have 18 x charMap.i (18) or 9 x charMap.M (18).
I hope that makes some kind of sense.
Here's my bid on a solution to the problem. There are a few bugs, but I'm quite satisfied with it.
I spend a lot of time trying to work something out by counting lines in the text area, but abandoned the effort as I came across increasingly complex solutions.
This solution depends on the div height and trims a .substring generated from the textarea user input so it fits within the desired div, in this case myDiv. The trimed string is also put into a second textarea which will be the one used in the form.
<!doctype html>
<head>
<script src="https://code.jquery.com/jquery-1.10.2.js"></script>
<style>
.myDiv{
min-height: 100px;
width: 100px;
background-color: pink;
position: absolute;
}
#mySecondDiv{
background-color: transparent;
border: 1px solid black;
};
</style>
</head>
<body>
<textarea id='userInput' cols="30" rows="7"></textarea>
<textarea id="toBeStored"></textarea>
<div class='myDiv'></div>
<div class='myDiv'></div>
<div class='myDiv' id='mySecondDiv'></div>
<script>
//For live versions set the 'left' property for .myDiv(0) and #toBeStored to negative so they are off screen
$('.myDiv').eq(0).offset({ top: 200, left: 350 });
$('#toBeStored').offset({ top: 10, left: 350});
$('.myDiv').eq(1).offset({ top: 200, left: 10 });
$('.myDiv').eq(2).offset({ top: 200, left: 10 });
var currentMaxChars = 0;
var testString = "";
var myDivHeight = $('.myDiv').height()
$(document).ready(function(){
while($('.myDiv').eq(0).height() <= myDivHeight ){
testString += " i";
$('.myDiv').eq(0).html(testString);
currentMaxChars++;
};
currentMaxChars = currentMaxChars*2;
maxChars = currentMaxChars;
$("#userInput").on('change keyup paste', function() {
var input = $(this).val().replace(/\n/g, '<br/>');
$('.myDiv').eq(1).html(input);
var str = $('#userInput').val().replace(/\n/g, '<br/>');
if(str.length == currentMaxChars){
currentMaxChars = maxChars;
};
if($('.myDiv').eq(0).height() <= myDivHeight){
$('.myDiv').eq(0).html(str.substring(0, currentMaxChars));
$('#toBeStored').html(str.substring(0, currentMaxChars));
} else {
while($('.myDiv').eq(0).height() > myDivHeight){
currentMaxChars--;
$('.myDiv').eq(0).html(str.substring(0, currentMaxChars));
$('#toBeStored').html(str.substring(0, currentMaxChars));
};
};
});
});
</script>
</body>
</html>

Make paragraph auto fill remaining height of parent

in my current project I have the following structure
<ol>
<li>...</li>
<li>
<section>
<h1>some title</h1>
<p>some text</p>
</section>
</li>
<li>...</li>
</ol>
The ol has a variable but set height which is filled by the li (via height:100%;). Now I want the text in the paragraph to fill the remaining height (via line-height and/or font-size).
I have researched this but didn't really find a solution to this, only FitText.js but it "is for gigantic display text only" as they say.
So I've come up with a JavaScript solution, calculating the remaining height from two perspectives (default line-height: 1em and line-height: 0em) and calculate the required line-height so remaining height is 0 (via basic maths, interpolating the function y = a*x + b where y equals the remaining height).
var outerHeight = $("section").innerHeight(),
h1Height = $("section").children("h1").first().outerHeight(),
pHeight = $("section").children("p").first().outerHeight();
var defaultHeight, a, b, x;
defaultHeight = outerHeight - (h1Height + pHeight);
$("section").children("p").first().css("line-height", "0em");
b = outerHeight - (h1Height + $("section").children("p").first().outerHeight());
a = defaultHeight - b;
x = -b / a;
$("section").children("p").first().css("line-height", x+"em");
jsfiddle here
Finally my question: Is it possible to achieve this in a better way (pure CSS would be awesome but I doubt that)? If not, what events could occur in which I had to recalculate (resizing browser eg)?
Also I'd like the text to fill the section to maximum extent - currently the last line's line-height might create a small visible area between the characters and the bottom of the section.
This looked interesting to me, so I thought I might take a crack at it. Here's a FIDDLE that might give you some new ideas.
I switched everything to divs (not sure why, perhaps it's just me)
I made the assumption that the title would be fixed in size, and the text would be distributed over the size of the large div minus the size of the title.
Then a simple division of totalheight and initial size of the text.
Obviously it won't work at the extremes (three words, or a book-length text.
I left the alerts in just for show, obviously you can delete them or comment them out.
JS
var outerheight = $(".oldiv").innerHeight();
var titleheight = $(".oldiv div:first-child").outerHeight();
var textheight = $(".oldiv div:last-child").outerHeight();
var heighttodistribute = outerheight - titleheight;
alert( outerheight + '-' + titleheight + '-' + textheight + '-' + heighttodistribute);
var newlineheight = heighttodistribute / textheight;
alert(newlineheight);
$('.oldiv div:last-child').css('line-height', newlineheight);

jQuery calculating 2 different HTML element attributes (height + margin-bottom)

I'm trying to get the height and the margin-bottom of a element, and count them up. But so far, i can only do this:
var sum = $('div').height() + 25);
That 25 i had to look up myself in the stylesheet at
div {
height: 75px;
margin-bottom: 25px;
}
How can i make this automaticly?
Because when i try:
var sum = $('div').height() + $('div').css('margin-bottom');
It isnt returning any value.
You're probably looking for http://api.jquery.com/outerheight/ which includes the margins, padding and borders (all which count towards the total height, along with the base height). Alternatively, you can use parseInt($('div').css('margin-bottom')) because the value is a css string that looks something like this: 100px. parseInt() would extract the 100 from that css, which is what you want.
In addition, you're kinda doing it wrong in general. Are you looking for the sum of all the heights of all the div elements? Or does your page have only one div?
$('div').css('margin-bottom') will have the px attached. Try and use something like parseInt()
var sum = $('div').height() + parseInt($('div').css('margin-bottom'), 10);
W
There are many 'div' elements in a document(usually).
You need a better selector for the one you want.
Fiddle for value in console.log
<div id="TestId" class="test"></div>
.test
{
height:40px;
margin:5px;
}
var tester = $("#TestId");
var eleHeight = tester.outerHeight();
var marginBottomHeight = tester.css('margin-bottom').replace("px", "")
console.log(eleHeight + parseInt(marginBottomHeight));

Get the offset position of the caret in a textarea in pixels [duplicate]

This question already has answers here:
How do I get the (x, y) pixel coordinates of the caret in text boxes?
(2 answers)
Closed 8 years ago.
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.
You can create a separate (invisible) element and fill it with textarea content from start to the cursor position. Textarea and the "clone" should have matching CSS (font properties, padding/margin/border and width). Then stack these elements on top of each other.
Let me start with a working example, then walk through the code: http://jsfiddle.net/g7rBk/
Updated Fiddle (with IE8 fix)
HTML:
<textarea id="input"></textarea>
<div id="output"><span></span></div>
<div id="xy"></div>
Textarea is self-explanatory. Output is a hidden element to which we'll pass text content and make measures. What's important is that we'll use an inline element. the "xy" div is just an indicator for testing purposes.
CSS:
/* identical styling to match the dimensions and position of textarea and its "clone"
*/
#input, #output {
position:absolute;
top:0;
left:0;
font:14px/1 monospace;
padding:5px;
border:1px solid #999;
white-space:pre;
margin:0;
background:transparent;
width:300px;
max-width:300px;
}
/* make sure the textarea isn't obscured by clone */
#input {
z-index:2;
min-height:200px;
}
#output {
border-color:transparent;
}
/* hide the span visually using opacity (not display:none), so it's still measurable; make it break long words inside like textarea does. */
#output span {
opacity:0;
word-wrap: break-word;
overflow-wrap: break-word;
}
/* the cursor position indicator */
#xy {
position:absolute;
width:4px;
height:4px;
background:#f00;
}
JavaScript:
/* get references to DOM nodes we'll use */
var input = document.getElementById('input'),
output = document.getElementById('output').firstChild,
position = document.getElementById('position'),
/* And finally, here it goes: */
update = function(){
/* Fill the clone with textarea content from start to the position of the caret. You may need to expand here to support older IE [1]. The replace /\n$/ is necessary to get position when cursor is at the beginning of empty new line.
*/
output.innerHTML = input.value.substr( 0, input.selectionStart ).replace(/\n$/,"\n\001");
/* the fun part!
We use an inline element, so getClientRects[2] will return a collection of rectangles wrapping each line of text.
We only need the position of the last rectangle.
*/
var rects = output.getClientRects(),
lastRect = rects[ rects.length - 1 ],
top = lastRect.top - input.scrollTop,
left = lastRect.left+lastRect.width;
/* position the little div and see if it matches caret position :) */
xy.style.cssText = "top: "+top+"px;left: "+left+"px";
}
[1] Caret position in textarea, in characters from the start
[2] https://developer.mozilla.org/en/docs/DOM/element.getClientRects
Edit: This example only works for fixed-width textarea. To make it work with user-resizable textarea you'd need to add an event listener to the resize event and set the #output dimensions to match new #input dimensions.
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/;
}
There's a much simpler solution for getting the caret position in pixels, than what's been presented in the other answers.
Note that this question is a duplicate of a 2008 one, and I've answered it here. I'll only maintain the answer at that link, since this question should have been closed as duplicate years ago.
Copy of the answer
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/
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.
JsFiddle of working example: http://jsfiddle.net/42zHC/2/
Basically, we figure out how many columns fit in the width (since it will be monospace). We have to force scrollbars to always be there otherwise the calculation is off. Then we divide the number of columns that fit with the width, and we get the x offset per character. Then we set the line height on the textarea. Since we know how many characters are in a row, we can divide that with the number of characters and we get the row number. With the line height, we now have the y offset. Then we get the scrollTop of the textarea and subtract that, so that once it starts using the scrollbar, it still shows up in the right position.
Javascript:
$(document).ready(function () {
var cols = document.getElementById('t').cols;
var width = document.getElementById('t').clientWidth;
var height = $('textarea').css('line-height');
var pos = $('textarea').position();
$('#t').on('keyup', function () {
el = document.getElementById("t");
if (el.selectionStart) {
selection = el.selectionStart;
} else if (document.selection) {
el.focus();
var r = document.selection.createRange();
if (r == null) {
selection = 0;
}
var re = el.createTextRange(),
rc = re.duplicate();
re.moveToBookmark(r.getBookmark());
rc.setEndPoint('EndToStart', re);
selection = rc.text.length;
} else { selection = 0 }
var row = Math.floor((selection-1) / cols);
var col = (selection - (row * cols));
var x = Math.floor((col*(width/cols)));
var y = (parseInt(height)*row);
$('span').html("row: " + row + "<br>columns" + col + "<br>width: " + width + "<br>x: " + x +"px<br>y: " + y +"px<br>Scrolltop: "+$(this).scrollTop()).css('top',pos.top+y-$(this).scrollTop()).css('left',pos.left+x+10);
});
});
HTML:
<textarea id="t"></textarea>
<br>
<span id="tooltip" style="background:yellow"></span>
CSS:
textarea {
height: 80px;
line-height: 12px;
overflow-y:scroll;
}
span {
position: absolute;
}
I couldn't get something similar to work, so my solution was to locate the character position of the caret in the textarea, cut out the current paragraph and display this next to the textarea.
Using the offset, I placed a fake cursor (div, display:inline, 1px wide, border-left: 1px solid black) in this view of the editable text.
This way, you can create a visual feedback area where you can show the result of effects (quite like stackoverflow does when you write an answer).

Categories

Resources