web-dev-qa-db-fra.com

Obtenir le décalage de début et de fin d'une plage par rapport à son conteneur parent

Supposons que j'ai cet élément HTML:

<div id="parent">
 Hello everyone! <a>This is my home page</a>
 <p>Bye!</p>
</div>

Et l'utilisateur sélectionne "home" avec sa souris.

Je veux pouvoir déterminer le nombre de caractères dans #parent sa sélection commence (et combien de caractères à partir de la fin de #parent sa sélection se termine). Cela devrait fonctionner même s'il sélectionne une balise HTML. (Et j'en ai besoin pour fonctionner dans tous les navigateurs)

range.startOffset semble prometteur, mais il s'agit d'un décalage par rapport uniquement au conteneur immédiat de la plage et il s'agit d'un décalage de caractère uniquement si le conteneur est un nœud de texte.

62
Tom Lehman

[~ # ~] mise à jour [~ # ~]

Comme indiqué dans les commentaires, ma réponse d'origine (ci-dessous) ne renvoie que la fin de la sélection ou la position du curseur. Il est assez facile d'adapter le code pour renvoyer un décalage de début et de fin; voici un exemple qui le fait:

function getSelectionCharacterOffsetWithin(element) {
    var start = 0;
    var end = 0;
    var doc = element.ownerDocument || element.document;
    var win = doc.defaultView || doc.parentWindow;
    var sel;
    if (typeof win.getSelection != "undefined") {
        sel = win.getSelection();
        if (sel.rangeCount > 0) {
            var range = win.getSelection().getRangeAt(0);
            var preCaretRange = range.cloneRange();
            preCaretRange.selectNodeContents(element);
            preCaretRange.setEnd(range.startContainer, range.startOffset);
            start = preCaretRange.toString().length;
            preCaretRange.setEnd(range.endContainer, range.endOffset);
            end = preCaretRange.toString().length;
        }
    } else if ( (sel = doc.selection) && sel.type != "Control") {
        var textRange = sel.createRange();
        var preCaretTextRange = doc.body.createTextRange();
        preCaretTextRange.moveToElementText(element);
        preCaretTextRange.setEndPoint("EndToStart", textRange);
        start = preCaretTextRange.text.length;
        preCaretTextRange.setEndPoint("EndToEnd", textRange);
        end = preCaretTextRange.text.length;
    }
    return { start: start, end: end };
}

function reportSelection() {
  var selOffsets = getSelectionCharacterOffsetWithin( document.getElementById("editor") );
  document.getElementById("selectionLog").innerHTML = "Selection offsets: " + selOffsets.start + ", " + selOffsets.end;
}

window.onload = function() {
  document.addEventListener("selectionchange", reportSelection, false);
  document.addEventListener("mouseup", reportSelection, false);
  document.addEventListener("mousedown", reportSelection, false);
  document.addEventListener("keyup", reportSelection, false);
};
#editor {
  padding: 5px;
  border: solid green 1px;
}
Select something in the content below:

<div id="editor" contenteditable="true">A <i>wombat</i> is a marsupial native to <b>Australia</b></div>
<div id="selectionLog"></div>

Voici une fonction qui obtiendra le décalage de caractère du curseur dans l'élément spécifié; cependant, c'est une implémentation naïve qui aura presque certainement des incohérences avec les sauts de ligne, et ne fait aucune tentative pour traiter le texte caché via CSS (je soupçonne IE ignorera correctement ce texte tandis que les autres navigateurs le feront). pas.) Pour gérer tout cela correctement serait difficile. J'ai maintenant essayé pour ma bibliothèque Rangy .

Exemple en direct: http://jsfiddle.net/TjXEG/900/

function getCaretCharacterOffsetWithin(element) {
    var caretOffset = 0;
    var doc = element.ownerDocument || element.document;
    var win = doc.defaultView || doc.parentWindow;
    var sel;
    if (typeof win.getSelection != "undefined") {
        sel = win.getSelection();
        if (sel.rangeCount > 0) {
            var range = win.getSelection().getRangeAt(0);
            var preCaretRange = range.cloneRange();
            preCaretRange.selectNodeContents(element);
            preCaretRange.setEnd(range.endContainer, range.endOffset);
            caretOffset = preCaretRange.toString().length;
        }
    } else if ( (sel = doc.selection) && sel.type != "Control") {
        var textRange = sel.createRange();
        var preCaretTextRange = doc.body.createTextRange();
        preCaretTextRange.moveToElementText(element);
        preCaretTextRange.setEndPoint("EndToEnd", textRange);
        caretOffset = preCaretTextRange.text.length;
    }
    return caretOffset;
}
165
Tim Down

Je sais que cela fait un an, mais ce message est un résultat de recherche supérieur pour beaucoup de questions sur la recherche de la position Caret et j'ai trouvé cela utile.

J'essayais d'utiliser l'excellent script de Tim ci-dessus pour trouver la nouvelle position du curseur après avoir fait glisser-déposer un élément d'une position à une autre dans une div modifiable de contenu. Cela fonctionnait parfaitement dans FF et IE, mais dans Chrome, l'action de glissement mettait en évidence tout le contenu entre le début et la fin du glissement, ce qui faisait que le caretOffset retourné était trop grand ou trop petit (par la longueur de l'élément sélectionné). région).

J'ai ajouté quelques lignes à la première instruction if pour vérifier si le texte a été sélectionné et ajuster le résultat en conséquence. La nouvelle déclaration est ci-dessous. Pardonnez-moi s'il est inapproprié d'ajouter ceci ici, car ce n'est pas ce que le PO tentait de faire, mais comme je l'ai dit, plusieurs recherches sur les informations liées à la position de Caret m'ont conduit à ce poste, il est donc (espérons-le) susceptible d'aider quelqu'un d'autre .

La première instruction if de Tim avec des lignes ajoutées (*):

if (typeof window.getSelection != "undefined") {
  var range = window.getSelection().getRangeAt(0);
  var selected = range.toString().length; // *
  var preCaretRange = range.cloneRange();
  preCaretRange.selectNodeContents(element);
  preCaretRange.setEnd(range.endContainer, range.endOffset);

  if(selected){ // *
    caretOffset = preCaretRange.toString().length - selected; // *
  } else { // *
    caretOffset = preCaretRange.toString().length; 
  } // *
}
20
Cody Crumrine

Après avoir expérimenté quelques jours, j'ai trouvé une approche qui semble prometteuse. Parce que selectNodeContents() ne gère pas <br> tags correctement, j'ai écrit un algorithme personnalisé pour déterminer la longueur du texte de chaque node à l'intérieur d'un contenteditable. Pour calculer par ex. au début de la sélection, je résume les longueurs de texte de tous les nœuds précédents. De cette façon, je peux gérer (plusieurs) sauts de ligne:

var editor = null;
var output = null;

const getTextSelection = function (editor) {
    const selection = window.getSelection();

    if (selection != null && selection.rangeCount > 0) {
        const range = selection.getRangeAt(0);

        return {
            start: getTextLength(editor, range.startContainer, range.startOffset),
            end: getTextLength(editor, range.endContainer, range.endOffset)
        };
    } else
        return null;
}

const getTextLength = function (parent, node, offset) {
    var textLength = 0;

    if (node.nodeName == '#text')
        textLength += offset;
    else for (var i = 0; i < offset; i++)
        textLength += getNodeTextLength(node.childNodes[i]);

    if (node != parent)
        textLength += getTextLength(parent, node.parentNode, getNodeOffset(node));

    return textLength;
}

const getNodeTextLength = function (node) {
    var textLength = 0;

    if (node.nodeName == 'BR')
        textLength = 1;
    else if (node.nodeName == '#text')
        textLength = node.nodeValue.length;
    else if (node.childNodes != null)
        for (var i = 0; i < node.childNodes.length; i++)
            textLength += getNodeTextLength(node.childNodes[i]);

    return textLength;
}

const getNodeOffset = function (node) {
    return node == null ? -1 : 1 + getNodeOffset(node.previousSibling);
}

window.onload = function () {
    editor = document.querySelector('.editor');
    output = document.querySelector('#output');

    document.addEventListener('selectionchange', handleSelectionChange);
}

const handleSelectionChange = function () {
    if (isEditor(document.activeElement)) {
        const textSelection = getTextSelection(document.activeElement);

        if (textSelection != null) {
            const text = document.activeElement.innerText;
            const selection = text.slice(textSelection.start, textSelection.end);
            print(`Selection: [${selection}] (Start: ${textSelection.start}, End: ${textSelection.end})`);
        } else
            print('Selection is null!');
    } else
        print('Select some text above');
}

const isEditor = function (element) {
    return element != null && element.classList.contains('editor');
}

const print = function (message) {
    if (output != null)
        output.innerText = message;
    else
        console.log('output is null!');
}
* {
    font-family: 'Georgia', sans-serif;
    padding: 0;
    margin: 0;
}

body {
    margin: 16px;
}

.p {
    font-size: 16px;
    line-height: 24px;
    padding: 0 2px;
}

.editor {
    border: 1px solid #0000001e;
    border-radius: 2px;
    white-space: pre-wrap;
}

#output {
    margin-top: 16px;
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <script src="./script.js" async></script>
    <link href="./stylesheet.css" rel="stylesheet">
    <title>Caret Position</title>
</head>
<body>
    <p class="editor" contenteditable="true"><em>Write<br></em><br>some <br>awesome <b><em>text </em></b>here...</p>
    <p id="output">Select some text above</p>
</body>
</html>
4
Candor