192 votes

Obtenir la position du caret contentEditable

Je trouve des tonnes de bonnes réponses, multi-navigateur, sur comment set la position du caret dans un contentEditable mais aucune sur la façon de obtenir la position du caret en premier lieu.

Ce que je veux faire, c'est connaître la position du caret dans un div sur keyup . Ainsi, lorsque l'utilisateur tape du texte, je peux, à tout moment, connaître la position du signe d'insertion dans l'image. contentEditable élément.

<div id="contentBox" contentEditable="true"></div>

$('#contentbox').keyup(function() { 
    // ... ? 
});

0 votes

Regardez sa position dans le texte. Ensuite, recherchez la dernière occurrence de '@' avant cette position. Donc, juste un peu de logique de texte.

0 votes

De plus, je ne prévois pas d'autoriser d'autres balises à l'intérieur du <diV>, seulement du texte.

0 votes

Ok, oui je Je suis going to need other tags within the <div>. Il y aura des balises <a>, mais il n'y aura pas d'imbrication...

171voto

Tim Down Points 124501

Le code suivant suppose :

  • Il y a toujours un seul nœud de texte dans la zone éditable. <div> et aucun autre noeud
  • Le div éditable n'a pas le CSS white-space la propriété est réglée sur pre

Si vous avez besoin d'une approche plus générale qui fonctionne avec des éléments imbriqués, essayez cette réponse :

https://stackoverflow.com/a/4812022/96100

Code :

function getCaretPosition(editableDiv) {
  var caretPos = 0,
    sel, range;
  if (window.getSelection) {
    sel = window.getSelection();
    if (sel.rangeCount) {
      range = sel.getRangeAt(0);
      if (range.commonAncestorContainer.parentNode == editableDiv) {
        caretPos = range.endOffset;
      }
    }
  } else if (document.selection && document.selection.createRange) {
    range = document.selection.createRange();
    if (range.parentElement() == editableDiv) {
      var tempEl = document.createElement("span");
      editableDiv.insertBefore(tempEl, editableDiv.firstChild);
      var tempRange = range.duplicate();
      tempRange.moveToElementText(tempEl);
      tempRange.setEndPoint("EndToEnd", range);
      caretPos = tempRange.text.length;
    }
  }
  return caretPos;
}

#caretposition {
  font-weight: bold;
}

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div id="contentbox" contenteditable="true">Click me and move cursor with keys or mouse</div>
<div id="caretposition">0</div>
<script>
  var update = function() {
    $('#caretposition').html(getCaretPosition(this));
  };
  $('#contentbox').on("mousedown mouseup keydown keyup", update);
</script>

0 votes

Désolé, j'ai dû annuler la réponse : I Je suis va avoir besoin d'autres étiquettes. Il y aura des balises <a> à l'intérieur de la <div>, mais pas d'imbrication. Je vais tester votre solution, peut-être même que cela fonctionne pour ce dont j'ai besoin...

0 votes

Oh, et je sais que j'ai d'abord dit qu'il n'y aurait pas d'autres tags là-dedans. Désolé, je me suis trompé.

13 votes

Cela ne fonctionnera pas s'il y a d'autres balises à l'intérieur. Question : si le signe d'insertion se trouve à l'intérieur d'une balise <a> à l'intérieur de l'élément <div> Quel décalage voulez-vous alors ? Le décalage dans le texte à l'intérieur de l'élément <a> ?

50voto

mwag Points 1

Quelques rides que je ne vois pas être abordées dans d'autres réponses :

  1. l'élément peut contenir plusieurs niveaux de nœuds enfants (par exemple, des nœuds enfants qui ont des nœuds enfants qui ont des nœuds enfants...).
  2. une sélection peut comporter différentes positions de début et de fin (par exemple, plusieurs caractères sont sélectionnés).
  3. le nœud contenant un Caret de début/fin ne peut être ni l'élément ni ses enfants directs.

Voici un moyen d'obtenir les positions de début et de fin sous forme de décalage par rapport à la valeur textContent de l'élément :

// node_walk: walk the element tree, stop when func(node) returns false
function node_walk(node, func) {
  var result = func(node);
  for(node = node.firstChild; result !== false && node; node = node.nextSibling)
    result = node_walk(node, func);
  return result;
};

// getCaretPosition: return [start, end] as offsets to elem.textContent that
//   correspond to the selected portion of text
//   (if start == end, caret is at given position and no text is selected)
function getCaretPosition(elem) {
  var sel = window.getSelection();
  var cum_length = [0, 0];

  if(sel.anchorNode == elem)
    cum_length = [sel.anchorOffset, sel.extentOffset];
  else {
    var nodes_to_find = [sel.anchorNode, sel.extentNode];
    if(!elem.contains(sel.anchorNode) || !elem.contains(sel.extentNode))
      return undefined;
    else {
      var found = [0,0];
      var i;
      node_walk(elem, function(node) {
        for(i = 0; i < 2; i++) {
          if(node == nodes_to_find[i]) {
            found[i] = true;
            if(found[i == 0 ? 1 : 0])
              return false; // all done
          }
        }

        if(node.textContent && !node.firstChild) {
          for(i = 0; i < 2; i++) {
            if(!found[i])
              cum_length[i] += node.textContent.length;
          }
        }
      });
      cum_length[0] += sel.anchorOffset;
      cum_length[1] += sel.extentOffset;
    }
  }
  if(cum_length[0] <= cum_length[1])
    return cum_length;
  return [cum_length[1], cum_length[0]];
}

31voto

Soubriquet Points 677

C'est un peu tard pour le faire, mais au cas où quelqu'un d'autre aurait des difficultés. Aucune des recherches Google que j'ai effectuées au cours des deux derniers jours n'a permis de trouver quelque chose qui fonctionne, mais j'ai trouvé une solution concise et élégante qui fonctionnera toujours, quel que soit le nombre de balises imbriquées :

function cursor_position() {
    var sel = document.getSelection();
    sel.modify("extend", "backward", "paragraphboundary");
    var pos = sel.toString().length;
    if(sel.anchorNode != undefined) sel.collapseToEnd();

    return pos;
}

// Demo:
var elm = document.querySelector('[contenteditable]');
elm.addEventListener('click', printCaretPosition)
elm.addEventListener('keydown', printCaretPosition)

function printCaretPosition(){
  console.log( cursor_position(), 'length:', this.textContent.trim().length )
}

<div contenteditable>some text here <i>italic text here</i> some other text here <b>bold text here</b> end of text</div>

Elle sélectionne jusqu'au début du paragraphe, puis compte la longueur de la chaîne pour obtenir la position actuelle et annule la sélection pour ramener le curseur à la position actuelle. Si vous souhaitez effectuer cette opération pour l'ensemble d'un document (plus d'un paragraphe), modifiez le paramètre paragraphboundary a documentboundary ou toute autre granularité adaptée à votre cas. Consultez l'API pour plus de détails . A la vôtre ! :)

26voto

Eisa Qasemi Points 470
$("#editable").on('keydown keyup mousedown mouseup',function(e){

       if($(window.getSelection().anchorNode).is($(this))){
          $('#position').html('0')
       }else{
         $('#position').html(window.getSelection().anchorOffset);
       }
 });

body{
  padding:40px;
}
#editable{
  height:50px;
  width:400px;
  border:1px solid #000;
}
#editable p{
  margin:0;
  padding:0;
}

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.0.1/jquery.min.js"></script>
<div contenteditable="true" id="editable">move the cursor to see position</div>
<div>
position : <span id="position"></span>
</div>

22voto

Jonathan R. Points 131

window.getSelection - vs - document.selection

Celui-là marche pour moi :

function getCaretCharOffset(element) {
  var caretOffset = 0;

  if (window.getSelection) {
    var range = window.getSelection().getRangeAt(0);
    var preCaretRange = range.cloneRange();
    preCaretRange.selectNodeContents(element);
    preCaretRange.setEnd(range.endContainer, range.endOffset);
    caretOffset = preCaretRange.toString().length;
  } 

  else if (document.selection && document.selection.type != "Control") {
    var textRange = document.selection.createRange();
    var preCaretTextRange = document.body.createTextRange();
    preCaretTextRange.moveToElementText(element);
    preCaretTextRange.setEndPoint("EndToEnd", textRange);
    caretOffset = preCaretTextRange.text.length;
  }

  return caretOffset;
}

// Demo:
var elm = document.querySelector('[contenteditable]');
elm.addEventListener('click', printCaretPosition)
elm.addEventListener('keydown', printCaretPosition)

function printCaretPosition(){
  console.log( getCaretCharOffset(elm), 'length:', this.textContent.trim().length )
}

<div contenteditable>some text here <i>italic text here</i> some other text here <b>bold text here</b> end of text</div>

La ligne d'appel dépend du type d'événement, pour un événement clé, utilisez ceci :

getCaretCharOffsetInDiv(e.target) + ($(window.getSelection().getRangeAt(0).startContainer.parentNode).index());

pour l'événement souris, utilisez ceci :

getCaretCharOffsetInDiv(e.target.parentElement) + ($(e.target).index())

dans ces deux cas, je m'occupe des lignes de rupture en ajoutant l'indice cible

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