How to Set the Caret (Cursor) Position in a Contenteditable Element (Div)

How to set the caret (cursor) position in a contenteditable element (div)?

In most browsers, you need the Range and Selection objects. You specify each of the selection boundaries as a node and an offset within that node. For example, to set the caret to the fifth character of the second line of text, you'd do the following:

function setCaret() {
var el = document.getElementById("editable")
var range = document.createRange()
var sel = window.getSelection()

range.setStart(el.childNodes[2], 5)
range.collapse(true)

sel.removeAllRanges()
sel.addRange(range)
}
<div id="editable" contenteditable="true">
text text text<br>text text text<br>text text text<br>
</div>

<button id="button" onclick="setCaret()">focus</button>

Set caret position in a content editable element

Try this:

Just replace range.setStart(el, 2) with range.setStart(el.childNodes[0], 2)

var el = document.getElementsByTagName('div')[0];

var range = document.createRange();

var sel = window.getSelection();

range.setStart(el.childNodes[0], 2);

range.collapse(true);

sel.removeAllRanges();

sel.addRange(range);

el.focus();
<div contenteditable>Hi ! How are you doing ?</div>

Set Caret Position in 'contenteditable' div that has children

So, I was experiencing the same issue and decided to write my own routine quickly, it walks through all the child nodes recursively and set the position.
Note how this takes a DOM node as argument, not a jquery object as your original post does

// Move caret to a specific point in a DOM element
function SetCaretPosition(el, pos){

// Loop through all child nodes
for(var node of el.childNodes){
if(node.nodeType == 3){ // we have a text node
if(node.length >= pos){
// finally add our range
var range = document.createRange(),
sel = window.getSelection();
range.setStart(node,pos);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);
return -1; // we are done
}else{
pos -= node.length;
}
}else{
pos = SetCaretPosition(node,pos);
if(pos == -1){
return -1; // no need to finish the for loop
}
}
}
return pos; // needed because of recursion stuff
}

I hope this'll help you!

Set the selector cursor to a new position in contenteditable div

<div> elements have no value property

$("#buttonbold").click(function(){
content.focus();
var valuelength = content.textContent.length - 4;
content.setSelectionRange(content.textContent.length,valuelength);
});

.


<div> elements do not have a setSelectionRange method either,
so here is a solution to make this kind of selection for elements using the contenteditable property:

const myDiv         = document.getElementById('my-div')

, btSelectRange = document.getElementById('bt-select-range')

;

function setSelectionRangeCE(el, pos, len)

{

el.focus();

let range = document.createRange()

, sel = window.getSelection()

;

range.setStart(el.firstChild, pos)

range.setEnd(el.firstChild, pos+len)

sel.removeAllRanges()

sel.addRange(range)

}

btSelectRange.onclick=_=>

{

setSelectionRangeCE(myDiv,2,5)

}
#my-div {

margin: 1em;

padding: .7em;

width: 16em;

height: 3em;

border: 1px solid grey;

}

button {

margin: 1em;

}
<div id="my-div" contenteditable >hello world</div>

<button id="bt-select-range" > select range pos:2 len:5 </button>

How to set caret position of a contenteditable div containing combination of text and element nodes

Your textNode has 3 children (1 text, 1 element, 1 text) and therefore you can't just use firstChild.

You need to iterate over the childNodes of the <div> and track the character count where the nodeType of the childNode equals Node.TEXT_NODE (see here on MDN). Where the character count is less than the value of caret you can deduct that from caret and move onto the next text node.

Per your condition that:

I desire and each image would be treated as 1 character

The code will deduct 1 from caret where nodeType == 1 i.e. Node.ELEMENT_NODE

Here is a code example with multiple icons:

var node = document.querySelector("div");

node.focus();

var caret = 24;

var child;

var childNodeIndex = 0;

for(var i=0; i<node.childNodes.length; i++) {

child = node.childNodes[i];

// Node.ELEMENT_NODE == 1

// Node.TEXT_NODE == 3

if(child.nodeType == Node.TEXT_NODE) {

// keep track of caret across text childNodes

if(child.length <= caret) {

caret -= child.length;

} else {

break;

}

} else if (child.nodeType == Node.ELEMENT_NODE) {

// condition that 'each image would be treated as 1 character'

if(caret > 0) {

caret -= 1;

} else {

break;

}

};

childNodeIndex += 1;

}

var textNode = node.childNodes[childNodeIndex];

// your original code continues here...

var range = document.createRange();

range.setStart(textNode, caret);

range.setEnd(textNode, caret);

var sel = window.getSelection();

sel.removeAllRanges();

sel.addRange(range);
<div id="text" contenteditable="true">a<img src="https://www.splitbrain.org/_static/ico/circular/ico/add.png"/><img src="https://www.splitbrain.org/_static/ico/circular/ico/add.png"/><img src="https://www.splitbrain.org/_static/ico/circular/ico/add.png"/><img src="https://www.splitbrain.org/_static/ico/circular/ico/add.png"/>b<img src="https://www.splitbrain.org/_static/ico/circular/ico/add.png"/>cdefghijkl<img src="https://www.splitbrain.org/_static/ico/circular/ico/add.png"/>mnopq<img src="https://www.splitbrain.org/_static/ico/circular/ico/add.png"/>rst<img src="https://www.splitbrain.org/_static/ico/circular/ico/add.png"/>uvw<img src="https://www.splitbrain.org/_static/ico/circular/ico/add.png"/>xyz<img src="https://www.splitbrain.org/_static/ico/circular/ico/add.png"/></div>

Get and set cursor position with contenteditable div

A good rich-text editor is one of the harder things to do currently, and is pretty much a project by itself (unfriendly API, huge number of corner cases, cross-browser differences, the list goes on). I would strongly advise you to try and find an existing solution.

Some libraries that can be used include:

  • Quill (http://quilljs.com)
  • WYSGIHTML (http://wysihtml.com)
  • CodeMirror library (http://codemirror.net)

Javascript to move caret to a search string in a contenteditable

Ok, I think I've finally done it.

function doFindStringAndMoveCaret(editor, findTx) {
treeObject = {}
var element = editor;
editor.focus();

function doTreeWalk(element, object) {
var nodeList = element.childNodes;
if (nodeList != null) {
if (nodeList.length) {
object[element.nodeName] = [];

for (var i = 0; i < nodeList.length; i++) {

if (nodeList[i].nodeType == 3) {
var p=nodeList[i].nodeValue.indexOf(findTx);
if ( p!=-1 ) {
var sel=window.getSelection();
var range=sel.getRangeAt(0);
range.setStart(nodeList[i], p);
range.setEnd(nodeList[i], p+findTx.length);
return;
}
}
else {
object[element.nodeName].push({});
doTreeWalk(nodeList[i]
, object[element.nodeName][object[element.nodeName].length - 1]);
}
}
}
}
}

doTreeWalk(element, treeObject);
return;
}
doFindStringAndMoveCaret(document.getElementById('mydiv'), 'peekabo');

A working example may be found on https://jsfiddle.net/Abeeee/krLeop49/6/

Thanks anyways

Regards
Abe

HTML contenteditable: Keep Caret Position When Inner HTML Changes

You need to get position of the cursor first then process and set the content. Then restore the cursor position.

Restoring cursor position is a tricky part when there are nested elements. Also you are creating new <strong> and <em> elements every time, old ones are being discarded.

const editor = document.querySelector('.editor');
editor.innerHTML = parse('For **bold** two stars. For *italic* one star. Some more **bold**.');

editor.addEventListener('input', () => {
//get current cursor position
let sel = window.getSelection();
let node = sel.focusNode;
let offset = sel.focusOffset;
let pos = getCursorPosition(editor, node, offset, {pos: 0, done: false});
//console.log('Position: ', pos);

editor.innerHTML = parse(editor.innerText);

// restore the position
sel.removeAllRanges();
let range = setCursorPosition(editor, document.createRange(), { pos: pos.pos, done: false});
range.collapse(true);
sel.addRange(range);

});

function parse(text) {
//use (.*?) lazy quantifiers to match content inside
return text
.replace(/\*{2}(.*?)\*{2}/gm, '**<strong>$1</strong>**') // bold
.replace(/(?<!\*)\*(?!\*)(.*?)(?<!\*)\*(?!\*)/gm, '*<em>$1</em>*'); // italic
}

// get the cursor position from .editor start
function getCursorPosition(parent, node, offset, stat) {
if (stat.done) return stat;

let currentNode = null;
if (parent.childNodes.length == 0) {
stat.pos += parent.textContent.length;
} else {
for (var i = 0; i < parent.childNodes.length && !stat.done; i++) {
currentNode = parent.childNodes[i];
if (currentNode === node) {
stat.pos += offset;
stat.done = true;
return stat;
} else
getCursorPosition(currentNode, node, offset, stat);
}
}
return stat;
}

//find the child node and relative position and set it on range
function 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++) {
currentNode = parent.childNodes[i];
setCursorPosition(currentNode, range, stat);
}
}
return range;
}
.editor {
height: 100px;
width: 300px;
border: 1px solid #888;
padding: 0.5rem;
}

em,strong{
font-size: 1.3rem;
}
<div class='editor' contenteditable />


Related Topics



Leave a reply



Submit