In javascript, how to detect the position of cursor? - javascript

My cursor is here(red flash), I need to detect its position, and locate the beginning "{{?" and the ending"{{?}}" of this expression.
First, how can I find my cursor location?

To get the caret position inside an input field you could use this:
HTML
<input type="text" id="input">
JS
function getCaretPosition(input) {
var position = 0;
if (document.selection) { //FOR IE...
var selrange = document.selection.createRange();
selrange.moveStart('character', -input.value.length);
position = selrange.text.length;
} else if (typeof input.selectionStart !== 'undefined') { //OTHERS
position = input.selectionStart;
}
return position;
}
You can call it like this:
var position = getCaretPosition(document.getElementById('input'));

This can be done by using range on editable content.
Example:
HTML:
<p contenteditable="true">This<!--cursor--> is an editable paragraph.</p>
Code:
selection = document.getSelection();
range = selection.getRangeAt(0);
startPosition = range.startOffset;
output: 4

<input type="text" id="Javascript_example" value="{{? ...}}1{{??}}22{{?}}" onclick="getposition()">
<script>
function getposition(){
var ptr = document.getElementById('Javascript_example');
alert(ptr);
var startPosition = ptr.selectionStart;
alert(startPosition);
var endPosition = ptr.selectionEnd;
alert(endPosition);
}
</script>

function foo(event) {
var x = event.clientX; // Get the horizontal coordinate
var y = event.clientY; // Get the vertical coordinate
var test = "X coords: " + x + ", Y coords: " + y;
document.querySelector('.test').innerHTML = test;
}
document.addEventListener('mousemove',foo);
.test {
width: 100%;
height: 100%;
background: #ccc;
}
<div class="test"></div>
This is how you can do this.
Here is the example made by experts :) >> 0o0o0o0o0o0o0

Related

How can I get the Cursor Position in a <code> box? [duplicate]

I'm finding tons of good, cross-browser answers on how to set the caret position in a contentEditable element, but none on how to get the caret position in the first place.
What I want to do is know the caret position within a div on keyup. So, when the user is typing text, I can, at any point, know the caret position within the contentEditable element.
<div id="contentBox" contentEditable="true"></div>
$('#contentbox').keyup(function() {
// ... ?
});
The following code assumes:
There is always a single text node within the editable <div> and no other nodes
The editable div does not have the CSS white-space property set to pre
If you need a more general approach that will work content with nested elements, try this answer:
https://stackoverflow.com/a/4812022/96100
Code:
function getCaretPosition(editableDiv) {
var caretPos = 0,
sel, range;
if (window.getSelection) {
sel = window.getSelection();
if (sel.rangeCount) {
range = sel.getRangeAt(0);
if (range.commonAncestorContainer.parentNode == editableDiv) {
caretPos = range.endOffset;
}
}
} else if (document.selection && document.selection.createRange) {
range = document.selection.createRange();
if (range.parentElement() == editableDiv) {
var tempEl = document.createElement("span");
editableDiv.insertBefore(tempEl, editableDiv.firstChild);
var tempRange = range.duplicate();
tempRange.moveToElementText(tempEl);
tempRange.setEndPoint("EndToEnd", range);
caretPos = tempRange.text.length;
}
}
return caretPos;
}
#caretposition {
font-weight: bold;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div id="contentbox" contenteditable="true">Click me and move cursor with keys or mouse</div>
<div id="caretposition">0</div>
<script>
var update = function() {
$('#caretposition').html(getCaretPosition(this));
};
$('#contentbox').on("mousedown mouseup keydown keyup", update);
</script>
A few wrinkles that I don't see being addressed in other answers:
the element can contain multiple levels of child nodes (e.g. child nodes that have child nodes that have child nodes...)
a selection can consist of different start and end positions (e.g. multiple chars are selected)
the node containing a Caret start/end may not be either the element or its direct children
Here's a way to get start and end positions as offsets to the element's textContent value:
// node_walk: walk the element tree, stop when func(node) returns false
function node_walk(node, func) {
var result = func(node);
for(node = node.firstChild; result !== false && node; node = node.nextSibling)
result = node_walk(node, func);
return result;
};
// getCaretPosition: return [start, end] as offsets to elem.textContent that
// correspond to the selected portion of text
// (if start == end, caret is at given position and no text is selected)
function getCaretPosition(elem) {
var sel = window.getSelection();
var cum_length = [0, 0];
if(sel.anchorNode == elem)
cum_length = [sel.anchorOffset, sel.extentOffset];
else {
var nodes_to_find = [sel.anchorNode, sel.extentNode];
if(!elem.contains(sel.anchorNode) || !elem.contains(sel.extentNode))
return undefined;
else {
var found = [0,0];
var i;
node_walk(elem, function(node) {
for(i = 0; i < 2; i++) {
if(node == nodes_to_find[i]) {
found[i] = true;
if(found[i == 0 ? 1 : 0])
return false; // all done
}
}
if(node.textContent && !node.firstChild) {
for(i = 0; i < 2; i++) {
if(!found[i])
cum_length[i] += node.textContent.length;
}
}
});
cum_length[0] += sel.anchorOffset;
cum_length[1] += sel.extentOffset;
}
}
if(cum_length[0] <= cum_length[1])
return cum_length;
return [cum_length[1], cum_length[0]];
}
Kinda late to the party, but in case anyone else is struggling. None of the Google searches I've found for the past two days have come up with anything that works, but I came up with a concise and elegant solution that will always work no matter how many nested tags you have:
function cursor_position() {
var sel = document.getSelection();
sel.modify("extend", "backward", "paragraphboundary");
var pos = sel.toString().length;
if(sel.anchorNode != undefined) sel.collapseToEnd();
return pos;
}
// Demo:
var elm = document.querySelector('[contenteditable]');
elm.addEventListener('click', printCaretPosition)
elm.addEventListener('keydown', printCaretPosition)
function printCaretPosition(){
console.log( cursor_position(), 'length:', this.textContent.trim().length )
}
<div contenteditable>some text here <i>italic text here</i> some other text here <b>bold text here</b> end of text</div>
It selects all the way back to the beginning of the paragraph and then counts the length of the string to get the current position and then undoes the selection to return the cursor to the current position. If you want to do this for an entire document (more than one paragraph), then change paragraphboundary to documentboundary or whatever granularity for your case. Check out the API for more details. Cheers! :)
$("#editable").on('keydown keyup mousedown mouseup',function(e){
if($(window.getSelection().anchorNode).is($(this))){
$('#position').html('0')
}else{
$('#position').html(window.getSelection().anchorOffset);
}
});
body{
padding:40px;
}
#editable{
height:50px;
width:400px;
border:1px solid #000;
}
#editable p{
margin:0;
padding:0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.1/jquery.min.js"></script>
<div contenteditable="true" id="editable">move the cursor to see position</div>
<div>
position : <span id="position"></span>
</div>
window.getSelection - vs - document.selection
This one works for me:
function getCaretCharOffset(element) {
var caretOffset = 0;
if (window.getSelection) {
var range = window.getSelection().getRangeAt(0);
var preCaretRange = range.cloneRange();
preCaretRange.selectNodeContents(element);
preCaretRange.setEnd(range.endContainer, range.endOffset);
caretOffset = preCaretRange.toString().length;
}
else if (document.selection && document.selection.type != "Control") {
var textRange = document.selection.createRange();
var preCaretTextRange = document.body.createTextRange();
preCaretTextRange.moveToElementText(element);
preCaretTextRange.setEndPoint("EndToEnd", textRange);
caretOffset = preCaretTextRange.text.length;
}
return caretOffset;
}
// Demo:
var elm = document.querySelector('[contenteditable]');
elm.addEventListener('click', printCaretPosition)
elm.addEventListener('keydown', printCaretPosition)
function printCaretPosition(){
console.log( getCaretCharOffset(elm), 'length:', this.textContent.trim().length )
}
<div contenteditable>some text here <i>italic text here</i> some other text here <b>bold text here</b> end of text</div>
The calling line depends on event type, for key event use this:
getCaretCharOffsetInDiv(e.target) + ($(window.getSelection().getRangeAt(0).startContainer.parentNode).index());
for mouse event use this:
getCaretCharOffsetInDiv(e.target.parentElement) + ($(e.target).index())
on these two cases I take care for break lines by adding the target index
Try this:
Caret.js
Get caret postion and offset from text field
https://github.com/ichord/Caret.js
demo:
http://ichord.github.com/Caret.js
function getCaretPosition() {
var x = 0;
var y = 0;
var sel = window.getSelection();
if(sel.rangeCount) {
var range = sel.getRangeAt(0).cloneRange();
if(range.getClientRects()) {
range.collapse(true);
var rect = range.getClientRects()[0];
if(rect) {
y = rect.top;
x = rect.left;
}
}
}
return {
x: x,
y: y
};
}
As this took me forever to figure out using the new window.getSelection API I am going to share for posterity. Note that MDN suggests there is wider support for window.getSelection, however, your mileage may vary.
const getSelectionCaretAndLine = () => {
// our editable div
const editable = document.getElementById('editable');
// collapse selection to end
window.getSelection().collapseToEnd();
const sel = window.getSelection();
const range = sel.getRangeAt(0);
// get anchor node if startContainer parent is editable
let selectedNode = editable === range.startContainer.parentNode
? sel.anchorNode
: range.startContainer.parentNode;
if (!selectedNode) {
return {
caret: -1,
line: -1,
};
}
// select to top of editable
range.setStart(editable.firstChild, 0);
// do not use 'this' sel anymore since the selection has changed
const content = window.getSelection().toString();
const text = JSON.stringify(content);
const lines = (text.match(/\\n/g) || []).length + 1;
// clear selection
window.getSelection().collapseToEnd();
// minus 2 because of strange text formatting
return {
caret: text.length - 2,
line: lines,
}
}
Here is a jsfiddle that fires on keyup. Note however, that rapid directional key presses, as well as rapid deletion seems to be skip events.
//global savedrange variable to store text range in
var savedrange = null;
function getSelection()
{
var savedRange;
if(window.getSelection && window.getSelection().rangeCount > 0) //FF,Chrome,Opera,Safari,IE9+
{
savedRange = window.getSelection().getRangeAt(0).cloneRange();
}
else if(document.selection)//IE 8 and lower
{
savedRange = document.selection.createRange();
}
return savedRange;
}
$('#contentbox').keyup(function() {
var currentRange = getSelection();
if(window.getSelection)
{
//do stuff with standards based object
}
else if(document.selection)
{
//do stuff with microsoft object (ie8 and lower)
}
});
Note: the range object its self can be stored in a variable, and can be re-selected at any time unless the contents of the contenteditable div change.
Reference for IE 8 and lower:
http://msdn.microsoft.com/en-us/library/ms535872(VS.85).aspx
Reference for standards (all other) browsers:
https://developer.mozilla.org/en/DOM/range (its the mozilla docs, but code works in chrome, safari, opera and ie9 too)
Try this way to get the Caret position from ContentEditable Div.
Description:
I have written this code for Angular but it also works for native HTML.
The code returns caret position only for SPAN element inside editable div.
My Code:
private getCaretPosition() {
let caretRevCount = 0;
if (window.getSelection) {
const selection = window.getSelection();
const currentNode = selection.focusNode.parentNode;
caretRevCount = selection.focusOffset;
let previousNode = currentNode.previousSibling;
while(previousNode && previousNode.nodeName === 'SPAN') {
// you can check specific element
caretRevCount += previousNode.textContent.length;
previousNode = previousNode.previousSibling;
}
}
return caretRevCount;
}
How code works:
Example scenario: "Hi there, this| is sample text".
Caret position: At the end of "this" text.
Initially, getting the selection area where caret is present from window.getSelection() method.
selection.focusOffSet returns only currentNode text length. In Eg. case currentNode is "this". It returns 4 to caretRevCount.
My approach is to backtrack from current node. So, I am looping previous nodes which is ["there, " , "Hi"] and adding its text length to caretRevCount.
Finally, after the loop gets completed caretRevCount returns a sum value which is caretPosition.
If you set the editable div style to "display:inline-block; white-space: pre-wrap" you don't get new child divs when you enter a new line, you just get LF character (i.e. &#10);.
function showCursPos(){
selection = document.getSelection();
childOffset = selection.focusOffset;
const range = document.createRange();
eDiv = document.getElementById("eDiv");
range.setStart(eDiv, 0);
range.setEnd(selection.focusNode, childOffset);
var sHtml = range.toString();
p = sHtml.length;
sHtml=sHtml.replace(/(\r)/gm, "\\r");
sHtml=sHtml.replace(/(\n)/gm, "\\n");
document.getElementById("caretPosHtml").value=p;
document.getElementById("exHtml").value=sHtml;
}
click/type in div below:
<br>
<div contenteditable name="eDiv" id="eDiv"
onkeyup="showCursPos()" onclick="showCursPos()"
style="width: 10em; border: 1px solid; display:inline-block; white-space: pre-wrap; "
>123
456
789</div>
<p>
html caret position:<br> <input type="text" id="caretPosHtml">
<p>
html from start of div:<br> <input type="text" id="exHtml">
What I noticed was when you press "enter" in the editable div, it creates a new node, so the focusOffset resets to zero. This is why I've had to add a range variable, and extend it from the child nodes' focusOffset back to the start of eDiv (and thus capturing all text in-between).
This one builds on #alockwood05's answer and provides both get and set functionality for a caret with nested tags inside the contenteditable div as well as the offsets within nodes so that you have a solution that is both serializable and de-serializable by offsets as well.
I'm using this solution in a cross-platform code editor that needs to get the caret start/end position prior to syntax highlighting via a lexer/parser and then set it back immediately afterward.
function countUntilEndContainer(parent, endNode, offset, countingState = {count: 0}) {
for (let node of parent.childNodes) {
if (countingState.done) break;
if (node === endNode) {
countingState.done = true;
countingState.offsetInNode = offset;
return countingState;
}
if (node.nodeType === Node.TEXT_NODE) {
countingState.offsetInNode = offset;
countingState.count += node.length;
} else if (node.nodeType === Node.ELEMENT_NODE) {
countUntilEndContainer(node, endNode, offset, countingState);
} else {
countingState.error = true;
}
}
return countingState;
}
function countUntilOffset(parent, offset, countingState = {count: 0}) {
for (let node of parent.childNodes) {
if (countingState.done) break;
if (node.nodeType === Node.TEXT_NODE) {
if (countingState.count <= offset && offset < countingState.count + node.length)
{
countingState.offsetInNode = offset - countingState.count;
countingState.node = node;
countingState.done = true;
return countingState;
}
else {
countingState.count += node.length;
}
} else if (node.nodeType === Node.ELEMENT_NODE) {
countUntilOffset(node, offset, countingState);
} else {
countingState.error = true;
}
}
return countingState;
}
function getCaretPosition()
{
let editor = document.getElementById('editor');
let sel = window.getSelection();
if (sel.rangeCount === 0) { return null; }
let range = sel.getRangeAt(0);
let start = countUntilEndContainer(editor, range.startContainer, range.startOffset);
let end = countUntilEndContainer(editor, range.endContainer, range.endOffset);
let offsetsCounts = { start: start.count + start.offsetInNode, end: end.count + end.offsetInNode };
let offsets = { start: start, end: end, offsets: offsetsCounts };
return offsets;
}
function setCaretPosition(start, end)
{
let editor = document.getElementById('editor');
let sel = window.getSelection();
if (sel.rangeCount === 0) { return null; }
let range = sel.getRangeAt(0);
let startNode = countUntilOffset(editor, start);
let endNode = countUntilOffset(editor, end);
let newRange = new Range();
newRange.setStart(startNode.node, startNode.offsetInNode);
newRange.setEnd(endNode.node, endNode.offsetInNode);
sel.removeAllRanges();
sel.addRange(newRange);
return true;
}
A straight forward way, that iterates through all the chidren of the contenteditable div until it hits the endContainer. Then I add the end container offset and we have the character index. Should work with any number of nestings. uses recursion.
Note: requires a poly fill for ie to support Element.closest('div[contenteditable]')
https://codepen.io/alockwood05/pen/vMpdmZ
function caretPositionIndex() {
const range = window.getSelection().getRangeAt(0);
const { endContainer, endOffset } = range;
// get contenteditableDiv from our endContainer node
let contenteditableDiv;
const contenteditableSelector = "div[contenteditable]";
switch (endContainer.nodeType) {
case Node.TEXT_NODE:
contenteditableDiv = endContainer.parentElement.closest(contenteditableSelector);
break;
case Node.ELEMENT_NODE:
contenteditableDiv = endContainer.closest(contenteditableSelector);
break;
}
if (!contenteditableDiv) return '';
const countBeforeEnd = countUntilEndContainer(contenteditableDiv, endContainer);
if (countBeforeEnd.error ) return null;
return countBeforeEnd.count + endOffset;
function countUntilEndContainer(parent, endNode, countingState = {count: 0}) {
for (let node of parent.childNodes) {
if (countingState.done) break;
if (node === endNode) {
countingState.done = true;
return countingState;
}
if (node.nodeType === Node.TEXT_NODE) {
countingState.count += node.length;
} else if (node.nodeType === Node.ELEMENT_NODE) {
countUntilEndContainer(node, endNode, countingState);
} else {
countingState.error = true;
}
}
return countingState;
}
}
This answer works with nested text elements, using recursive functions. 🪄
Bonus: sets the caret position to saved position.
function getCaretData(elem) {
var sel = window.getSelection();
return [sel.anchorNode, sel.anchorOffset];
}
function setCaret(el, pos) {
var range = document.createRange();
var sel = window.getSelection();
range.setStart(el,pos);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
}
let indexStack = [];
function checkParent(elem) {
let parent = elem.parentNode;
let parentChildren = Array.from(parent.childNodes);
let elemIndex = parentChildren.indexOf(elem);
indexStack.unshift(elemIndex);
if (parent !== cd) {
checkParent(parent);
} else {
return;
}
}
let stackPos = 0;
let elemToSelect;
function getChild(parent, index) {
let child = parent.childNodes[index];
if (stackPos < indexStack.length-1) {
stackPos++;
getChild(child, indexStack[stackPos]);
} else {
elemToSelect = child;
return;
}
}
let cd = document.querySelector('.cd'),
caretpos = document.querySelector('.caretpos');
cd.addEventListener('keyup', () => {
let caretData = getCaretData(cd);
let selectedElem = caretData[0];
let caretPos = caretData[1];
indexStack = [];
checkParent(selectedElem);
cd.innerHTML = 'Hello world! <span>Inline! <span>In inline!</span></span>';
stackPos = 0;
getChild(cd, indexStack[stackPos]);
setCaret(elemToSelect, caretPos);
caretpos.innerText = 'indexStack: ' + indexStack + '. Got child: ' + elemToSelect.data + '. Moved caret to child at pos: ' + caretPos;
})
.cd, .caretpos {
font-family: system-ui, Segoe UI, sans-serif;
padding: 10px;
}
.cd span {
display: inline-block;
color: purple;
padding: 5px;
}
.cd span span {
color: chocolate;
padding: 3px;
}
:is(.cd, .cd span):hover {
border-radius: 3px;
box-shadow: inset 0 0 0 2px #005ecc;
}
<div class="cd" contenteditable="true">Hello world! <span>Inline! <span>In inline!</span></span></div>
<div class="caretpos">Move your caret inside the elements above ⤴</div>
Codepen
So based off of the answer provided by Chris Sullivan, I managed to create a version of it that wouldn't reset when a selection was made via keyboard and was able to detect both the column and the line number.
In this method, you first have to figure out a solution to fetching all of the text up to the carat. You can do this by getting the current selection (which is the caret), cloning the first range of it, collapsing it, then changing the start node of the range to be the beginning of your element. From there, you can extract all the text up to the carat by simply running a toString on the range. Now that you have the text, we can perform some simple calculations on it to determine the line number and column.
For the line number, you simply need to calculate the number of newlines in the string of text. This can be done using some simple regexp, which can be seen in the code below.
For the column number, there's three ways to get a "column number."
The "relative column" to the line number, similar to how Windows Notepad calculates it, is the easiest to calculate. This is simply the range's end offset (range.endOffset).
The actual position of the caret relative to the number of arrow-key presses you would need to press to get to that position. This can be calculated by replacing all of the newlines in the text, and then getting the length of it.
The actual position of the caret relative to the actual text; this you can fetch by just getting the length of the text.
Enough talk, now time for some show:
// Caret
function getCaretPosition(element) {
// Check for selection
if (window.getSelection().type == "None") {
return {
"ln": -1,
"col": -1
}
}
// Copy range
var selection = window.getSelection();
var range = selection.getRangeAt(0).cloneRange();
// Collapse range
range.collapse();
// Move range to encompass everything
range.setStart(element.firstChild, 0);
// Calculate position
var content = range.toString();
var text = JSON.stringify(content);
var lines = (text.match(/\\n/g) || []).length + 1;
// Return caret position (col - 2 due to some weird calculation with regex)
return {
"ln": lines,
// "col": range.endOffset + 1 // Method 1
"col": text.replace(/\\n/g, " ").length - 2 // Method 2
// "col": text.length -2 // Method 3
}
}
Now through this method, if you wanted, you can get the caret position every time the selection is updated:
document.addEventListener("selectionchange", function(e) {
console.log(getCaretPosition(document.getElementById("text-area")));
});
I hope this helps someone, I was pulling my hair out for hours trying to figure out how to do this!
I used John Ernest's excellent code, and reworked it a bit for my needs:
Using TypeScript (in an Angular application);
Using a slightly different data structure.
And while working on it, I stumbled on the little known (or little used) TreeWalker, and simplified the code further, as it allows to get rid of recursivity.
A possible optimization could be to walk the tree once to find both start node and end node, but:
I doubt the speed gain would be perceptible by the user, even at the end of a huge, complex page;
It would make the algorithm more complex and less readable.
Instead, I treated the case where the start is the same as the end (just a caret, no real selection).
[EDIT] It seems that range's nodes are always of Text type, so I simplified code a bit more, and it allows to get the node length without casting it.
Here is the code:
export type CountingState = {
countBeforeNode: number;
offsetInNode: number;
node?: Text; // Always of Text type
};
export type RangeOffsets = {
start: CountingState;
end: CountingState;
offsets: { start: number; end: number; }
};
export function isTextNode(node: Node): node is Text {
return node.nodeType === Node.TEXT_NODE;
}
export function getCaretPosition(container: Node): RangeOffsets | undefined {
const selection = window.getSelection();
if (!selection || selection.rangeCount === 0) { return undefined; }
const range = selection.getRangeAt(0);
const start = countUntilEndNode(container, range.startContainer as Text, range.startOffset);
const end = range.collapsed ? start : countUntilEndNode(container, range.endContainer as Text, range.endOffset);
const offsets = { start: start.countBeforeNode + start.offsetInNode, end: end.countBeforeNode + end.offsetInNode };
const rangeOffsets: RangeOffsets = { start, end, offsets };
return rangeOffsets;
}
export function setCaretPosition(container: Node, start: number, end: number): boolean {
const selection = window.getSelection();
if (!selection) { return false; }
const startState = countUntilOffset(container, start);
const endState = start === end ? startState : countUntilOffset(container, end);
const range = document.createRange(); // new Range() doesn't work for me!
range.setStart(startState.node!, startState.offsetInNode);
range.setEnd(endState.node!, endState.offsetInNode);
selection.removeAllRanges();
selection.addRange(range);
return true;
}
function countUntilEndNode(
parent: Node,
endNode: Text,
offset: number,
countingState: CountingState = { countBeforeNode: 0, offsetInNode: 0 },
): CountingState {
const treeWalker = document.createTreeWalker(parent, NodeFilter.SHOW_TEXT);
while (treeWalker.nextNode()) {
const node = treeWalker.currentNode as Text;
if (node === endNode) {
// We found the target node, memorize it.
countingState.node = node;
countingState.offsetInNode = offset;
break;
}
// Add length of text nodes found in the way, until we find the target node.
countingState.countBeforeNode += node.length;
}
return countingState;
}
function countUntilOffset(
parent: Node,
offset: number,
countingState: CountingState = { countBeforeNode: 0, offsetInNode: 0 },
): CountingState {
const treeWalker = document.createTreeWalker(parent, NodeFilter.SHOW_TEXT);
while (treeWalker.nextNode()) {
const node = treeWalker.currentNode as Text;
if (countingState.countBeforeNode <= offset && offset < countingState.countBeforeNode + node.length) {
countingState.offsetInNode = offset - countingState.countBeforeNode;
countingState.node = node;
break;
}
countingState.countBeforeNode += node.length;
}
return countingState;
}
The code below counts the caret position by taking the offset at the current element and then navigating back all the elements inside the contenteditable and counting the total number of characters.
This will:
Not break formatting functionality
Work with multiple rows.
If you encounter an issue please let me know so I can update the code.
function getRowTextLength(currentNode) {
let previousSibling;
let textLength = 0;
//this means we are outside our desired scope
if (currentNode?.contentEditable == "true") {
return textLength;
}
while (currentNode) {
//get the previous element of the currentNode
previousSibling =
currentNode.previousSibling || //UNFORMATTED text case
//avoid targetting the contenteditable div itself
(currentNode.parentNode.nodeName != "DIV"
? currentNode.parentNode.previousSibling //FORMATTED text case
: null);
//count the number of characters in the previous element, if exists
textLength = previousSibling
? textLength + previousSibling.textContent.length
: textLength;
//set current element as previous element
currentNode = previousSibling;
//continue looping as long as we have a previous element
}
return textLength;
}
//pass e.target from an eventListener as argument
function getCaretPosition(element) {
let selection = getSelection(element);
//caret position at current row
let caretPosition = selection.anchorOffset;
let currentNode = selection.baseNode;
caretPosition += getRowTextLength(currentNode);
//get closest div parent node
if (caretPosition != 0) {
do {
currentNode = currentNode.parentNode;
} while (currentNode.nodeName != "DIV");
}
caretPosition += getRowTextLength(currentNode);
//console.log("CARET POSITION ", caretPosition);
return caretPosition;
}
Get the caret's index position relative to the content editable:
const getCaretPosition = () => {
var selection = document.getSelection();
if (!selection || !divRef) return 0;
selection.collapseToEnd();
const range = selection.getRangeAt(0);
const clone = range.cloneRange();
clone.selectNodeContents(divRef);
clone.setEnd(range.startContainer, range.startOffset);
return clone.toString().length;
}

Javascript full screen display issues

I have been trying to put a text particle effect on my website. I have tested it on Codepen and works great but does not work on my website.
The text shows but the effect does not work as required. The text stays in a static position on the screen but the hover effect is below the word.
taking the mose through the text does nothing, if I move the mouse to the bottom of the screen the effect works on the text above.
The text stays static, however, when I move scroll down, the position of the text effect stays at the bottom of the screen until it ends up below the div and stops working all together.
I was wondering if someone could help me centre this behind the text.
Also I am keen to change the colour of the text, to a linear gradient from #125eaa to #d52027
The link to the page on my site - https://www.supplementgenie.co.uk/testpage
link to my codepen - https://codepen.io/Paulmcf87/pen/gjpgOB
However, when I run this code on JS Fiddle it gives me a 404 error?
You will see, running code below it works great as on Codepen. It doesnt seem to translate properly to a full screen
Any help would be great
The code I am using
var pixels=new Array();
var canv=$('canv');
var ctx=canv.getContext('2d');
var wordCanv=$('wordCanv');
var wordCtx=wordCanv.getContext('2d');
var mx=-1;
var my=-1;
var words="";
var txt=new Array();
var cw=0;
var ch=0;
var resolution=1;
var n=0;
var timerRunning=false;
var resHalfFloor=0;
var resHalfCeil=0;
function canv_mousemove(evt)
{
mx=evt.clientX-canv.offsetLeft;
my=evt.clientY-canv.offsetTop;
}
function Pixel(homeX,homeY)
{
this.homeX=homeX;
this.homeY=homeY;
this.x=Math.random()*cw;
this.y=Math.random()*ch;
//tmp
this.xVelocity=Math.random()*10-5;
this.yVelocity=Math.random()*10-5;
}
Pixel.prototype.move=function()
{
var homeDX=this.homeX-this.x;
var homeDY=this.homeY-this.y;
var homeDistance=Math.sqrt(Math.pow(homeDX,2) + Math.pow(homeDY,2));
var homeForce=homeDistance*0.01;
var homeAngle=Math.atan2(homeDY,homeDX);
var cursorForce=0;
var cursorAngle=0;
if(mx >= 0)
{
var cursorDX=this.x-mx;
var cursorDY=this.y-my;
var cursorDistanceSquared=Math.pow(cursorDX,2) + Math.pow(cursorDY,2);
cursorForce=Math.min(10000/cursorDistanceSquared,10000);
cursorAngle=Math.atan2(cursorDY,cursorDX);
}
else
{
cursorForce=0;
cursorAngle=0;
}
this.xVelocity+=homeForce*Math.cos(homeAngle) + cursorForce*Math.cos(cursorAngle);
this.yVelocity+=homeForce*Math.sin(homeAngle) + cursorForce*Math.sin(cursorAngle);
this.xVelocity*=0.92;
this.yVelocity*=0.92;
this.x+=this.xVelocity;
this.y+=this.yVelocity;
}
function $(id)
{
return document.getElementById(id);
}
function timer()
{
if(!timerRunning)
{
timerRunning=true;
setTimeout(timer,33);
for(var i=0;i<pixels.length;i++)
{
pixels[i].move();
}
drawPixels();
wordsTxt.focus();
n++;
if(n%10==0 && (cw!=document.body.clientWidth || ch!=document.body.clientHeight)) body_resize();
timerRunning=false;
}
else
{
setTimeout(timer,10);
}
}
function getRandomColor(min, max) {
return Math.random() * (max - min) + min;
}
function drawPixels()
{
var imageData=ctx.createImageData(cw,ch);
var actualData=imageData.data;
var index;
var goodX;
var goodY;
var realX;
var realY;
for(var i=0;i<pixels.length;i++)
{
goodX=Math.floor(pixels[i].x);
goodY=Math.floor(pixels[i].y);
for(realX=goodX-resHalfFloor; realX<=goodX+resHalfCeil && realX>=0 && realX<cw;realX++)
{
for(realY=goodY-resHalfFloor; realY<=goodY+resHalfCeil && realY>=0 && realY<ch;realY++)
{
index=(realY*imageData.width + realX)*4;
actualData[index+3]=realX;
actualData[index+2]=realX;
actualData[index+1]=realY;
}
}
}
imageData.data=actualData;
ctx.putImageData(imageData,0,0);
}
function readWords()
{
words=$('wordsTxt').value;
txt=words.split('\n');
}
function init()
{
readWords();
var fontSize=200;
var wordWidth=0;
do
{
wordWidth=0;
fontSize-=5;
wordCtx.font=fontSize+"px Avenir, sans-serif";
for(var i=0;i<txt.length;i++)
{
var w=wordCtx.measureText(txt[i]).width;
if(w>wordWidth) wordWidth=w;
}
} while(wordWidth>cw-50 || fontSize*txt.length > ch-50)
wordCtx.clearRect(0,0,cw,ch);
wordCtx.textAlign="center";
wordCtx.textBaseline="middle";
for(var i=0;i<txt.length;i++)
{
wordCtx.fillText(txt[i],cw/2,ch/2 - fontSize*(txt.length/2-(i+0.5)));
}
var index=0;
var imageData=wordCtx.getImageData(0,0,cw,ch);
for(var x=0;x<imageData.width;x+=resolution) //var i=0;i<imageData.data.length;i+=4)
{
for(var y=0;y<imageData.height;y+=resolution)
{
i=(y*imageData.width + x)*4;
if(imageData.data[i+3]>128)
{
if(index >= pixels.length)
{
pixels[index]=new Pixel(x,y);
}
else
{
pixels[index].homeX=x;
pixels[index].homeY=y;
}
index++;
}
}
}
pixels.splice(index,pixels.length-index);
}
function body_resize()
{
cw=document.body.clientWidth;
ch=document.body.clientHeight;
canv.width=cw;
canv.height=ch;
wordCanv.width=cw;
wordCanv.height=ch;
init();
}
wordsTxt.focus();
wordsTxt.value="Supplement Genie";
resolution=1;
resHalfFloor=Math.floor(resolution/2);
resHalfCeil=Math.ceil(resolution/2);
body_resize();
timer();
#wordsTxt{
display:none
}
div.pixeltext canvas{
width:98vw;
height:100vh;
}
div.pixeltext{
background-color: #d52027;
<div class="pixeltext">
<canvas id="canv" onmousemove="canv_mousemove(event);" onmouseout="mx=-1;my=-1;">
you need a canvas-enabled browser, such as Google Chrome
</canvas>
<canvas id="wordCanv" width="500px" height="500px" style="border:1px solid black;display:none;">
</canvas>
<textarea id="wordsTxt" style="position:absolute;left:-100;top:-100;" onblur="init();" onkeyup="init();" onclick="init();"></textarea>
<div>
You should define the cw,ch variables based on the canvas dimensions and not on the document.body.clientWidth etc.
Also you should avoid using canv.offsetLeft and canv.offsetTop since your element is inside other positioned elements and the offset properties return the position relative to the closest positioned ancestor.
Using canv.getBoundingClientRect which returns the width/height and top/left/right/bottom properties relative to the viewport will solve the problem.
(your codepen was also using wrong names of the methods you had created)
See https://codepen.io/gpetrioli/pen/rreagp which basically modifies the following methods
function canvMousemove(evt) {
var rect = canv.getBoundingClientRect();
mx = evt.clientX - rect.left;
my = evt.clientY - rect.top;
}
function body_resize() {
var rect = canv.getBoundingClientRect()
cw = rect.width;
ch = rect.height;
console.log(ch);
canv.width = cw;
canv.height = ch;
wordCanv.width = cw;
wordCanv.height = ch;
init();
}
In your site i noticed a timer method which should also be modified to
function timer() {
var rect;
if (!timerRunning) {
timerRunning = true;
setTimeout(timer, 33);
for (var i = 0; i < pixels.length; i++) {
pixels[i].move();
}
drawPixels();
wordsTxt.focus();
n++;
rect = canv.getBoundingClientRect();
if (n % 10 == 0 && (cw != rect.width || ch != rect.height)) body_resize();
timerRunning = false;
} else {
setTimeout(timer, 10);
}
}

Get cursor writing positions

Is it possible to get the positions of cursor of writing (absolute x,y pixels positions of the last character) inside the input textarea
Note: its not just count number of characters, but I have to deal with new lines, for example, if the user type the Enter key (I have to detect the new position in pixels of the last character
I want this because i need to display a popup with suggestions while users type texts
if you have an example in reactjs or in classic javascript (not jquery) please share with your code
i hope that my question was clear.
Thank you in advance
Finally i found a solution:
here the code for reactjs
var text = this.refs.areatext,
coords = {};
var carPos = text.selectionEnd,
div = document.createElement("div"),
span = document.createElement("span"),
copyStyle = getComputedStyle(text);
[].forEach.call(copyStyle, function(prop){
div.style[prop] = copyStyle[prop];
});
div.style.position = "absolute";
document.body.appendChild(div);
div.textContent = text.value.substr(0, carPos);
span.textContent = text.value.substr(carPos) || ".";
div.appendChild(span);
coords = {
"TOP": span.offsetTop,
"LEFT": span.offsetLeft
};
document.body.removeChild(div);
this.setState({x:coords.LEFT,y:coords.TOP})
for javascript
(function() {
var text = document.querySelector(‘textarea’),
indicator = document.querySelector(‘.indicator’),
getCoord = function(e) {
var carPos = text.selectionEnd,
div = document.createElement(‘div’),
span = document.createElement(‘span’),
copyStyle = getComputedStyle(text),
coords = {};
[].forEach.call(copyStyle, function(prop){
div.style[prop] = copyStyle[prop];
});
div.style.position = ‘absolute’;
document.body.appendChild(div);
div.textContent = text.value.substr(0, carPos);
span.textContent = text.value.substr(carPos) || ‘.’;
div.appendChild(span);
coords = {
‘TOP’: span.offsetTop,
‘LEFT’: span.offsetLeft
};
console.log(coords);
indicator.style.left = coords.LEFT + ‘px’;
indicator.style.top = coords.TOP + ‘px’;
document.body.removeChild(div);
};
text.addEventListener(‘input’, getCoord);
}());
Please check this code, i have made cursor detection in javascript, hope will help you.
window.onload = init;
function init() {
if (window.Event) {
document.captureEvents(Event.MOUSEMOVE);
}
document.onmousemove = getCursorXY;
}
function getCursorXY(e) {
document.getElementById('cursorX').value = (window.Event) ? e.pageX : event.clientX + (document.documentElement.scrollLeft ? document.documentElement.scrollLeft : document.body.scrollLeft);
document.getElementById('cursorY').value = (window.Event) ? e.pageY : event.clientY + (document.documentElement.scrollTop ? document.documentElement.scrollTop : document.body.scrollTop);
}
<html>
<body>
<input type="text" id="cursorX" size="3"> X-position of the mouse cursor
<br /><br />
<input type="text" id="cursorY" size="3"> Y-position of the mouse cursor
</body>
</html>

Set text-cursor position in a textarea

I'm working on a BBCode editor and here is the code:
var txtarea = document.getElementById("editor_area");
function boldText() {
var start = txtarea.selectionStart;
var end = txtarea.selectionEnd;
var sel = txtarea.value.substring(start, end);
var finText = txtarea.value.substring(0, start) + '[b]' + sel + '[/b]' + txtarea.value.substring(end);
txtarea.value = finText;
txtarea.focus();
}
Everything is OK except one thing which is the position of the text-cursor. When I click on the boldText button, it sets the cursor position at the end of the Textarea!!
Actually, I want to be able to set the cursor position at a certain index. I want something like this:
txtarea.setFocusAt(20);
After refocusing the textarea with txtarea.focus(), add this line:
txtarea.selectionEnd= end + 7;
That will set the cursor seven positions ahead of where it was previously, which will take [b][/b] into account.
Example
document.getElementById('bold').addEventListener('click', boldText);
function boldText() {
var txtarea = document.getElementById("editor_area");
var start = txtarea.selectionStart;
var end = txtarea.selectionEnd;
var sel = txtarea.value.substring(start, end);
var finText = txtarea.value.substring(0, start) + '[b]' + sel + '[/b]' + txtarea.value.substring(end);
txtarea.value = finText;
txtarea.focus();
txtarea.selectionEnd= end + 7;
}
#editor_area {
width: 100%;
height: 10em;
}
<button id="bold">B</button>
<textarea id="editor_area"></textarea>
if you are using jquery you can do it like this.
$('textarea').prop('selectionEnd', 13);
you can use these 2 functions below written by Jamie Munro (setSelectionRange() & setCaretToPos()):
function setSelectionRange(input, selectionStart, selectionEnd) {
if (input.setSelectionRange) {
input.focus();
input.setSelectionRange(selectionStart, selectionEnd);
}
else if (input.createTextRange) {
var range = input.createTextRange();
range.collapse(true);
range.moveEnd('character', selectionEnd);
range.moveStart('character', selectionStart);
range.select();
}
}
function setCaretToPos (input, pos) {
setSelectionRange(input, pos, pos);
}
EXAMPLE:
for example, if you want to set the caret at the end of your textarea you can have this:
setCaretToPos(document.getElementById('textarea'), -1);
Through JQuery:
var cursorPos = $('#textarea').prop('selectionStart');
$('#textarea').prop('selectionEnd',cursorPos-2);
Realizing this is an older question, this is offered only as something to think about as an option now because it may likely be more efficient than extracting and assembling pieces of the textarea value string, and it sets the cursor automatically based on the fourth argument of setRangeText and autofocuses also. It works in Firefox 66.0.02 and I haven't tested it elsewhere. The cursor is placed after the '[/b]'.
t = document.getElementById("editor_area");
b = t.selectionStart,
e = t.selectionEnd + 3; // length of '[b]'
t.setSelectionRange( b, b );
t.setRangeText( '[b]' );
t.setSelectionRange( e, e );
t.setRangeText( '[/b]', e, e, 'end' );
This is a little OT, but if anyone is interested:
Brief: Set cursor inside input element throug row and column
Dependency: setSelectionRange() from #ashkan nasirzadeh
Example call: setTextCursor(textarea,textarea.val, 0, 1);
// #brief: set cursor inside _input_ at position (column,row)
// #input: input DOM element. E.g. a textarea
// #content: textual content inside the DOM element
// #param row: starts a 0
// #param column: starts at 0
function setTextCursor(input, content, row, column){
// search row times:
var pos = 0;
var prevPos = 0;
for( var i = 0; (i<row) && (pos != -1); ++i){
prevPos = pos;
pos = content.indexOf("\n",pos+1);
}
// if we can't go as much down as we want,
// go as far as worked
if(-1 == pos){ pos = prevPos; }
if(0 != row)
++pos; // one for the linebreak
// prevent cursor from going beyond the current line
var lineEndPos = content.indexOf("\n", pos+1);
if((-1 != lineEndPos) &&
(column > lineEndPos-pos)){
// go *only* to the end of the current line
pos = lineEndPos;
} else{
// act as usual
pos += column
}
setSelectionRange(input, pos,pos);
}

JavaScript mouseover/mousemove cusor postion without clicking in input text box

I'm attempting to combine a JavaScript mechanism for auto placing the users cursor inside of an input box through the mouseover and mousemove listeners.
I have an almost perfect working example here: http://codepen.io/anon/pen/doxNLm?editors=101
var current_element = document.getElementById("hover");
current_element.onmousemove = function showCoords(evt) {
var form = document.forms.form_coords;
var parent_id = this.id;
form.parentId.value = parent_id;
form.pageXCoords.value = evt.pageX;
form.pageYCoords.value = evt.pageY;
form.layerXCoords.value = evt.layerX;
form.layerYCoords.value = evt.layerY;
function getTextWidth(text, font) {
// re-use canvas object for better performance
var canvas = getTextWidth.canvas || (getTextWidth.canvas = document.createElement("canvas"));
var context = canvas.getContext("2d");
context.font = font;
var metrics = context.measureText(text);
return metrics.width;
};
var element_base_browser_styles = window.getDefaultComputedStyle(current_element);
var total_text_pixal_length = getTextWidth(current_element.value, element_base_browser_styles.fontFamily + " " + element_base_browser_styles.fontSize);
var add_char_pixal_lengths = 0;
var myStringArray = current_element.value.split('');
var arrayLength = myStringArray.length;
for (var i = 0; i <= arrayLength; i++) {
var get_char_value = getTextWidth(myStringArray[i], element_base_browser_styles.fontFamily + " " + element_base_browser_styles.fontSize);
add_char_pixal_lengths = add_char_pixal_lengths + (get_char_value) + 1.311111111111; //every char value is added together.
// console.log("Total: " + x);
if ((add_char_pixal_lengths)> (evt.layerX)) {
this.setSelectionRange(i, i);
add_char_pixal_lengths = 0;
break;
}
}
}
current_element.onmouseover = function() {
this.focus()
}
The problem I'm having is like Geosynchronous orbit; the cursor shifts out of place sometimes a few pixels (left or right). My calculation probably sucks, but I'm not sure canvas is really the best way to do the measurement? Is there a better way?
mousemove listener to receive element cursor coordinates from e.pageX
font style using window.getComputedStyles(input_element)
arr.split('') from input_element.text string: x = ['a','b','c']
'for loop' the array, generate a canvas and measure each characters width
add all char widths one by one until the value is greater than e.pageX
set the 'for loop' iterate as the setSelectionRange(i, i)
Any help or suggestions on making this better would be appreciated. Thanks!

Categories

Resources