contenteditable, placez le curseur à la fin du texte (cross-browser)

111

sortie dans 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>

Je crois qu'en FF, cela ressemblerait à ceci:

hey
<br />
what's up?

et dans IE :

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

Malheureusement, il n'y a pas de moyen agréable de faire en sorte que chaque navigateur insère un <br />tag au lieu d'un div ou d'un p, ou du moins je n'ai rien trouvé en ligne.


Quoi qu'il en soit, ce que j'essaie de faire maintenant, c'est que lorsque j'appuie sur le bouton , je veux que le curseur soit placé à la fin du texte, il devrait donc ressembler à ceci:

hey
what's up?|

un moyen de le faire pour qu'il fonctionne dans tous les navigateurs ?

exemple:

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

        ele.focus();

        //set caret -> end pos
     }
 }
Josh Crozier
la source
1
Cela a fonctionné mieux pour moi
Илья Зеленько

Réponses:

282

La fonction suivante le fera dans tous les principaux navigateurs:

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>

Placer le curseur au début est presque identique: il suffit de changer le booléen passé dans les appels à collapse(). Voici un exemple qui crée des fonctions pour placer le curseur au début et à la fin:

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);
Tim Down
la source
Ne fonctionne pas avec Chrome car createTextRange n'est pas une fonction standard. Voir stackoverflow.com/a/41743191/700206 .
Whitneyland
@Lee: Cela fonctionne bien dans Chrome, qui prend en charge window.getSelectionet document.createRange. La createTextRangebranche est destinée aux anciennes versions d'Internet Explorer.
Tim Down
au moment de la rédaction window.getSelectionn'est pas pris en charge par 0,29% de tous les navigateurs (IE> 8). voir: caniuse.com/#search=window.getSelection
w.stoettinger
@TimDown cela fonctionne. +1. Mais pouvez-vous également expliquer le code en ajoutant des commentaires s'il vous plaît? Serait génial
shinobi
Tout le monde sait comment définir le curseur à la fin (utilisez ce code) en essayant de cibler un élément iframe> body? L'élément corps a editablecontent="true"...? placeCaretAtEnd(this);ne fonctionnera pas pour moi.
RobbTe
6

Malheureusement, l'excellente réponse de Tim n'a fonctionné pour moi que pour le placement à la fin, pour le placement au départ, j'ai dû le modifier légèrement.

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

Je ne sais pas si focus()et select()sont réellement nécessaires.

dimide
la source
Vous avez également introduit une bibliothèque externe. Si vous ne pensez pas que la concentration et la sélection sont nécessaires, pourquoi ne pas commenter les lignes et les tester?
dotnethaggis
Merci, jquery supprimé. Je me souviens que j'avais des résultats contradictoires, je vais essayer de les isoler à nouveau.
dimid
1

Cet exemple (en direct) montre une courte fonction simple setCaretAtStartEnd, qui prend deux arguments; Un nœud (modifiable) pour placer le curseur & un booléen indiquant où le placer (début ou fin du nœud)

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
la source
-1

Si vous utilisez le compilateur de fermeture google, vous pouvez faire ce qui suit (un peu simplifié de la réponse de Tim):

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

Voici la même chose dans ClojureScript:

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

J'ai testé cela dans Chrome, Safari et FireFox, pas sûr d'IE ...

Matt WD
la source
1
range = goog.dom.Range.createFromNodeContents (el); qu'est-ce que goog.dom?
Anh Tú
Cela a fonctionné pour moi. @ AnhTú: goog.dom est un espace de noms fourni par la bibliothèque de fermeture.
David Beneš