Version 2 of my Hacky Experiment
This new version works with any font that can be customized on demand, and any size of the text field.
Noticing that some of you are still trying to get this to work, I decided to try a new approach. This time my FAR results are better - at least on google chrome on linux. I no longer have a Windows computer, so I can only check on chrome / firefox on Ubuntu. My results work 100% sequentially in Chrome, and let's say somewhere around 70 - 80% on Firefox, but I don’t think it would be incredibly difficult to find inconsistencies.
This new version is based on the Canvas object. In my example , I actually show this very canvas - just so you can see it in action, but it could be easily done with a hidden canvas.
This is definitely a hack, and I apologize in advance for my rather complicated code. At least in google chrome it works sequentially, no matter what font I set for it, or the size of the text field. I used the example of Sam Shaffron to show the coordinates of the cursor (gray-background div). I also added a “Randomize” link so you can see how it works in different font / texarea sizes and styles and keeps an eye on the cursor position updates on the fly. I recommend watching a full-screen demo so you can better see the canvas canvas.
I'll tell you how it works ...
The main idea is that we try to redraw the text area on the canvas as much as possible. Since the browser uses the same font engine for both sides and the texaria, we can use the canvas font metering function to find out where this happens. From there, we can use the available canvas methods to find out our coordinates.
First of all, we adjust our canvas according to the size of the text field. This is entirely for visual purposes, since the size of the canvas does not really affect our result. Since Canvas doesn’t actually provide a means of wrapping words, I had to conjure (steal / borrow / merge together) to break the lines into the greatest possible match of the text area. This is where you will probably find that you need to do the most cross-browser setup.
After word wrapping, everything else is basic math. We split the lines into an array to simulate word wrap, and now we want to scroll through these lines and get to the end of our current selection. To do this, we simply count the characters, and as soon as we surpass selection.end , we know that we have come down far enough. Multiply the line count to this point with the line height, and you have the y coordinate.
The x coordinate is very similar, except that we use context.measureText . While we print the desired number of characters, this will give us the width of the line that stretches to the Canvas, which ends after the last character drawn, which is the character before the currentl selection.end position.
When trying to debug this for other browsers, you need to look for where the lines will not break properly. In some places, you will see that the last word in a line in a canvas can be wrapped in a text box or vice versa. This is due to the way the browser handles word wraps. As long as you get the wrapping in the canvas according to the text box, your cursor should be correct.
I will insert the source below. You should be able to copy and paste it, but if you do, I ask you to upload your own copy of jquery-fieldselection instead of clicking on the one on my server.
I also picked up a new demo as well as a violin .
Good luck
<!DOCTYPE html> <html lang="en-US"> <head> <meta charset="utf-8" /> <title>Tooltip 2</title> <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js"></script> <script type="text/javascript" src="http://enobrev.info/cursor/js/jquery-fieldselection.js"></script> <style type="text/css"> form { float: left; margin: 20px; } #textariffic { height: 400px; width: 300px; font-size: 12px; font-family: 'Arial'; line-height: 12px; } #tip { width:5px; height:30px; background-color: #777; position: absolute; z-index:10000 } #mock-text { float: left; margin: 20px; border: 1px inset #ccc; } .scrollbar-measure { width: 100px; height: 100px; overflow: scroll; position: absolute; top: -9999px; } #randomize { float: left; display: block; } </style> <script type="text/javascript"> var oCanvas; var oTextArea; var $oTextArea; var iScrollWidth; $(function() { iScrollWidth = scrollMeasure(); oCanvas = document.getElementById('mock-text'); oTextArea = document.getElementById('textariffic'); $oTextArea = $(oTextArea); $oTextArea .keyup(update) .mouseup(update) .scroll(update); $('#randomize').bind('click', randomize); update(); }); function randomize() { var aFonts = ['Arial', 'Arial Black', 'Comic Sans MS', 'Courier New', 'Impact', 'Times New Roman', 'Verdana', 'Webdings']; var iFont = Math.floor(Math.random() * aFonts.length); var iWidth = Math.floor(Math.random() * 500) + 300; var iHeight = Math.floor(Math.random() * 500) + 300; var iFontSize = Math.floor(Math.random() * 18) + 10; var iLineHeight = Math.floor(Math.random() * 18) + 10; var oCSS = { 'font-family': aFonts[iFont], width: iWidth + 'px', height: iHeight + 'px', 'font-size': iFontSize + 'px', 'line-height': iLineHeight + 'px' }; console.log(oCSS); $oTextArea.css(oCSS); update(); return false; } function showTip(x, y) { $('#tip').css({ left: x + 'px', top: y + 'px' }); } </script> </head> <body> <a href="#" id="randomize">Randomize</a> <form id="tipper"> <textarea id="textariffic">Aliquam urna. Nullam augue dolor, tincidunt condimentum, malesuada quis, ultrices at, arcu. Aliquam nunc pede, convallis auctor, sodales eget, aliquam eget, ligula. Proin nisi lacus, scelerisque nec, aliquam vel, dictum mattis, eros. Curabitur et neque. Fusce sollicitudin. Quisque at risus. Suspendisse potenti. Mauris nisi. Sed sed enim nec dui viverra congue. Phasellus velit sapien, porttitor vitae, blandit volutpat, interdum vel, enim. Cras sagittis bibendum neque. Proin eu est. Fusce arcu. Aliquam elit nisi, malesuada eget, dignissim sed, ultricies vel, purus. Maecenas accumsan diam id nisi. Phasellus et nunc. Vivamus sem felis, dignissim non, lacinia id, accumsan quis, ligula. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Sed scelerisque nulla sit amet mi. Nulla consequat, elit vitae tempus vulputate, sem libero rhoncus leo, vulputate viverra nulla purus nec turpis. Nam turpis sem, tincidunt non, congue lobortis, fermentum a, ipsum. Nulla facilisi. Aenean facilisis. Maecenas a quam eu nibh lacinia ultricies. Morbi malesuada orci quis tellus. Sed eu leo. Donec in turpis. Donec non neque nec ante tincidunt posuere. Pellentesque blandit. Ut vehicula vestibulum risus. Maecenas commodo placerat est. Integer massa nunc, luctus at, accumsan non, pulvinar sed, odio. Pellentesque eget libero iaculis dui iaculis vehicula. Curabitur quis nulla vel felis ullamcorper varius. Sed suscipit pulvinar lectus.</textarea> </form> <div id="tip"></div> <canvas id="mock-text"></canvas> </body> </html>
Mistake
There is one mistake that I remember. If you place the cursor in front of the first letter in a line, it will display the "position" as the last letter on the previous line. This is due to how the selection.end function works. I do not think that it should be too difficult to find for this case and correct it accordingly.
Version 1
Leaving this here so you can see progress without having to scroll through the change history.
This is not ideal, and it most definitely collapses, but I got it to work very well in WinXP IE, FF, Safari, Chrome and Opera.
As far as I can tell, there is no way to directly recognize the x / y cursor in any browser. The IE method mentioned by Adam Bellaire is interesting, but unfortunately not a cross browser. I thought the best thing is to use symbols in the form of a grid.
Unfortunately, there is no font metric information built into any of the browsers, which means that a monospaced font is the only type of font that will have a consistent dimension. In addition, there is no reliable means of determining the font width from the font height. At first I tried using a percentage of height that worked great. Then I changed the font size and it all went to hell.
I tried one method for determining the width of characters, which was to create a temporary text area and continue to add characters until scrollHeight (or scrollWidth) changes. This seems plausible, but about halfway down this road, I realized that I could just use the cols attribute on the text box, and thought there were enough hacks in this test to add another one. This means that you cannot set the width of the text area via css. You must use cols for this.
The next problem I came across is that even when you install the font through css, browsers report the font differently. When you do not install the font, mozilla uses monospace by default, IE uses Courier New , Opera "Courier New" (with quotes), Safari, 'Lucida Grand' (with single quotes). When you install the font on monospace , mozilla, etc. Take what you give them, Safari comes out as -webkit-monospace , and Opera stays with "Courier New" .
So now we initialize some vars. Be sure to set the line height in css. Firefox reports the correct line height, but IE reported "normal" and I did not worry about other browsers. I just set the line height in my css and solved the difference. I have not tested using ems instead of pixels. Char height - only font size. Probably pre-install this in your CSS.
In addition, another pre-setting before we begin to post characters, which really made me scratch my head. For that is mozilla, texarea characters <cols, everything else is <= chars. Thus, Chrome can fit 50 characters across, but mozilla, etc. Breaks the last word from the line.
Now we will create an array of positions of the first character for each line. We look at each Char in the text box. If this is a new line, we add a new position to our line array. If this is a space, we try to find out whether the current "word" will fit into the line on which we are, or if it will be transferred to the next line. Punctuation is considered part of the "word". I have not tested with tabs, but there is a line there to add 4 characters for the char tab.
As soon as we have an array of line positions, we scroll and try to find which line the cursor is on. We use the hte "End" of choice as our cursor.
x = (cursor position - position of the first character of the cursor line) * character width
y = ((cursor line + 1) * line height) - scroll position
I am using jquery 1.2.6 , jquery- fieldselection and jquery-dimensions
Demo: http://enobrev.info/cursor/
And the code:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title>Tooltip</title> <script type="text/javascript" src="js/jquery-1.2.6.js"></script> <script type="text/javascript" src="js/jquery-fieldselection.js"></script> <script type="text/javascript" src="js/jquery.dimensions.js"></script> <style type="text/css"> form { margin: 20px auto; width: 500px; } #textariffic { height: 400px; font-size: 12px; font-family: monospace; line-height: 15px; } #tip { position: absolute; z-index: 2; padding: 20px; border: 1px solid #000; background-color: #FFF; } </style> <script type="text/javascript"> $(function() { $('textarea') .keyup(update) .mouseup(update) .scroll(update); }); function showTip(x, y) { y = y + $('#tip').height(); $('#tip').css({ left: x + 'px', top: y + 'px' }); } function update() { var oPosition = $(this).position(); var sContent = $(this).val(); var bGTE = jQuery.browser.mozilla || jQuery.browser.msie; if ($(this).css('font-family') == 'monospace' </script> </head> <body> <form id="tipper"> <textarea id="textariffic" cols="50"> Aliquam urna. Nullam augue dolor, tincidunt condimentum, malesuada quis, ultrices at, arcu. Aliquam nunc pede, convallis auctor, sodales eget, aliquam eget, ligula. Proin nisi lacus, scelerisque nec, aliquam vel, dictum mattis, eros. Curabitur et neque. Fusce sollicitudin. Quisque at risus. Suspendisse potenti. Mauris nisi. Sed sed enim nec dui viverra congue. Phasellus velit sapien, porttitor vitae, blandit volutpat, interdum vel, enim. Cras sagittis bibendum neque. Proin eu est. Fusce arcu. Aliquam elit nisi, malesuada eget, dignissim sed, ultricies vel, purus. Maecenas accumsan diam id nisi. Phasellus et nunc. Vivamus sem felis, dignissim non, lacinia id, accumsan quis, ligula. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Sed scelerisque nulla sit amet mi. Nulla consequat, elit vitae tempus vulputate, sem libero rhoncus leo, vulputate viverra nulla purus nec turpis. Nam turpis sem, tincidunt non, congue lobortis, fermentum a, ipsum. Nulla facilisi. Aenean facilisis. Maecenas a quam eu nibh lacinia ultricies. Morbi malesuada orci quis tellus. Sed eu leo. Donec in turpis. Donec non neque nec ante tincidunt posuere. Pellentesque blandit. Ut vehicula vestibulum risus. Maecenas commodo placerat est. Integer massa nunc, luctus at, accumsan non, pulvinar sed, odio. Pellentesque eget libero iaculis dui iaculis vehicula. Curabitur quis nulla vel felis ullamcorper varius. Sed suscipit pulvinar lectus. </textarea> </form> <p id="tip">Here I Am!!</p> </body> </html>