← Back toHTML DOM

Get or set the cursor position in a contentEditable element

Written byPhuoc Nguyen
Created
17 Sep, 2023
Category
Level 2 — Intermediate
When working with contenteditable elements on a web page, you might need to programmatically get or set the cursor position. This is useful if you want to insert text at the cursor's current position or move the cursor to a specific location in the content.
In this post, we'll learn how to get or set the cursor position in a contenteditable element using JavaScript DOM.

Getting cursor position

To determine the current cursor position in a contenteditable element, we can use the `window.getSelection()` method. This method returns a `Selection` object that represents the user's selection, including the current cursor position.
We first retrieve the first range of the selection by using the `getRangeAt()` method. A range is a continuous part of any document object model, and in our case, it represents the location of the text cursor.
Next, we clone this range using the `cloneRange()` method and select all contents within our contenteditable element using the `selectNodeContents()` method. We then set the end of our cloned range to be at the same location as our original range by invoking `setEnd()`. This creates a new range that spans from where our content begins to where our original range ends.
Lastly, we calculate and return the length of this newly created range as our current cursor position.
Here's a code example demonstrating how to get the current cursor position:
js
const selection = window.getSelection();
const range = selection.getRangeAt(0);
const clonedRange = range.cloneRange();
clonedRange.selectNodeContents(contentEle);
clonedRange.setEnd(range.endContainer, range.endOffset);

const cursorPosition = clonedRange.toString().length;

Setting cursor position

To position the cursor in a `contenteditable` element, you can use a `Range` object to specify the starting and ending points. Here's how it works:
First, we create a new `Range` object using `document.createRange()`. Then, we traverse the DOM tree iteratively using a while loop and a stack. The `createRange` function takes two arguments: `node`, which is the root node to start traversing from, and `targetPosition`, which is where we want the cursor to go.
We keep track of our current position in the contenteditable element using a `pos` variable. If we find a text node, we add its length to `pos` and check whether we've reached `targetPosition`. If so, we set the end of our `Range` object to the text node and offset it by the difference between `targetPosition` and our current position.
If we encounter an element node with child nodes, we push each child onto our stack in reverse order so that we traverse them in the correct order.
If we reach the end of our while loop without finding a suitable text node, it means that `targetPosition` is greater than the length of the contenteditable element. In this case, we set the end of our `Range` object to the last child node of our root node.
js
const createRange = (node, targetPosition) => {
let range = document.createRange();
range.selectNode(node);
range.setStart(node, 0);

let pos = 0;
const stack = [node];
while (stack.length > 0) {
const current = stack.pop();

if (current.nodeType === Node.TEXT_NODE) {
const len = current.textContent.length;
if (pos + len >= targetPosition) {
range.setEnd(current, targetPosition - pos);
return range;
}
pos += len;
} else if (current.childNodes && current.childNodes.length > 0) {
for (let i = current.childNodes.length - 1; i >= 0; i--) {
stack.push(current.childNodes[i]);
}
}
}

// The target position is greater than the
// length of the contenteditable element.
range.setEnd(node, node.childNodes.length);
return range;
};
To set the cursor position, we first create a new `Range` object and set its start to the desired location. Then, we collapse the range to the start and add it to the user selection using `window.getSelection().addRange()`. We also remove any existing ranges from the selection using `selection.removeAllRanges()` to ensure that the new range is the only one selected.
Here's an example code that shows how to set the cursor position:
js
const setPosition = (targetPosition) => {
const range = createRange(contentEle, targetPosition);
const selection = window.getSelection();
selection.removeAllRanges();
selection.addRange(range);
};
Feel free to experiment with the demo below. Click anywhere inside the editable element to see the cursor position. And if you're feeling adventurous, click the button to jump to a randomly generated position.

See also

If you found this post helpful, please consider giving the repository a star on GitHub or sharing the post on your favorite social networks 😍. Your support would mean a lot to me!

Questions? 🙋

Do you have any questions about front-end development? If so, feel free to create a new issue on GitHub using the button below. I'm happy to help with any topic you'd like to learn more about, even beyond what's covered in this post.
While I have a long list of upcoming topics, I'm always eager to prioritize your questions and ideas for future content. Let's learn and grow together! Sharing knowledge is the best way to elevate ourselves 🥷.
Ask me questions

Recent posts ⚡

Newsletter 🔔

If you're into front-end technologies and you want to see more of the content I'm creating, then you might want to consider subscribing to my newsletter.
By subscribing, you'll be the first to know about new articles, products, and exclusive promotions.
Don't worry, I won't spam you. And if you ever change your mind, you can unsubscribe at any time.
Phước Nguyễn