104 votes

Comment déplacer le curseur à la fin d'une entité modifiable du contenu

Je dois déplacer le curseur à la fin du nœud contenteditable comme sur le widget de notes de Gmail.

J'ai lu des fils de discussion sur StackOverflow, mais ces solutions sont basées sur l'utilisation d'entrées et elles ne fonctionnent pas avec les éléments contenteditable.

272voto

Nico Burns Points 6012

La solution de Geowa4 fonctionnera pour une zone de texte, mais pas pour un élément contenteditable.

Cette solution permet de déplacer le curseur à la fin d'un élément contenteditable. Elle devrait fonctionner dans tous les navigateurs qui prennent en charge contenteditable.

function setEndOfContenteditable(contentEditableElement)
{
    var range,selection;
    if(document.createRange)//Firefox, Chrome, Opera, Safari, IE 9+
    {
        range = document.createRange();//Créer une plage (une plage est comme la sélection mais invisible)
        range.selectNodeContents(contentEditableElement);//Sélectionner tout le contenu de l'élément avec la plage
        range.collapse(false);//réduire la plage au point final. false signifie se réduire à la fin plutôt qu'au début
        selection = window.getSelection();//obtenir l'objet de sélection (permet de modifier la sélection)
        selection.removeAllRanges();//supprimer toutes les plages déjà créées
        selection.addRange(range);//faire de la plage que vous venez de créer la sélection visible
    }
    else if(document.selection)//IE 8 et inférieur
    { 
        range = document.body.createTextRange();//Créer une plage (une plage est comme la sélection mais invisible)
        range.moveToElementText(contentEditableElement);//Sélectionner tout le contenu de l'élément avec la plage
        range.collapse(false);//réduire la plage au point final. false signifie se réduire à la fin plutôt qu'au début
        range.select();//Sélectionner la plage (la rendre visible)
    }
}

Il peut être utilisé par un code similaire à :

elem = document.getElementById('txt1');//Ceci est l'élément vers lequel vous voulez déplacer le curseur à la fin
setEndOfContenteditable(elem);

1 votes

La solution de geowa4 fonctionnera pour les textarea dans Chrome, mais elle ne fonctionnera pas pour les éléments contenteditable dans n'importe quel navigateur. La mienne fonctionne pour les éléments contenteditable, mais pas pour les textareas.

4 votes

Ceci est la réponse correcte à cette question, parfait, merci Nico.

0 votes

Vous pouvez alors déplacer le curseur avec sélection.modify('move', 'left', 'character'). Fonctionne dans Chrome pour moi, voir Selection#modify et Selection docs.

48voto

Juank Points 1066

Si vous ne vous souciez pas des anciens navigateurs, celui-ci a fonctionné pour moi.

// [optionnel] assurez-vous que le focus est sur l'élément
yourContentEditableElement.focus();
// sélectionnez tout le contenu dans l'élément
document.execCommand('selectAll', false, null);
// réduisez la sélection jusqu'à la fin
document.getSelection().collapseToEnd();

29voto

VitoShadow Points 2096

Il y a aussi un autre problème.

La solution de Nico Burns fonctionne si la div contenteditable ne contient pas d'autres éléments multilignes.

Par exemple, si une div contient d'autres divs, et que ces autres divs contiennent d'autres éléments à l'intérieur, certains problèmes pourraient survenir.

Pour les résoudre, j'ai élaboré la solution suivante, qui est une amélioration de celle de Nico:

//Idée de gestion de l'espace de noms de http://enterprisejquery.com/2010/10/how-good-c-habits-can-encourage-bad-javascript-habits-part-1/
(function( cursorManager ) {

    //De : http://www.w3.org/TR/html-markup/syntax.html#syntax-elements
    var voidNodeTags = ['AREA', 'BASE', 'BR', 'COL', 'EMBED', 'HR', 'IMG', 'INPUT', 'KEYGEN', 'LINK', 'MENUITEM', 'META', 'PARAM', 'SOURCE', 'TRACK', 'WBR', 'BASEFONT', 'BGSOUND', 'FRAME', 'ISINDEX'];

    //De : https://stackoverflow.com/questions/237104/array-containsobj-in-javascript
    Array.prototype.contains = function(obj) {
        var i = this.length;
        while (i--) {
            if (this[i] === obj) {
                return true;
            }
        }
        return false;
    }

    //Idée de base de : https://stackoverflow.com/questions/19790442/test-if-an-element-can-contain-text
    function canContainText(node) {
        if(node.nodeType == 1) { //est un nœud élément
            return !voidNodeTags.contains(node.nodeName);
        } else { //n'est pas un nœud élément
            return false;
        }
    };

    function getLastChildElement(el){
        var lc = el.lastChild;
        while(lc && lc.nodeType != 1) {
            if(lc.previousSibling)
                lc = lc.previousSibling;
            else
                break;
        }
        return lc;
    }

    //Basé sur la réponse de Nico Burns
    cursorManager.setEndOfContenteditable = function(contentEditableElement)
    {

        while(getLastChildElement(contentEditableElement) &&
              canContainText(getLastChildElement(contentEditableElement))) {
            contentEditableElement = getLastChildElement(contentEditableElement);
        }

        var range,selection;
        if(document.createRange)//Firefox, Chrome, Opera, Safari, IE 9+
        {    
            range = document.createRange();//Créer une plage (une plage est comme la sélection mais invisible)
            range.selectNodeContents(contentEditableElement);//Sélectionner tout le contenu de l'élément avec la plage
            range.collapse(false);//réduire la plage au point de fin. false signifie se réduire à la fin plutôt qu'au début
            selection = window.getSelection();//obtenir l'objet de sélection (vous permet de modifier la sélection)
            selection.removeAllRanges();//supprimer les sélections déjà effectuées
            selection.addRange(range);//faire de la plage que vous venez de créer la sélection visible
        }
        else if(document.selection)//IE 8 et inférieur
        { 
            range = document.body.createTextRange();//Créer une plage (une plage est comme la sélection mais invisible)
            range.moveToElementText(contentEditableElement);//Sélectionner tout le contenu de l'élément avec la plage
            range.collapse(false);//réduire la plage au point de fin. false signifie se réduire à la fin plutôt qu'au début
            range.select();//Sélectionnez la plage (rendez-la visible la sélection
        }
    }

}( window.cursorManager = window.cursorManager || {}));

Utilisation:

var editableDiv = document.getElementById("my_contentEditableDiv");
cursorManager.setEndOfContenteditable(editableDiv);

Ainsi, le curseur est sûrement positionné à la fin du dernier élément, éventuellement imbriqué.

MODIFICATION #1: Afin d'être plus générique, l'instruction while devrait également considérer toutes les autres balises qui ne peuvent pas contenir de texte. Ces éléments sont appelés éléments vides, et dans cette question il y a quelques méthodes sur la façon de tester si un élément est vide. Donc, en supposant qu'il existe une fonction appelée canContainText qui renvoie true si l'argument n'est pas un élément vide, la ligne de code suivante:

contentEditableElement.lastChild.tagName.toLowerCase() != 'br'

doit être remplacée par:

canContainText(getLastChildElement(contentEditableElement))

MODIFICATION #2: Le code ci-dessus est entièrement mis à jour, avec tous les changements décrits et discutés

0 votes

Intéressant, j'aurais pensé que le navigateur s'occuperait automatiquement de ce cas (non que je sois surpris que ce ne soit pas le cas, les navigateurs ne semblent jamais faire les choses intuitives avec contenteditable). Avez-vous un exemple de HTML où votre solution fonctionne mais la mienne ne fonctionne pas ?

0 votes

Dans mon code, il y avait une autre erreur. Je l'ai corrigée. Maintenant, vous pouvez vérifier que mon code fonctionne sur cette page, tandis que le vôtre ne fonctionne pas.

0 votes

Je reçois une erreur en utilisant votre fonction, la console indique Uncaught TypeError: Cannot read property 'nodeType' of null et cela provient de l'appel de la fonction getLastChildElement. Savez-vous ce qui pourrait causer ce problème?

24voto

Friedrich Points 1036

Une version plus courte et lisible utilisant uniquement sélection (sans plage):

function setEndOfContenteditable(elem) {
    let sel = window.getSelection();
    sel.selectAllChildren(elem);
    sel.collapseToEnd();
}

Un paragraphe texte span en italique un paragraphe.

Très utile: https://javascript.info/selection-range

19voto

Andrew Points 359

Il est possible de placer le curseur à la fin à travers la plage :

setCaretToEnd(cible/*: HTMLDivElement*/) {
  const plage = document.createRange();
  const sel = window.getSelection();
  plage.selectNodeContents(cible);
  plage.collapse(false);
  sel.removeAllRanges();
  sel.addRange(plage);
  cible.focus();
  plage.detach(); // optimisation

  // régler le défilement jusqu'à la fin s'il y a plusieurs lignes
  cible.scrollTop = cible.scrollHeight; 
}

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X