Convert textarea row/column to screen x/y coordinates - javascript

I have a textarea element on my webpage that will contain some plain text (alphanumerics, newlines, tabs, spaces). I know how to get the row and column of my cursor. But, I need a way to convert those into screen coordinates so I could display a floating div at that exact location of the cursor. I see two ways
Method 1: Take the offset of the textarea from (0,0) on screen. For x-coord, iterate through each character on that line and multiply the number of characters with the width of each character (tabs vs actual characters would be counted differently) and add it to the original offset. For y-coord, take the number of rows times the height of each row and add it to the original offset. But, how do I compute the character width and row height?
Method 2: Find a javascript library. Does anybody know of an existing javascript library/framework such as jQuery that would do this? I have looked and can't seem to find anything.

After the onchange event is fired, check for mouseMove events and then get the coordinates:
var waiting;
function text_changed(event) { //attach this to onChange
waiting = true;
setTimeout('waiting = false',1000);
}
function mouse_moved(event) { //attach this to mouseMove
if (waiting) {
display_div(event.pageX,event.pageY);
waiting = false;
}
}
function display_div(x,y) {
$('#floating_div').css('left',x).css('top',y).css('display','block');
}
if you have to, replace the jQuery with regular DOM scripting:
function display_div(x,y) {
var elmt = document.getElementById('floating_div');
elmnt.setAttribute('style','left:'+x+';top:'+y+';display:block');
}

Related

JavaScript(Canvas): How to update the pointer's position inside the animation frame and NOT inside the event listener? [duplicate]

Is it possible to get the mouse position with JavaScript after page loads without any mouse movement event (without moving the mouse)?
Real answer: No, it's not possible.
OK, I have just thought of a way. Overlay your page with a div that covers the whole document. Inside that, create (say) 2,000 x 2,000 <a> elements (so that the :hover pseudo-class will work in IE 6, see), each 1 pixel in size. Create a CSS :hover rule for those <a> elements that changes a property (let's say font-family). In your load handler, cycle through each of the 4 million <a> elements, checking currentStyle / getComputedStyle() until you find the one with the hover font. Extrapolate back from this element to get the co-ordinates within the document.
N.B. DON'T DO THIS.
Edit 2020: This does not work any more. It seems so, that the browser vendors patched this out. Because the most browsers rely on chromium, it might be in its core.
Old answer:
You can also hook mouseenter (this event is fired after page reload, when the mousecursor is inside the page). Extending Corrupted's code should do the trick:
var x = null;
var y = null;
document.addEventListener('mousemove', onMouseUpdate, false);
document.addEventListener('mouseenter', onMouseUpdate, false);
function onMouseUpdate(e) {
x = e.pageX;
y = e.pageY;
console.log(x, y);
}
function getMouseX() {
return x;
}
function getMouseY() {
return y;
}
You can also set x and y to null on mouseleave-event. So you can check if the user is on your page with it's cursor.
What you can do is create variables for the x and y coordinates of your cursor, update them whenever the mouse moves and call a function on an interval to do what you need with the stored position.
The downside to this of course is that at least one initial movement of the mouse is required to have it work. As long as the cursor updates its position at least once, we are able to find its position regardless of whether it moves again.
var cursor_x = -1;
var cursor_y = -1;
document.onmousemove = function(event)
{
cursor_x = event.pageX;
cursor_y = event.pageY;
}
setInterval(check_cursor, 1000);
function check_cursor(){console.log('Cursor at: '+cursor_x+', '+cursor_y);}
The preceding code updates once a second with a message of where your cursor is.
#Tim Down's answer is not performant if you render 2,000 x 2,000 <a> elements:
OK, I have just thought of a way. Overlay your page with a div that
covers the whole document. Inside that, create (say) 2,000 x 2,000
elements (so that the :hover pseudo-class will work in IE 6, see),
each 1 pixel in size. Create a CSS :hover rule for those elements
that changes a property (let's say font-family). In your load handler,
cycle through each of the 4 million elements, checking
currentStyle / getComputedStyle() until you find the one with the
hover font. Extrapolate back from this element to get the co-ordinates
within the document.
N.B. DON'T DO THIS.
But you don't have to render 4 million elements at once, instead use binary search. Just use 4 <a> elements instead:
Step 1: Consider the whole screen as the starting search area
Step 2: Split the search area into 2 x 2 = 4 rectangle <a> elements
Step 3: Using the getComputedStyle() function determine in which rectangle mouse hovers
Step 4: Reduce the search area to that rectangle and repeat from step 2.
This way you would need to repeat these steps max 11 times, considering your screen is not wider than 2048px.
So you will generate max 11 x 4 = 44 <a> elements.
If you don't need to determine the mouse position exactly to a pixel, but say 10px precision is OK. You would repeat the steps at most 8 times, so you would need to draw max 8 x 4 = 32 <a> elements.
Also generating and then destroying the <a> elements is not performat as DOM is generally slow. Instead, you can just reuse the initial 4 <a> elements and just adjust their top, left, width and height as you loop through steps.
Now, creating 4 <a> is an overkill as well. Instead, you can reuse the same one <a> element for when testing for getComputedStyle() in each rectangle. So, instead of splitting the search area into 2 x 2 <a> elements just reuse a single <a> element by moving it with top and left style properties.
So, all you need is a single <a> element change its width and height max 11 times, and change its top and left max 44 times and you will have the exact mouse position.
You could try something similar to what Tim Down suggested - but instead of having elements for each pixel on the screen, create just 2-4 elements (boxes), and change their location, width, height dynamically to divide the yet possible locations on screen by 2-4 recursively, thus finding the mouse real location quickly.
For example - first elements take right and left half of screen, afterwards the upper and lower half. By now we already know in which quarter of screen the mouse is located, are able to repeat - discover which quarter of this space...
Here's my solution. It exports window.currentMouseX and window.currentMouseY properties you can use anywhere. It uses the position of a hovered element (if any) initially and afterwards listens to mouse movements to set the correct values.
(function () {
window.currentMouseX = 0;
window.currentMouseY = 0;
// Guess the initial mouse position approximately if possible:
var hoveredElement = document.querySelectorAll(':hover');
hoveredElement = hoveredElement[hoveredElement.length - 1]; // Get the most specific hovered element
if (hoveredElement != null) {
var rect = hoveredElement.getBoundingClientRect();
// Set the values from hovered element's position
window.currentMouseX = window.scrollX + rect.x;
window.currentMouseY = window.scrollY + rect.y;
}
// Listen for mouse movements to set the correct values
window.addEventListener('mousemove', function (e) {
window.currentMouseX = e.pageX;
window.currentMouseY = e.pageY;
}, /*useCapture=*/true);
}())
Composr CMS Source: https://github.com/ocproducts/composr/commit/a851c19f925be20bc16bfe016be42924989f262e#diff-b162dc9c35a97618a96748639ff41251R1202
The most simple solution but not 100% accurate
$(':hover').last().offset()
Result: {top: 148, left: 62.5}
The result depend on the nearest element size and return undefined when user switched the tab
Yes, It's possible.
If you add "mouseover" event to the document it will fire instantly and you can get the mouse position, of course if mouse pointer was over the document.
document.addEventListener('mouseover', setInitialMousePos, false);
function setInitialMousePos( event ) {
console.log( event.clientX, event.clientY);
document.removeEventListener('mouseover', setInitialMousePos, false);
}
Previously it was possible to read mouse position through window.event but it's deprecated now.
var x = 0;
var y = 0;
document.addEventListener('mousemove', onMouseMove, false)
function onMouseMove(e){
x = e.clientX;
y = e.clientY;
}
function getMouseX() {
return x;
}
function getMouseY() {
return y;
}
I implemented a horizontal/vertical search, (first make a div full of vertical line links arranged horizontally, then make a div full of horizontal line links arranged vertically, and simply see which one has the hover state) like Tim Down's idea above, and it works pretty fast. Sadly, does not work on Chrome 32 on KDE.
jsfiddle.net/5XzeE/4/
You do not have to move the mouse to get the cursor's location. The location is also reported on events other than mousemove. Here's a click-event as an example:
document.body.addEventListener('click',function(e)
{
console.log("cursor-location: " + e.clientX + ',' + e.clientY);
});
Riffing on #SuperNova's answer, here's an approach using ES6 classes that keeps the context for this correct in your callback:
class Mouse {
constructor() {
this.x = 0;
this.y = 0;
this.callbacks = {
mouseenter: [],
mousemove: [],
};
}
get xPos() {
return this.x;
}
get yPos() {
return this.y;
}
get position() {
return `${this.x},${this.y}`;
}
addListener(type, callback) {
document.addEventListener(type, this); // Pass `this` as the second arg to keep the context correct
this.callbacks[type].push(callback);
}
// `handleEvent` is part of the browser's `EventListener` API.
// https://developer.mozilla.org/en-US/docs/Web/API/EventListener/handleEvent
handleEvent(event) {
const isMousemove = event.type === 'mousemove';
const isMouseenter = event.type === 'mouseenter';
if (isMousemove || isMouseenter) {
this.x = event.pageX;
this.y = event.pageY;
}
this.callbacks[event.type].forEach((callback) => {
callback();
});
}
}
const mouse = new Mouse();
mouse.addListener('mouseenter', () => console.log('mouseenter', mouse.position));
mouse.addListener('mousemove', () => console.log('mousemove A', mouse.position));
mouse.addListener('mousemove', () => console.log('mousemove B', mouse.position));
Not mouse position, but, if you're looking for current cursor postion (for use cases like getting last typed character etc) then, below snippet works fine.
This will give you the cursor index related to text content.
window.getSelection().getRangeAt(0).startOffset
I envision that maybe you have a parent page with a timer and after a certain amount of time or a task is completed, you forward the user to a new page. Now you want the cursor position, and because they are waiting, they aren't necessarily touching the mouse. So track the mouse on the parent page using standard events and pass the last value to the new page in a get or a post variable.
You can use JHarding's code on your parent page so that the latest position is always available in a global variable:
var cursorX;
var cursorY;
document.onmousemove = function(e){
cursorX = e.pageX;
cursorY = e.pageY;
}
This won't help users that navigate to this page by means other than your parent page.
I think i may have a reasonable solution with out counting divs and pixels..lol
Simply use animation frame or a time interval of a function. you will still need a mouse event one time though just to initiate, but technically you position this where ever you like.
Essentially we are tracking a dummy div at all times with out mouse movement.
// create a div(#mydiv) 1px by 1px set opacity to 0 & position:absolute;
Below is the logic..
var x,y;
$('body').mousemove(function( e ) {
var x = e.clientX - (window.innerWidth / 2);
var y = e.clientY - (window.innerHeight / 2);
}
function looping (){
/* track my div position 60 x 60 seconds!
with out the mouse after initiation you can still track the dummy div.x & y
mouse doesn't need to move.*/
$('#mydiv').x = x; // css transform x and y to follow
$('#mydiv)'.y = y;
console.log(#mydiv.x etc)
requestAnimationFrame( looping , frame speed here);
}

How to make contenteditable div accept only plaintext?

I have a div as a textarea, because div can change it's height based on the text inside it, the problem here is if someone copied some styled text (text and html) and pasted it, the style sticks with the text, contenteditable='plaintext-only' would solve the problem but it seems to be a webkit-only feature.
So is there a way to allow text only inside a div?
For reference: https://jsfiddle.net/w25dnuen/
Here's an example of a text area that will grow based on the contents you enter:
http://jsfiddle.net/janjarfalk/r3Ekw/
The reason that the elastic text area works is the external jquery.elastic.source.js which is located here: http://jquery-elastic.googlecode.com/svn/trunk/jquery.elastic.source.js
so now that you have looked at those two resources, let's talk about what it does. The script has an "udpate" function that basically just adds whitespace when it gets to the end of the row here:
// Add an extra white space so new rows are added when you are at the end of a row.
$twin.html(textareaContent+' ');
And then here you can see the respective functions:
// Updates the width of the twin. (solution for textareas with widths in percent)
function setTwinWidth(){
var curatedWidth = Math.floor(parseInt($textarea.width(),10));
if($twin.width() !== curatedWidth){
$twin.css({'width': curatedWidth + 'px'});
// Update height of textarea
update(true);
}
}
// Sets a given height and overflow state on the textarea
function setHeightAndOverflow(height, overflow){
var curratedHeight = Math.floor(parseInt(height,10));
if($textarea.height() !== curratedHeight){
$textarea.css({'height': curratedHeight + 'px','overflow':overflow});
}
}
This is all in the elastic function. Hopefully this gives a little insight and helps you out.

Change background on hover of a draggable div

I have a small draggable division (black) and many nodes with different IDs beside it
I need a hovering effect when I drag the black element on the nodes. What I am doing in allowDrop function is:
var dragObj;
function drag(ev){
dragObj = ev;
}
function allowDrop(ev){
ev.preventDefault();
var Dragged = dragObj;
var Hovered = ev;
var leftIndent = Dragged.layerX;
var hoveredID = Hovered.target.id.toString().split('_');
var nodesOnLeft = Math.floor(leftIndent/12);
var startingNode = hoveredID[0]-nodesOnLeft;
for (i=1;i<=Math.floor(draggableElementLength/12);i++){
var toApplyCssID = startingNode.toString() + '_1';
var toApplyCss = document.getElementById(toApplyCssID);
$('#'+toApplyCssID).css('background-color','lightgreen');
}
}
basically I am using the layerX property to find out the distance between my mouse pointer and draggable division's border and adjusting that to calculate number of nodes where I have to apply new CSS and applying that by finding the ID of node.
This is working but its making the process very slow as it involves many calculations and its not the hover effect as its not going away when I am removing the draggable division.
Is there any other way to achieve this or do I need to make code changes in existing code.
thanks
Without the HTML and the rest of the script, I can only point you in the direction you should be taking in this kind of scenario:
Don't constantly repeat calculations (that do not change) in a loop, store them outside the function:
Use document.getElementById(toApplyCssID) for each node and store the
elements in an array
Get the distance from the mouse position to the required edge or
edges of the div (leftIndent) on initial drag/mousedown and store
that in a variable
Store Math.floor(draggableElementLength/12) in another variable (you
haven't shown where this originates, but it doesn't seem to change in
the function...)
Apply the CSS to the relavent elements (from the array of elements)
using the other stored values to select them
Remove the CSS on those that had it applied earlier
This may not be the ultimate solution, but these are some of the things you can look at to speed up what you (may) have currently.

Is particular x-y coordinate in between lines of text?

How can one tell, using Javascript (jQuery will work), if a particular X-Y coordinate on an HTML page is vertically between two lines of text? The lines may be in the middle of a long paragraph, inside a lengthy line-item tag, in a span, between two tags, etc. I have no way of controlling the HTML or the X-Y point, but I need to know if the X-Y point is in the middle of a line of text, or if it is in-between two lines of text; and it needs to be pretty efficient.
Please ask any questions you may have if I have not been clear enough.
Many thanks.
You can call .getBoundingClientRect() on a text range. You will need to write separate code for IE vs Non-IE browsers to get the text ranges.
This should be relatively easy in IE, thanks to textRange.moveToPoint(x, y). For other browsers you'll have to do something like do a binary search on the elements in the DOM, calling .getBoundingClientRect() on the elements, until you find the element that contains your text. Then create a range that contains the text of that element and do a binary search on the range to find whether your point overlaps any text.
All of this will be greatly complicated if you have absolutely positioned elements with text overlapping other elements.
Having dealt with text ranges, I don't think you can technically put anything "between" two lines of text on the same HTML node. Even if you use line height, every pixel belongs to one of the lines (even though it's visually space between them).
I'll throw out a few options which might help.
The simplest answer is probably just to use the line height:
get dom element that was clicked (event.relatedTarget in jQuery?)
determine its offset relative to the page (i.e. where the top of that element is)
determine the point that was clicked (x,y coords of the mouse event)
compare the two using the line-height of text in the row
This would look something like this:
function getLines(topOfElement, clickPoint, lineHeight) {
return Math.floor( (clickPoint - topOfElement)/lineHeight );
}
var topOfElement = $(element).offset().top; //must be position: relative|absolute
var clickedPoint = event.clientY; //might be pageY?
var lineHeight = parseFloat($(element).css('line-height')); //probably need to set this in px using css or it might be null
var textHeight = parseInt($(element).css('font-size')); //probably need to set this in px using css or it might be null
var prevLineNumber = getLines(topOfElement, clickedPoint, lineHeight);
// the previous line ends (in theory) at the bottom of the text (textHeight)
// you might need to adjust this definition to your needs
var prevLineBottom = prevLineNumber*lineHeight+topOfElement+textHeight;
// the next line begins (in theory) at the top of its line
// you might need to adjust this definition to your needs
var nextLineTop = (prevLineNumber+1)+lineHeight;
if( clickedPoint >= nextLineTop ) {
alert('clicked on row '+(prevLineNumber+1));
}
else if( clickedPoint <= prevLineBottom ) {
alert('clicked on row '+prevLineNumber);
}
else {
alert('clicked between rows '+prevLineNumber+' and '+(prevLineNumber+1));
}
If you want to see if the click happened between two html nodes, you can do that with Rangy, as well as some fancy selection and range calculations.
You could use it for things like determining the exact length of the text before and after the seletion. This is only useful if you want to see where in the text they clicked.
function getTextAtClick() {
var result = {nodeClicked: null, textBefore: '', textAfter: '', valid: false};
//get a selection object (even though the selection is technically zero length)
var sel = rangy.getSelection();
//you would probably want to discard any selection not zero length (i.e actual selection of text instead of a click)
// if not, you'd need to decide what it means to select across multiple dom nodes :(
if( sel.toString().length > 0 ) { return result; }
// get the point where the click occurred
var range = sel.getRangeAt(0);
result.valid = true;
// determine text in our dom element up to the click point
var before = rangy.createRange();
before.setStart(range.startContainer, 0);
before.setEnd(range.startContainer, range.startOffset);
result.textBefore = before.toString();
// determine text in our dom element after the click point
var after = rangy.createRange();
after.setStart(range.startContainer, range.startOffset+1);
after.setEndAfter(range.startContainer);
result.textAfter = after.toString();
return result;
}

Limit textarea symbols to visible part

Is there any way to limit textarea entered symbols to visible part only?
I don't want scrolling, I have specified number of rows column for it and I want that user could not enter so many characters that possible scrolling will appear.
Of course I could set overflow:hidden but symbols anyway will be entered. Limiting to the number of characters is not my case, because different character take different width: for example W and 1. I need this logic because data entered in textarea are used on some print report and there is no possibility for scrolling in paper.
I have found only 2 possible solutions:
Use some average number of
characters. And limit by this
number. This is very rough. Because
for strings with average number of
big characters greater than usual it
will still hide some data.
Use rendering of entered
characters to some separate div and
calculate its width/height. Seem to
me will be very slow and not
sure this is the correct implementation.
You can use JavaScript to check the scrollHeight and if bigger than the "original" height, truncate the text until it has no scroll anymore. Code for this would be:
function CheckScroll(oTextarea) {
if (oTextarea.scrollHeight > oTextarea.offsetHeight) {
while (oTextarea.scrollHeight > oTextarea.offsetHeight) {
oTextarea.value = oTextarea.value.substr(0, oTextarea.value.length - 1);
}
}
}
And to trigger it:
<textarea cols="15" rows="3" onkeyup="CheckScroll(this);" onchange="CheckScroll(this);" onpaste="CheckScroll(this);"></textarea>
Live test case: http://jsfiddle.net/yahavbr/bNqVf/1/
I came up with something similar to Shadow Wizard's effort which uses oninput to detect all forms of input (like pasting, drag/drop), not just keyboard input. It requires switching off the scrollbars of the textarea with the following CSS:
textarea { overflow: hidden; }
You might want to set resize: none; for browsers like Firefox 4 and Chrome. Also, Opera's wrapping doesn't break if there are no spaces, but it doesn't support word-wrap: word-break; properly so I'm not sure how you'd work around this. The JavaScript involves remembering the content of the textarea each time it changes and, if the text exceeds the size of the element, the change is reverted to the previous value:
var prev = "",
tArea = document.getElementById("limit");
// Need to use onpropertychange event for IE8 and lower
if ("onpropertychange" in tArea && !("oninput" in tArea)) {
tArea.onpropertychange = function () {
// Only run code if value property changes
if (window.event.propertyName == "value")
this.oninput();
}
}
// oninput will fire for all types of input, not just keyboard
tArea.oninput = function () {
// Temporarily remove the onpropertychange event to prevent a stack overflow
var opc = this.onpropertychange;
this.onpropertychange = null;
// Revert value if the text exceeds the size of the box
if (this.scrollHeight > this.offsetHeight) {
this.value = prev;
}
prev = this.value;
if (opc)
this.onpropertychange = opc;
}
Working demo: http://jsfiddle.net/37Jnn/ - tested in Firefox 4, IE8, Chrome 9, Opera 10.
You could style it to use Courier, or some other non-proportional font. That way, you'd know exactly how many characters could fit, since all characters are the same size, which would make it easy to limit it using any one of several well-known techniques for limiting the number of characters in the string.
The downside is that it wouldn't look pretty (unless you happen to like how Courier looks), but it is a workable solution, and I can't think of anything else.
Just to summarize. I will exclude Courier font solution from discussion. Because in most cases it is not acceptable. Both solutions that use key up/property changes has drawback:
visually text is entered and only then removed.
Making substr(0, oTextarea.value.length - 1) has problems when last symbol is \n in IE. In this case it will hang because \r symbol is left. So it should be used oTextarea.value.length - 2 in case of last \n.
For on property change should be used some multi browser solution. For example like this
The only possible way to make such checks user friendly (not allowing entering) is to create for each text area other hidden text area and make all checks on this text area in events before data are really shown in original text area (like key down). But in this case it will require handling of all selection ranges, pastes and reapplying them on hidden. And logic will be not trivial.

Categories