Make multiple contenteditables behave like one document - javascript

I have a bunch of multiline contenteditable divs arranged vertically, and I want to allow for natural navigation between them by arrow keys (as if it was one document). For that, on keydown event I need to:
Know current row of caret and number of rows to determine if we need to move up (first line and ↑ key pressed) or down (last line and ↓ key)
Know current character (position in a shown string) to determine if we need to move up (position==0 and ← key pressed) or down (position==text.length and → pressed)
The process should not stop between switching elements when the key is being held and not released (hence keydown event, not keyup)
Preferrably: The event should stop propagating (for example, if I'm on the first column on last row and I press ↓ key, it should not jump to the last character on the line and then go down)
Preferrably (would be really awesome): After we jump to the next element, we would not just .focus() the element, but emulate a click in the same vertical position as where we were before, so that it would feel natural, like in text editors.
All scripts/libraries I had found to date are either not doing all things I need or buggy. Please include demos in your suggestions, so that I can test without incorporating in my code first. Thanks!
Update: Visual explanation - note that there are more than 2 divs and 'arrow down key on the last line' is just one of the four triggers

I already wrote some code but it's not finished at all... Maybe you can start with that and try to complete what I've done if you want ;) I'll continue working on it this week in order to provide you with a solution... Here is what I've done so far :
var ajaxResult = [
"Inter has ruinarum varietates a Nisibi quam tuebatur accitus Vrsicinus, cui nos obsecuturos iunxerat imperiale praeceptum, dispicere litis exitialis certamina cogebatur. Inter has ruinarum varietates a Nisibi quam tuebatur accitus Vrsicinus, cui nos obsecuturos iunxerat imperiale praeceptum, dispicere litis exitialis certamina cogebatur. Inter has ruinarum varietates exitialis certamina cogebatur",
"Inter has ruinarum varietates a Nisibi quam tuebatur accitus",
"Inter has ruinarum varietates a Nisibi quam tuebatur accitus Vrsicinus, cui nos obsecuturos iunxerat imperiale praeceptum, dispicere litis exitialis certamina cogebatur. Inter has ruinarum varietates a Nisibi quamos iunxerat imperiale praeceptum, dispicere litis exitialis certamina cogebatur. Inter has ruinarum varietates exitialis certamina cogebatur",
];
/*************************************************************
*
* LIST OF CONTENT EDITABLE DIVS MANAGEMENT
*
**************************************************************/
// Create the editable divs
window.onload = function(){
var contentEditables = createContentEditables();
document.body.appendChild(contentEditables);
}
// Remember all the content editable elements in the order they appear in the dom
var _currentEdit,
_edits = [];
function createContentEditables(){
var div;
var result = document.createDocumentFragment();
for (var i = 0, n = ajaxResult.length ; i < n ; i++){
div = createContentEditable(ajaxResult[i]);
_edits.push(div);
result.appendChild(div);
}
return result;
}
function getPreviousEdit(edit){
// Search for the edit index
var index = _edits.indexOf(edit);
if(index == 0)
return;
// Return the previous one
return _edits[index - 1];
}
function getNextEdit(edit){
// Search for the edit index
var index = _edits.indexOf(edit);
if(index == _edits.length - 1)
return;
// Return the previous one
return _edits[index + 1];
}
/*************************************************************
*
* CONTENT EDITABLE MANAGEMENT
*
**************************************************************/
// We need to define the line height of the div to be able to retrieve the number of lines
var LINE_HEIGHT = 16;
// variables to keep trace of relevant information about the div
var _lines, _caretPosition;
/*
* Create a div with contenteditable set to true with the text
* received from the server
*/
function createContentEditable(text){
var element = document.createElement('div');
element.className = 'contenteditable';
element.innerHTML = text;
element.style.lineHeight = LINE_HEIGHT + 'px';
element.setAttribute('contenteditable', true);
// Set listeners
element.addEventListener('mouseup', onEdit_mouseup);
element.addEventListener('keydown', onEdit_keydown);
element.addEventListener('focus', onEdit_focus);
return element;
}
function onEdit_keydown(domEvent){
// Update caret position
_caretPosition = getCaretPosition(domEvent.target);
switch(domEvent.keyCode){
case 37: // left arrow
if (_caretPosition.index == 0){
var previousEdit = getPreviousEdit(domEvent.target);
if(previousEdit){
console.log("go to end of previous edit");
console.log(previousEdit);
previousEdit.focus();
}
}
break;
case 38: // up arrow
if (_caretPosition.line == 1){
var previousEdit = getPreviousEdit(domEvent.target);
if(previousEdit){
console.log("go to previous edit keeping the caret offset");
console.log(previousEdit);
previousEdit.focus();
}
}
break;
case 39: // right arrow
if (_caretPosition.index == domEvent.target.innerHTML.length){
var nextEdit = getNextEdit(domEvent.target);
if(nextEdit){
console.log("go to beginning of next edit");
console.log(nextEdit);
nextEdit.focus();
}
}
break;
case 40: // down arrow
if (_caretPosition.line == getLines(domEvent.target)){
var nextEdit = getNextEdit(domEvent.target);
if(nextEdit){
console.log("go to next edit keeping the caret offset");
console.log(nextEdit);
nextEdit.focus();
}
}
break;
}
}
function onEdit_mouseup(domEvent){
// Update caret position
_caretPosition = getCaretPosition(domEvent.target);
}
function onEdit_focus(domEvent){
// Add listeners
_currentEdit = domEvent.target;
_currentEdit.addEventListener('blur', onEdit_blur);
window.addEventListener('resize', onWindow_resize);
}
function onEdit_blur(domEvent){
// Remove listeners
domEvent.target.removeEventListener('blur', onEdit_blur);
window.removeEventListener('resize', onWindow_resize);
}
function onWindow_resize(domEvent){
// Update caret position
_caretPosition = getCaretPosition(_currentEdit);
}
/*************************************************************
*
* HELPERS
*
**************************************************************/
//http://stackoverflow.com/questions/4811822/get-a-ranges-start-and-end-offsets-relative-to-its-parent-container/4812022#4812022
//http://stackoverflow.com/questions/5528004/how-to-get-number-of-rows-in-contenteditable-area-and-current-caret-line-positio
function getCaretPosition(element){
var caretPosition = {index: 0, line: 0};
var doc = element.ownerDocument || element.document;
var win = doc.defaultView || doc.parentWindow;
var elemOffsetTop = element.offsetTop;
var sel;
// Get the x position of the caret
if (typeof win.getSelection != "undefined") {
sel = win.getSelection();
if (sel.rangeCount > 0) {
var range = win.getSelection().getRangeAt(0);
// Retrieve the current line
var rects = range.getClientRects();
var caretOffsetTop;
if (typeof rects[1] != "undefined"){
caretOffsetTop = rects[1].top;
}
else if (typeof rects[0] != "undefined"){
caretOffsetTop = rects[0].top;
}
else{
// Create dummy element to get y position of the caret
var dummy = document.createElement('CANVAS');
dummy.id = 'findCaretHelper';
range.insertNode(dummy);
caretOffsetTop = dummy.offsetTop;
element.removeChild(dummy);
}
var preCaretRange = range.cloneRange();
preCaretRange.selectNodeContents(element);
preCaretRange.setEnd(range.endContainer, range.endOffset);
// Remember caret position
caretPosition.index = preCaretRange.toString().length;
caretPosition.line = Math.ceil((caretOffsetTop - elemOffsetTop)/LINE_HEIGHT) + 1;
}
}
// support ie
//else if ( (sel = doc.selection) && sel.type != "Control") {
//var textRange = sel.createRange();
//var preCaretTextRange = doc.body.createTextRange();
//preCaretTextRange.moveToElementText(element);
//preCaretTextRange.setEndPoint("EndToEnd", textRange);
//caretPosition.x = preCaretTextRange.text.length;
//}
return caretPosition;
}
function getLines(element){
return element.clientHeight/LINE_HEIGHT;;
}
.contenteditable{
border: solid 1px #aaa;
margin: 10px 0;
}
I managed getting information about the current line, the current character index in the content editable div and some other stuff... I still have to work on focusing an other content editable div in order to put the caret at the right place... I hope this beginning of a solution will help you!

You could simply make the parent element / containing element contenteditable as opposed to each paragraph. This will automatically add / remove p tags accordingly.
https://jsfiddle.net/ewesmwmv/2/

Related

How to set caret position in a contenteditable after I append a span into it; and move caret to the end of the span?

I have a contenteditable that I am using for a chat application. When I tag a user, I find the current caretPosition and add a span to that location which is like this;
<span class=\"highlight\" contenteditable=\"false\">${userName}</span>
When I replace the tag with this span however, I lose focus of the contenteditable and the caret position is lost. I've tried looking at these answers,
Answer 1
Answer 2
but in both cases focus is lost, and if I set el.focus() before the setCursorPosition function, the caret just goes to the beginning of the contenteditable. my code;
getCaretPositionInnerHTML() {
var target = document.createTextNode("\u0001");
document.getSelection().getRangeAt(0).insertNode(target);
var position = this.contenteditable.nativeElement.innerHTML.indexOf("\u0001");
target.parentNode.removeChild(target);
return position;
}
saveMention(event, user): void { //lose focus after this
event.stopPropagation();
let tempString = this.contenteditable.nativeElement.innerHTML;
let index = this.getCaretPositionInnerHTML();
let replacement = this.getSpanFromUserFn(user);// returns the span in question
let currentWordLength = this.getCurrentWord(tempString, index);// replace the current word with my span
let newString = tempString.substring(0, index - currentWordLength.length) + replacement + tempString.substring(index + 1);
this.contenteditable.nativeElement.innerHTML = newString;
}
ngOnChanges(changes: SimpleChanges) {
if (this.caretPosition) {
console.log(changes);
this.selection.removeAllRanges();
let range = this.setCursorPosition(this.element.nativeElement, document.createRange(), { pos: this.caretPosition, done: false});
this.element.nativeElement.focus();
range.collapse(true);
this.sel.addRange(range);
}
//find the child node and relative position and set it on range
setCursorPosition(parent, range, stat) {
if (stat.done) return range;
if (parent.childNodes.length == 0) {
if (parent.textContent.length >= stat.pos) {
range.setStart(parent, stat.pos);
stat.done = true;
} else {
stat.pos = stat.pos - parent.textContent.length;
}
} else {
for (var i = 0; i < parent.childNodes.length && !stat.done; i++) {
this.currentNode = parent.childNodes[i];
this.setCursorPosition(this.currentNode, range, stat);
}
}
return range;
}

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;
}

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 to increase/decrease font size (external css)

I managed to pull together a script that will increase the font sizes of parts of a web page using buttons or links. It works in Moz/Chrome, but sticks on IE, tho theoretically it shouldn't have a issue in these major browsers. But I'm stuck on whether or not it's possible to use currentStyle to get the fontSize from a variable populated by getElementsByName; certainly IE is drawing blanks.
Here's my script:
function changeFontSize(element,step)
{
var styleProp = 'font-size';
step = parseInt(step,10);
var x = document.getElementsByName(element);
for(i=0;i<x.length;i++) {
if (x[i].currentStyle) {
var y = parseInt(x[i].currentStyle[styleProp],10);
} else if (window.getComputedStyle) {
var y = parseInt(document.defaultView.getComputedStyle(x[i],null).getPropertyValue(styleProp),10);
}
x[i].style.fontSize = (y+step) + 'px';
}
}
The 3 sites I've used to pull this together are:
www.vijayjoshi.org
www.quirksmode.org
and (this isn't spam, this is actually important) //http://www.white-hat-web-design.co.uk/blog/controlling-font-size-with-javascript/
Can anyone point out a solution please? Thanks in advance!
what about updating your code with the following :
function changeFontSize(element,step)
{
function computeFontSizeUpdate(oE)
{
//- init fSize with proper null value
var fSize = null;
//- retrieve fSize from style
if (oE.currentStyle) {
fSize = parseInt(oE.currentStyle[styleProp], 10);
}
else if (window.getComputedStyle) {
var s = document.defaultView.getComputedStyle(oE,null);
fSize = (s) ? parseInt(s.getPropertyValue(styleProp),10) : NaN;
}
//- check fSize value based on return of parseInt function
if( isNaN(fSize) == false && fSize != null)
{
fSize += nStep + 'px';
if(oE.currentStyle)
oE.currentStyle.fontSize = fSize;
else
oE.style.fontSize = fSize;
}
};
var styleProp = 'font-size';
var nStep = parseInt(step, 10);
//- ensure step value
if( isNaN(nStep) ) nStep = 0;
//- get target elements
var oElems = document.getElementsByName(element);
if ( oElems && oElems.length == 0)
{
var oE = document.getElementById(element);
if(oE) computeFontSizeUpdate(oE);
}
else
{
for(oE in oElems)
{
computeFontSizeUpdate(oE);
}
}
}
I have updated script with fix and few better naming for some variables.
Also, I am sorry cause I am on Mac right now, I wasn't able to test the provided script in IE ... but from what I remember it should do the trick.
Using some JS console you can directly execute directly on this page
changeFontSize("nav-tags", 50);
and you will notice that the Tags element in the menu bar would get affected :)
Hope this helps

Get cursor or text position in pixels for input element

IE allows me to create a text range in an input element, upon which I can call getBoundingClientRect() and get the position in pixels of a certain character or the cursor/caret. Is there any way of getting the position of a certain character in pixels in other browsers?
var input = $("#myInput")[0];
var pixelPosition = null;
if (input.createTextRange)
{
var range = input.createTextRange();
range.moveStart("character", 6);
pixelPosition = range.getBoundingClientRect();
}
else
{
// Is there any way to create a range on an input's value?
}
I'm using jQuery, but I doubt it will be able to address my situation. I expect a pure JavaScript solution, if any, but jQuery answers are welcome.
Demo
I have written a function which behaves as expected. A very detailed demonstration panel can be found here: Fiddle: http://jsfiddle.net/56Rep/5/
The interface in the demo is self-explanatory.
The functionality as requested in the question would be implemented in my function as follows:
var pixelPosition = getTextBoundingRect(input, 6)
Function dependencies
Updated: The function is pure JavaScript, and not dependent on any plugin or framework!
The function assumes that the getBoundingClientRect method exist. Text ranges are used when they're supported. Otherwise, the functionality is achieved using my function logic.
Function logic
The code itself contains several comments. This part goes in a deeper detail.
One temporary <div> container is created.
1 - 3 <span> elements are created. Each span holds a part of the input's value (offsets 0 to selectionStart, selectionStart to selectionEnd, selectionEnd to end of string, only the second span is meaninngful).
Several significant style properties from the input element are copied to these <div> and <span> tags. Only significant style properties are copied. For example, color is not copied, because it does not affect the offsets of a character in any way.#1
The <div> is positioned at the exact position of the text node (input's value). Borders and paddings are taken into account, to make sure that the temporary <div> is correctly positioned.
A variable is created, which holds the return value of div.getBoundingClientRect().
The temporary <div> is removed, unless parameter debug is set to true.
The function returns the ClientRect object. For more information about this object, see this page. The demo also shows a list of properties: top, left, right, bottom, height and width.
#1: getBoundingClientRect() (and some minor properties) is used to determine the position of the input element. Then, the padding and border width are added, to get the real position of a text node.
Known issues
The only case of an inconsistency was encountered when getComputedStyle returned a wrong value for font-family: When a page hasn't defined a font-family property, the computedStyle returns an incorrect value (even Firebug is experiencing this issue; environment: Linux, Firefox 3.6.23, font "Sans Serif").
As visible in the demo, the positioning is sometimes slightly off (almost zero, always smaller than 1 pixel).
Technical restrictions prevents the script from getting the exact offset of a text fragment when the contents has been moved, e.g. when the first visible character in an input field does not equal the first value's character.
Code
// #author Rob W http://stackoverflow.com/users/938089/rob-w
// #name getTextBoundingRect
// #param input Required HTMLElement with `value` attribute
// #param selectionStart Optional number: Start offset. Default 0
// #param selectionEnd Optional number: End offset. Default selectionStart
// #param debug Optional boolean. If true, the created test layer
// will not be removed.
function getTextBoundingRect(input, selectionStart, selectionEnd, debug) {
// Basic parameter validation
if(!input || !('value' in input)) return input;
if(typeof selectionStart == "string") selectionStart = parseFloat(selectionStart);
if(typeof selectionStart != "number" || isNaN(selectionStart)) {
selectionStart = 0;
}
if(selectionStart < 0) selectionStart = 0;
else selectionStart = Math.min(input.value.length, selectionStart);
if(typeof selectionEnd == "string") selectionEnd = parseFloat(selectionEnd);
if(typeof selectionEnd != "number" || isNaN(selectionEnd) || selectionEnd < selectionStart) {
selectionEnd = selectionStart;
}
if (selectionEnd < 0) selectionEnd = 0;
else selectionEnd = Math.min(input.value.length, selectionEnd);
// If available (thus IE), use the createTextRange method
if (typeof input.createTextRange == "function") {
var range = input.createTextRange();
range.collapse(true);
range.moveStart('character', selectionStart);
range.moveEnd('character', selectionEnd - selectionStart);
return range.getBoundingClientRect();
}
// createTextRange is not supported, create a fake text range
var offset = getInputOffset(),
topPos = offset.top,
leftPos = offset.left,
width = getInputCSS('width', true),
height = getInputCSS('height', true);
// Styles to simulate a node in an input field
var cssDefaultStyles = "white-space:pre;padding:0;margin:0;",
listOfModifiers = ['direction', 'font-family', 'font-size', 'font-size-adjust', 'font-variant', 'font-weight', 'font-style', 'letter-spacing', 'line-height', 'text-align', 'text-indent', 'text-transform', 'word-wrap', 'word-spacing'];
topPos += getInputCSS('padding-top', true);
topPos += getInputCSS('border-top-width', true);
leftPos += getInputCSS('padding-left', true);
leftPos += getInputCSS('border-left-width', true);
leftPos += 1; //Seems to be necessary
for (var i=0; i<listOfModifiers.length; i++) {
var property = listOfModifiers[i];
cssDefaultStyles += property + ':' + getInputCSS(property) +';';
}
// End of CSS variable checks
var text = input.value,
textLen = text.length,
fakeClone = document.createElement("div");
if(selectionStart > 0) appendPart(0, selectionStart);
var fakeRange = appendPart(selectionStart, selectionEnd);
if(textLen > selectionEnd) appendPart(selectionEnd, textLen);
// Styles to inherit the font styles of the element
fakeClone.style.cssText = cssDefaultStyles;
// Styles to position the text node at the desired position
fakeClone.style.position = "absolute";
fakeClone.style.top = topPos + "px";
fakeClone.style.left = leftPos + "px";
fakeClone.style.width = width + "px";
fakeClone.style.height = height + "px";
document.body.appendChild(fakeClone);
var returnValue = fakeRange.getBoundingClientRect(); //Get rect
if (!debug) fakeClone.parentNode.removeChild(fakeClone); //Remove temp
return returnValue;
// Local functions for readability of the previous code
function appendPart(start, end){
var span = document.createElement("span");
span.style.cssText = cssDefaultStyles; //Force styles to prevent unexpected results
span.textContent = text.substring(start, end);
fakeClone.appendChild(span);
return span;
}
// Computing offset position
function getInputOffset(){
var body = document.body,
win = document.defaultView,
docElem = document.documentElement,
box = document.createElement('div');
box.style.paddingLeft = box.style.width = "1px";
body.appendChild(box);
var isBoxModel = box.offsetWidth == 2;
body.removeChild(box);
box = input.getBoundingClientRect();
var clientTop = docElem.clientTop || body.clientTop || 0,
clientLeft = docElem.clientLeft || body.clientLeft || 0,
scrollTop = win.pageYOffset || isBoxModel && docElem.scrollTop || body.scrollTop,
scrollLeft = win.pageXOffset || isBoxModel && docElem.scrollLeft || body.scrollLeft;
return {
top : box.top + scrollTop - clientTop,
left: box.left + scrollLeft - clientLeft};
}
function getInputCSS(prop, isnumber){
var val = document.defaultView.getComputedStyle(input, null).getPropertyValue(prop);
return isnumber ? parseFloat(val) : val;
}
}
I ended up creating a hidden mock input out of a span positioned absolutely and styled similarly to the input. I set the text of that span to the value of the input up to the character whose position I want to find. I insert the span before the input and get it's offset:
function getInputTextPosition(input, charOffset)
{
var pixelPosition = null;
if (input.createTextRange)
{
var range = input.createTextRange();
range.moveStart("character", charOffset);
pixelPosition = range.getBoundingClientRect();
}
else
{
var text = input.value.substr(0, charOffset).replace(/ $/, "\xa0");
var sizer = $("#sizer").insertBefore(input).text(text);
pixelPosition = sizer.offset();
pixelPosition.left += sizer.width();
if (!text) sizer.text("."); // for computing height. An empty span returns 0
pixelPosition.bottom = pixelPosition.top + sizer.height();
}
return pixelPosition
}
The css for my sizer span:
#sizer
{
position: absolute;
display: inline-block;
visibility: hidden;
margin: 3px; /* simulate padding and border without affecting height and width */
font-family: "segoe ui", Verdana, Arial, Sans-Serif;
font-size: 12px;
}
May 2014 update: The incredibly lightweight and robust textarea-caret-position Component library now supports <input type="text"> as well, rendering all other answers obsolete.
A demo is available at http://jsfiddle.net/dandv/aFPA7/
Thanks to Rob W for inspiration towards RTL support.
2016 update: A more modern HTML5 based solution would be to use the contenteditable property.
<div contenteditable="true"> <!-- behaves as input -->
Block of regular text, and <span id='interest'>text of interest</span>
</div>
We can now find the position of the span using jquery offset(). And of course, the <span> tags can be inserted upfront or dynamically.

Categories

Resources