contenteditable, set caret at the end of the text (cross-browser)

64,555

Solution 1

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

Solution 2

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.

Solution 3

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>

Share:
64,555
Admin
Author by

Admin

Updated on December 04, 2020

Comments

  • Admin
    Admin over 3 years

    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
         }
     }
    
  • Anh Tú
    Anh Tú about 7 years
    range = goog.dom.Range.createFromNodeContents(el); what's goog.dom?
  • dotnethaggis
    dotnethaggis almost 7 years
    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?
  • dimid
    dimid almost 7 years
    Thanks, removed jquery. I remember I had some contradicting results, I'll try to isolate them again.
  • whitneyland
    whitneyland over 6 years
    Does not work with Chrome because createTextRange is not a standard function. See stackoverflow.com/a/41743191/700206.
  • Tim Down
    Tim Down over 6 years
    @Lee: It works fine in Chrome, which supports window.getSelection and document.createRange. The createTextRange branch is for old versions of Internet Explorer.
  • w.stoettinger
    w.stoettinger over 6 years
    at the time of writing window.getSelection is not supported by 0.29% of all browsers (IE>8). see: caniuse.com/#search=window.getSelection
  • shinobi
    shinobi about 6 years
    @TimDown this works. +1. But can you also explain the code by adding comments please? Would be great
  • RobbTe
    RobbTe almost 6 years
    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.
  • David Beneš
    David Beneš over 5 years
    This worked for me. @AnhTú: goog.dom is a namespace provided by closure-library.
  • vsync
    vsync over 5 years
    @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?
  • Alpesh Prajapati
    Alpesh Prajapati over 5 years
    @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)..
  • Tim Down
    Tim Down over 5 years
    @AlpeshPrajapati: Do you have a demo page I can look at?
  • Alpesh Prajapati
    Alpesh Prajapati over 5 years
    @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: mutuallyhuman.com/blog/2018/05/03/… ...Read the section "Asynchronous saves and data refreshes to the server"
  • webprogrammer
    webprogrammer over 4 years
    Does it work with nested hey <p>what's up?</p>? Where the caret is put: right after "?" mark or after closing "</p>" tag?
  • user1432181
    user1432181 over 3 years
    This seems to work ok : jsfiddle.net/Abeeee/7me8dkpb
  • Tim Down
    Tim Down over 3 years
    @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: jsfiddle.net/c0md8u5n
  • ashleedawg
    ashleedawg over 2 years
    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.selectNo‌​deContents(el);r.col‌​lapse(false);s=w.get‌​Selection();s.remove‌​AllRanges();s.addRan‌​ge(r)}else if(typeof document.body.createTextRange!=u){t=document.body.createText‌​Range();t.moveToElem‌​entText(el);t.collap‌​se(false);t.select()‌​}}. Works great for me, everywhere.
  • ashleedawg
    ashleedawg over 2 years
    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, so I'm not sure whether or not @TimDown wrote it and don't know who to credit... alas, I tried.
  • ashleedawg
    ashleedawg over 2 years
    focus() places the caret at the beginning for me with no additional code required
  • Tim Down
    Tim Down over 2 years
    @ashleedawg: I definitely wrote this code.