web-dev-qa-db-fra.com

Comment définir la position du curseur (curseur) dans l'élément contenteditable (div)?

J'ai ce code HTML simple à titre d'exemple:

<div id="editable" contenteditable="true">
  text text text<br>
  text text text<br>
  text text text<br>
</div>
<button id="button">focus</button>

Je veux quelque chose de simple - quand je clique sur le bouton, je veux placer le curseur (curseur) à un emplacement spécifique dans le div modifiable. De recherche sur le Web, j'ai ce JS attaché à cliquer sur un bouton, mais cela ne fonctionne pas (FF, Chrome):

var range = document.createRange();
var myDiv = document.getElementById("editable");
range.setStart(myDiv, 5);
range.setEnd(myDiv, 5);

Est-il possible de régler manuellement la position du curseur comme ceci?

159
Frodik

Dans la plupart des navigateurs, vous avez besoin des objets Range et Selection . Vous spécifiez chacune des limites de sélection en tant que nœud et un décalage au sein de ce nœud. Par exemple, pour définir le curseur avec le cinquième caractère de la deuxième ligne de texte, procédez comme suit:

var el = document.getElementById("editable");
var range = document.createRange();
var sel = window.getSelection();
range.setStart(el.childNodes[2], 5);
range.collapse(true);
sel.removeAllRanges();
sel.addRange(range);

IE <9 fonctionne complètement différemment. Si vous devez prendre en charge ces navigateurs, vous aurez besoin d'un code différent.

exemple jsFiddle: http://jsfiddle.net/timdown/vXnCM/

222
Tim Down

La plupart des réponses que vous trouvez sur le positionnement du curseur de contenteditable sont assez simplistes en ce sens qu'elles ne traitent que les entrées avec du texte Vanilla simple. Une fois que vous utilisez des éléments html dans le conteneur, le texte saisi est divisé en nœuds et distribué librement dans une arborescence.

Pour définir la position du curseur, j'ai cette fonction qui boucle autour de tous les nœuds de texte enfant dans le nœud fourni et définit une plage allant du début du nœud initial au caractère chars.count:

function createRange(node, chars, range) {
    if (!range) {
        range = document.createRange()
        range.selectNode(node);
        range.setStart(node, 0);
    }

    if (chars.count === 0) {
        range.setEnd(node, chars.count);
    } else if (node && chars.count >0) {
        if (node.nodeType === Node.TEXT_NODE) {
            if (node.textContent.length < chars.count) {
                chars.count -= node.textContent.length;
            } else {
                range.setEnd(node, chars.count);
                chars.count = 0;
            }
        } else {
           for (var lp = 0; lp < node.childNodes.length; lp++) {
                range = createRange(node.childNodes[lp], chars, range);

                if (chars.count === 0) {
                    break;
                }
            }
        }
    } 

    return range;
};

J'appelle ensuite la routine avec cette fonction:

function setCurrentCursorPosition(chars) {
    if (chars >= 0) {
        var selection = window.getSelection();

        range = createRange(document.getElementById("test").parentNode, { count: chars });

        if (range) {
            range.collapse(false);
            selection.removeAllRanges();
            selection.addRange(range);
        }
    }
};

La plage.collapse (false) place le curseur à la fin de la plage. Je l'ai testé avec les dernières versions de Chrome, IE, Mozilla et Opera, et elles fonctionnent toutes bien.

PS Si quelqu'un est intéressé, j'obtiens la position actuelle du curseur en utilisant ce code:

function isChildOf(node, parentId) {
    while (node !== null) {
        if (node.id === parentId) {
            return true;
        }
        node = node.parentNode;
    }

    return false;
};

function getCurrentCursorPosition(parentId) {
    var selection = window.getSelection(),
        charCount = -1,
        node;

    if (selection.focusNode) {
        if (isChildOf(selection.focusNode, parentId)) {
            node = selection.focusNode; 
            charCount = selection.focusOffset;

            while (node) {
                if (node.id === parentId) {
                    break;
                }

                if (node.previousSibling) {
                    node = node.previousSibling;
                    charCount += node.textContent.length;
                } else {
                     node = node.parentNode;
                     if (node === null) {
                         break
                     }
                }
           }
      }
   }

    return charCount;
};

Le code agit à l'inverse de la fonction set. Il récupère les window.getSelection (). FocusNode et focusOffset actuels et compte en arrière tous les caractères de texte rencontrés jusqu'à ce qu'il atteigne un nœud parent avec l'id de containerId. La fonction isChildOf vérifie simplement avant d'exécuter que le nœud fourni est en réalité un enfant du fourni parentId.

Le code devrait fonctionner directement sans changement, mais je viens de le prendre à partir d'un plugin jQuery que j'ai développé, alors j'ai piraté quelques ceci - laissez-moi savoir si quelque chose ne fonctionne pas!

48
Liam

Il est très difficile de placer le curseur en position appropriée lorsque vous avez un élément d'avance comme (p) (span), etc. Le but est d'obtenir (texte d'objet):

<div id="editable" contenteditable="true">dddddddddddddddddddddddddddd<p>dd</p>psss<p>dd</p>
    <p>dd</p>
    <p>text text text</p>
</div>
<p id='we'></p>
<button onclick="set_mouse()">focus</button>
<script>

    function set_mouse() {
        var as = document.getElementById("editable");
        el = as.childNodes[1].childNodes[0];//goal is to get ('we') id to write (object Text) because it work only in object text
        var range = document.createRange();
        var sel = window.getSelection();
        range.setStart(el, 1);
        range.collapse(true);
        sel.removeAllRanges();
        sel.addRange(range);

        document.getElementById("we").innerHTML = el;// see out put of we id
    }
</script>
3
Jalaluddin Rumi
  const el = document.getElementById("editable");
  el.focus()
  let char = 1, sel; // character at which to place caret

  if (document.selection) {
    sel = document.selection.createRange();
    sel.moveStart('character', char);
    sel.select();
  }
  else {
    sel = window.getSelection();
    sel.collapse(el.lastChild, char);
  }
2
Sagar M

Si vous ne voulez pas utiliser jQuery, vous pouvez essayer cette approche:

public setCaretPosition() {
    const editableDiv = document.getElementById('contenteditablediv');
    const lastLine = this.input.nativeElement.innerHTML.replace(/.*?(<br>)/g, '');
    const selection = window.getSelection();
    selection.collapse(editableDiv.childNodes[editableDiv.childNodes.length - 1], lastLine.length);
}

editableDiv votre élément éditable, n'oubliez pas de définir un id pour cela. Ensuite, vous devez extraire votre innerHTML de l'élément et couper toutes les conduites de frein. Et juste définir effondrement avec les arguments suivants.

2
Volodymyr Khmil

J'écris un surligneur de syntaxe (et un éditeur de code de base) et j'avais besoin de savoir comment taper automatiquement un caractère guillemet simple et déplacer le curseur (comme beaucoup d'éditeurs de code de nos jours).

Voici un extrait de ma solution, grâce à l’aide apportée par ce fil, à la documentation MDN et à beaucoup de visionnage de la console moz.

//onKeyPress event

if (evt.key === "\"") {
    let sel = window.getSelection();
    let offset = sel.focusOffset;
    let focus = sel.focusNode;

    focus.textContent += "\""; //setting div's innerText directly creates new
    //nodes, which invalidate our selections, so we modify the focusNode directly

    let range = document.createRange();
    range.selectNode(focus);
    range.setStart(focus, offset);

    range.collapse(true);
    sel.removeAllRanges();
    sel.addRange(range);
}

//end onKeyPress event

C'est dans un élément div contenteditable

Je laisse ceci ici comme un remerciement, réalisant qu'il y a déjà une réponse acceptée.

1
Jonathan Crowder

Je pense que ce n'est pas simple de placer le curseur dans une position dans l'élément contenteditable. J'ai écrit mon propre code pour cela. Il contourne l'arborescence des noeuds en calculant le nombre de caractères restants et en insérant le caret dans l'élément requis. Je n'ai pas beaucoup testé ce code.

//Set offset in current contenteditable field (for start by default or for with forEnd=true)
function setCurSelectionOffset(offset, forEnd = false) {
    const sel = window.getSelection();
    if (sel.rangeCount !== 1 || !document.activeElement) return;

    const firstRange = sel.getRangeAt(0);

    if (offset > 0) {
        bypassChildNodes(document.activeElement, offset);
    }else{
        if (forEnd)
            firstRange.setEnd(document.activeElement, 0);
        else
            firstRange.setStart(document.activeElement, 0);
    }



    //Bypass in depth
    function bypassChildNodes(el, leftOffset) {
        const childNodes = el.childNodes;

        for (let i = 0; i < childNodes.length && leftOffset; i++) {
            const childNode = childNodes[i];

            if (childNode.nodeType === 3) {
                const curLen = childNode.textContent.length;

                if (curLen >= leftOffset) {
                    if (forEnd)
                        firstRange.setEnd(childNode, leftOffset);
                    else
                        firstRange.setStart(childNode, leftOffset);
                    return 0;
                }else{
                    leftOffset -= curLen;
                }
            }else
            if (childNode.nodeType === 1) {
                leftOffset = bypassChildNodes(childNode, leftOffset);
            }
        }

        return leftOffset;
    }
}

J'ai aussi écrit du code pour obtenir la position actuelle du curseur (n'a pas été testé):

//Get offset in current contenteditable field (start offset by default or end offset with calcEnd=true)
function getCurSelectionOffset(calcEnd = false) {
    const sel = window.getSelection();
    if (sel.rangeCount !== 1 || !document.activeElement) return 0;

    const firstRange     = sel.getRangeAt(0),
          startContainer = calcEnd ? firstRange.endContainer : firstRange.startContainer,
          startOffset    = calcEnd ? firstRange.endOffset    : firstRange.startOffset;
    let needStop = false;

    return bypassChildNodes(document.activeElement);



    //Bypass in depth
    function bypassChildNodes(el) {
        const childNodes = el.childNodes;
        let ans = 0;

        if (el === startContainer) {
            if (startContainer.nodeType === 3) {
                ans = startOffset;
            }else
            if (startContainer.nodeType === 1) {
                for (let i = 0; i < startOffset; i++) {
                    const childNode = childNodes[i];

                    ans += childNode.nodeType === 3 ? childNode.textContent.length :
                           childNode.nodeType === 1 ? childNode.innerText.length :
                           0;
                }
            }

            needStop = true;
        }else{
            for (let i = 0; i < childNodes.length && !needStop; i++) {
                const childNode = childNodes[i];
                ans += bypassChildNodes(childNode);
            }
        }

        return ans;
    }
}

Vous devez également connaître les valeurs range.startOffset et range.endOffset contiennent un décalage de caractère pour les nœuds de texte (nodeType === 3) et un décalage de nœud enfant pour les nœuds d'élément (nodeType === 1). range.startContainer et range.endContainer peuvent faire référence à n'importe quel nœud d'élément de n'importe quel niveau de l'arbre (bien sûr, ils peuvent également faire référence à des nœuds de texte).

0
vitaliydev