Ruft den Start- und Endversatz eines Bereichs relativ zum übergeordneten Container ab

78

Angenommen, ich habe dieses HTML-Element:

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

Und der Benutzer wählt mit seiner Maus "Zuhause".

Ich möchte bestimmen können, wie viele Zeichen in #parentseiner Auswahl beginnen (und wie viele Zeichen am Ende #parentseiner Auswahl enden). Dies sollte auch dann funktionieren, wenn er ein HTML-Tag auswählt. (Und ich brauche es, um in allen Browsern zu funktionieren)

range.startOffset sieht vielversprechend aus, ist jedoch nur ein Versatz relativ zum unmittelbaren Container des Bereichs und nur dann ein Zeichenversatz, wenn der Container ein Textknoten ist.

Tom Lehman
quelle
> Dies sollte auch dann funktionieren, wenn er ein HTML-Tag auswählt. <Was meinst du damit? Wie wählt jemand ein HTML-Tag aus? Bitte erkläre.
Satyajit
Wenn der Benutzer alles auswählt #parent, enthält seine Auswahl einige HTML-Tags (<a> und <p>)
Tom Lehman

Antworten:

197

AKTUALISIEREN

Wie in den Kommentaren erwähnt, gibt meine ursprüngliche Antwort (unten) nur das Ende der Auswahl oder die Caret-Position zurück. Es ist ziemlich einfach, den Code anzupassen, um einen Start- und einen Endversatz zurückzugeben. Hier ist ein Beispiel dafür:

Hier ist eine Funktion, mit der der Zeichenversatz des Carets innerhalb des angegebenen Elements ermittelt wird. Dies ist jedoch eine naive Implementierung, die mit ziemlicher Sicherheit Inkonsistenzen mit Zeilenumbrüchen aufweist und keinen Versuch unternimmt, mit über CSS verborgenem Text umzugehen (ich vermute, dass der IE diesen Text korrekt ignoriert, während andere Browser dies nicht tun). Es wäre schwierig, mit all diesen Dingen richtig umzugehen. Ich habe es jetzt für meine Rangy- Bibliothek versucht .

Live-Beispiel: 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;
}
Tim Down
quelle
2
@ TimDown: Großartig, danke! Für mein spezielles Beispiel (mit TinyMCE) habe ich tatsächlich einen einfacheren Weg gefunden: tinyMCE.execCommand('mceInsertContent', false, newContent);den ich hier gefunden habe . Aber +1 für die Hilfe!
Travesty3
4
@ RafaelDiaz: Ja, und alle anderen Zeilenumbrüche, die durch HTML oder CSS impliziert werden. Es ist keine ideale Lösung.
Tim Down
1
@TimDown Gibt es eine Möglichkeit, dies zu ändern, um die Position stattdessen über das HTML-Element abzurufen? zB HTML-Zeichenfolge: "<div class =" c1 "> Hallo </ div> Und der Cursor hat" el "in Hallo ausgewählt, es würde 17
Fred Johnson
1
@TimDown Ich würde vorschlagen, win.getSelection().rangeCount > 0vor dem Ausführen eine Prüfung hinzuzufügen win.getSelection().getRangeAt(0), um Fehler zu vermeiden, wie unter stackoverflow.com/questions/22935320/… beschrieben . Ich bin selbst auf das Problem gestoßen, der RangeCount-Check hat es behoben
Gregor
2
@ KimchiMan: Du nicht mehr. element.documentwar für IE 5 und 5.5.
Tim Down
24

Ich weiß, dass dies ein Jahr alt ist, aber dieser Beitrag ist ein Top-Suchergebnis für viele Fragen zum Finden der Caret-Position und ich fand dies nützlich.

Ich habe versucht, Tims hervorragendes Skript oben zu verwenden, um die neue Cursorposition zu finden, nachdem ich ein Element in einem inhaltsbearbeitbaren Div von einer Position zur anderen gezogen habe. In FF und IE funktionierte es perfekt, aber in Chrome wurden beim Ziehen alle Inhalte zwischen dem Beginn und dem Ende des Ziehens hervorgehoben, was dazu führte, dass die Rückgabe caretOffsetzu groß oder zu klein war (gemessen an der Länge des ausgewählten Bereichs).

Ich habe der ersten if-Anweisung einige Zeilen hinzugefügt, um zu überprüfen, ob Text ausgewählt wurde, und das Ergebnis entsprechend anzupassen. Die neue Aussage ist unten. Verzeihen Sie mir, wenn es unangemessen ist, dies hier hinzuzufügen, da es nicht das ist, was das OP versucht hat, aber wie gesagt, mehrere Suchanfragen nach Informationen in Bezug auf die Caret-Position haben mich zu diesem Beitrag geführt, sodass es (hoffentlich) wahrscheinlich jemand anderem hilft .

Tims erste if-Anweisung mit hinzugefügten Zeilen (*):

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; 
  } // *
}
Cody Crumrine
quelle
1
Weiß jemand, wie man ein Wort hervorhebt, wenn ich bereits Versatzwerte bei mir habe (ich erhalte eine serverseitige JSON-Antwort) und das Wort basierend darauf hervorheben möchte? Ich konnte in der Rangy-Bibliothek nichts finden, wo ich einfach diese beiden Werte ( startZeichenversatz und stopZeichenversatz) einfügen und das Wort hervorheben kann. Bitte beraten.
John
Ich weiß, dass dies ein 4 Jahre alter Beitrag ist, aber können wir ein Wort in einem solchen Szenario hervorheben, in dem ich einen Bereich auswählen muss
Varun
Gibt es einen Grund, nach dem Sie suchen, selectedbevor Sie ihn subtrahieren? Wenn es 0 ist, ändern Sie das nicht, caretOffsetindem Sie 0 subtrahieren, oder?
Donnie D'Amato
1
@ DonnieD'Amato Guter Punkt. Ich habe einige Tests durchgeführt und wurde immer ausgewählt == 0, wenn es keine Auswahl gab (niemals undefiniert oder null oder etwas anderes Unerwartetes), daher sollten Sie sicher sein, diese Prüfung zu überspringen und immer ausgewählt abzuziehen.
Cody Crumrine
1
@CodyCrumrine Abgesehen davon ist das Subtrahieren von etwas nullgleichbedeutend mit der Verwendung 0(aber nicht sicher, ob dies in allen Javascript-Engines zutrifft). :)
Aleclarson
15

Nachdem ich einige Tage experimentiert hatte, fand ich einen Ansatz, der vielversprechend aussieht. Da selectNodeContents()nicht verarbeitet <br>Tags korrekt, schrieb ich einen benutzerdefinierten Algorithmus , um die Textlänge jeder bestimmen nodeinnerhalb ein contenteditable. Um zB den Auswahlstart zu berechnen, fasse ich die Textlängen aller vorhergehenden Knoten zusammen. Auf diese Weise kann ich (mehrere) Zeilenumbrüche verarbeiten:

Offenheit
quelle
Ich musste getTextLength und getNodeTextLength in eine if-Bedingungsprüfung einschließen if (node != null). Zusätzlich habe ich einen Check für eine Null node.parentNodenachif (node != parent)
RugerSR9
Funktioniert perfekt, vielen Dank. document.activeElementfunktionierte aus irgendeinem Grund nicht, also benutzte ich die einzelne Div, mit der ich arbeitete.
Xertz
1

Diese Lösung zählt die Länge des Textinhalts früherer Geschwister, die zum übergeordneten Container zurückkehren. Es deckt wahrscheinlich nicht alle Randfälle ab, obwohl es verschachtelte Tags beliebiger Tiefe verarbeitet, aber es ist ein guter, einfacher Ausgangspunkt, wenn Sie einen ähnlichen Bedarf haben.

  calculateTotalOffset(node, offset) {
    let total = offset
    let curNode = node

    while (curNode.id != 'parent') {
      if(curNode.previousSibling) {
        total += curNode.previousSibling.textContent.length

        curNode = curNode.previousSibling
      } else {
        curNode = curNode.parentElement
      }
    }

   return total
 }

 // after selection

let start = calculateTotalOffset(range.startContainer, range.startOffset)
let end = calculateTotalOffset(range.endContainer, range.endOffset)
tfwright
quelle