131

output in Chrome:

<div id="content" contenteditable="true" style="border:1px solid #000;width:500px;height:40px;">
    hey
    <div>what's up?</div>
<div>
<button id="insert_caret"></button>

I believe in FF it would look something like this:

hey
<br />
what's up?

and in IE:

hey
<p>what's up?</p>

unfortunately, there is no nice way of making it so that every browser inserts a <br /> instead of a div- or p-tag, or at least I couldn't find anything online.


ANYWAY, what I am trying to do now is, when I hit the button, I want the caret to be set at the end of the text, so it should look something like this:

hey
what's up?|

any way to do this so it works in all browser?

example:

$(document).ready(function()
{
    $('#insert_caret').click(function()
    {
        var ele = $('#content');
        var length = ele.html().length;

        ele.focus();

        //set caret -> end pos
     }
 }
Josh Crozier
  • 233,099
  • 56
  • 391
  • 304

4 Answers4

318

The following function will do it in all major browsers:

function placeCaretAtEnd(el) {
    el.focus();
    if (typeof window.getSelection != "undefined"
            && typeof document.createRange != "undefined") {
        var range = document.createRange();
        range.selectNodeContents(el);
        range.collapse(false);
        var sel = window.getSelection();
        sel.removeAllRanges();
        sel.addRange(range);
    } else if (typeof document.body.createTextRange != "undefined") {
        var textRange = document.body.createTextRange();
        textRange.moveToElementText(el);
        textRange.collapse(false);
        textRange.select();
    }
}

placeCaretAtEnd( document.querySelector('p') );
p{ padding:.5em; border:1px solid black; }
<p contentEditable>foo bar </p>

Placing the caret at the start is almost identical: it just requires changing the Boolean passed into the calls to collapse(). Here's an example that creates functions for placing the caret at the start and at the end:

function createCaretPlacer(atStart) {
    return function(el) {
        el.focus();
        if (typeof window.getSelection != "undefined"
                && typeof document.createRange != "undefined") {
            var range = document.createRange();
            range.selectNodeContents(el);
            range.collapse(atStart);
            var sel = window.getSelection();
            sel.removeAllRanges();
            sel.addRange(range);
        } else if (typeof document.body.createTextRange != "undefined") {
            var textRange = document.body.createTextRange();
            textRange.moveToElementText(el);
            textRange.collapse(atStart);
            textRange.select();
        }
    };
}

var placeCaretAtStart = createCaretPlacer(true);
var placeCaretAtEnd = createCaretPlacer(false);
vsync
  • 118,978
  • 58
  • 307
  • 400
Tim Down
  • 318,141
  • 75
  • 454
  • 536
  • Does not work with Chrome because createTextRange is not a standard function. See https://stackoverflow.com/a/41743191/700206. – whitneyland Oct 09 '17 at 17:01
  • @Lee: It works fine in Chrome, which supports `window.getSelection` and `document.createRange`. The `createTextRange` branch is for old versions of Internet Explorer. – Tim Down Oct 10 '17 at 09:43
  • at the time of writing `window.getSelection` is not supported by 0.29% of all browsers (IE>8). see: http://caniuse.com/#search=window.getSelection – w.stoettinger Oct 17 '17 at 20:24
  • @TimDown this works. +1. But can you also explain the code by adding comments please? Would be great – shinobi Jun 23 '18 at 21:19
  • Anyone knows how to set the caret to the end (use this code) when trying to target an iframe > body element? The body element has `editablecontent="true"`...? `placeCaretAtEnd(this);` won't work for me. – RobbTe Jul 18 '18 at 14:22
  • @TimDown - Took the liberty adding a demo. I face a difficult problem where such a solution doesn't work when there's a trailing space. I saw you faced a similar problem, some years back in one of the github repos. any **insight** on the problem? – vsync Nov 15 '18 at 23:14
  • @TimDown This solution is working chrome, but in my case it does not work in firefox. Can you please help ?..Issue in firefox is like cursor is pointing to last character but when i type a single character, cursor jumps on to first character and writing after will add characters from right to left(Islamic language).. – Alpesh Prajapati Mar 25 '19 at 14:34
  • @AlpeshPrajapati: Do you have a demo page I can look at? – Tim Down Mar 25 '19 at 16:55
  • @TimDown Thank you very much for your response. But now I think I got the issue why it was like that. It was data-binding issue and updating at the same time. Thanks to the blog: https://www.mutuallyhuman.com/blog/2018/05/03/the-curious-case-of-cursor-jumping/ ...Read the section "Asynchronous saves and data refreshes to the server" – Alpesh Prajapati Mar 26 '19 at 08:27
  • 1
    Does it work with nested `hey

    what's up?

    `? Where the caret is put: right after "?" mark or after closing "" tag?
    – webprogrammer Mar 11 '20 at 19:38
  • 1
    @user1432181: You've got a typo in "contenteditable" and you can't call focus() on an element within the editable content. Here's a working example: https://jsfiddle.net/c0md8u5n/ – Tim Down Nov 26 '20 at 17:35
  • Here's the exact same function, compressed: `function placeCaretAtEnd(el){el.focus();var u="undefined",w=window,r,s,t;if(typeof w.getSelection!=u&&typeof document.createRange!=u){r=document.createRange();r.selectNodeContents(el);r.collapse(false);s=w.getSelection();s.removeAllRanges();s.addRange(r)}else if(typeof document.body.createTextRange!=u){t=document.body.createTextRange();t.moveToElementText(el);t.collapse(false);t.select()}}`. Works great for me, everywhere. – ashleedawg Mar 12 '22 at 03:32
  • I was about to correct another question that used this code, to cite this answer as the source, but then realized [it's been quoted several times](https://www.google.com/search?q=site:stackoverflow.com+%22placeCaretAtEnd%22&tbs=li:1), so I'm not sure whether or not @TimDown wrote it and don't know who to credit... alas, I tried. – ashleedawg Mar 12 '22 at 03:38
  • @ashleedawg: I definitely wrote this code. – Tim Down Mar 22 '22 at 13:11
10

Unfortunately Tim's excellent answer worked for me only for placing at the end, for placing at the start I had to modify it slightly.

function setCaret(target, isStart) {
  const range = document.createRange();
  const sel = window.getSelection();
  if (isStart){
    const newText = document.createTextNode('');
    target.appendChild(newText);
    range.setStart(target.childNodes[0], 0);
  }
  else {
    range.selectNodeContents(target);
  }
  range.collapse(isStart);
  sel.removeAllRanges();
  sel.addRange(range);
  target.focus();
  target.select();
}

Not sure though if focus() and select() are actually needed.

dimid
  • 7,285
  • 1
  • 46
  • 85
  • 1
    You also introduced an external library. If you don't think focus and select is required why not comment out the lines and test it? – dotnethaggis Jul 19 '17 at 10:41
  • Thanks, removed jquery. I remember I had some contradicting results, I'll try to isolate them again. – dimid Jul 20 '17 at 04:02
  • `focus()` places the caret at the beginning for me with no additional code required – ashleedawg Mar 12 '22 at 03:39
6

This (live) example shows a short simple function, setCaretAtStartEnd, which takes two arguments; A (editable) node to place the caret at & a Boolean indicating where to place it (start or end of the node)

const editableElm = document.querySelector('[contenteditable]');

document.querySelectorAll('button').forEach((elm, idx) => 
  elm.addEventListener('click', () => {
    editableElm.focus()
    setCaretAtStartEnd(editableElm, idx) 
  })
)

function setCaretAtStartEnd( node, atEnd ){
  const sel = document.getSelection();
  node = node.firstChild;

  if( sel.rangeCount ){
      ['Start', 'End'].forEach(pos =>
        sel.getRangeAt(0)["set" + pos](node, atEnd ? node.length : 0)
      )
  }
}
[contenteditable]{ padding:5px; border:1px solid; }
<h1 contenteditable>Place the caret anywhere</h1>
<br>
<button>Move caret to start</button>
<button>Move caret to end</button>
vsync
  • 118,978
  • 58
  • 307
  • 400
-1

If you are using the google closure compiler, you can do the following (somewhat simplified from Tim's answer):

function placeCaretAtEnd(el) {
    el.focus();
    range = goog.dom.Range.createFromNodeContents(el);
    range.collapse(false);
    range.select();
}

Here's the same thing in ClojureScript:

(defn place-caret-at-end [el] 
   (.focus el)
   (doto (.createFromNodeContents goog.dom.Range el)
         (.collapse false)
         .select))

I have tested this in Chrome, Safari and FireFox, not sure about IE...

Matt W-D
  • 1,605
  • 2
  • 19
  • 22