2016-05-02 10 views
9

Ich schreibe einen Syntax-Highlighter. Der Highlighter sollte die Hervorhebung sofort bei der Texteingabe und Navigation mit den Pfeiltasten aktualisieren.Wie bekomme ich die Position des Textcursors nach dem Tastendruck?

Das Problem, mit dem ich konfrontiert bin, ist, dass, wenn das 'keypress' Ereignis ausgelöst wird, Sie immer noch die alte Position des Textcursors über window.getSelection() erhalten.

Beispiel:

function handleKeyEvent(evt) { 
 
    console.log(evt.type, window.getSelection().getRangeAt(0).startOffset); 
 
} 
 

 
var div = document.querySelector("div"); 
 
div.addEventListener("keydown", handleKeyEvent); 
 
div.addEventListener("keypress", handleKeyEvent); 
 
div.addEventListener("input", handleKeyEvent); 
 
div.addEventListener("keyup", handleKeyEvent);
<div contenteditable="true">f<span class="highlight">oo</span></div>

Im Beispiel setzen die Einfügemarke vor dem Wort 'foo', dann (die rechte Pfeiltaste) drücken.

Innerhalb der Konsole Ihres Lieblings DevTool Sie folgenden sehen werden:

keydown 0 
keypress 0 
keyup 1 

Die 0 neben keypress ist offensichtlich die alte Position der Einfügemarke. Wenn Sie ein wenig nach unten → länger halten, werden Sie so etwas wie dieses:

keydown 0 
keypress 0 
keydown 1 
keypress 1 
keydown 1 
keypress 1 
keydown 2 
keypress 2 
keyup 2 

Was ich will bekommen, ist die neue Position der Einfügemarke, wie ich es für ‚keyup‘ bekommen würde oder ‚Input‘. Obwohl "keyup" zu spät ausgelöst wird (ich möchte die Syntax hervorheben, während die Taste gedrückt wird) und "input" nur ausgelöst wird, wenn tatsächlich ein Eingang vorhanden ist ( erzeugt jedoch keine Eingabe).

Gibt es ein Ereignis, das ausgelöst wird, nachdem sich die Caret-Position geändert hat und nicht nur bei der Eingabe? Oder muss ich die Position des Textcursors berechnen und wenn ja, wie? (Ich nehme an, dies ziemlich kompliziert werden kann, wenn der Text umbrochen und Sie drücken (die Pfeiltaste nach unten).)

+1

Ich denke, Sie werden ein anderes Problem mit dem Tastendruck-Ereignis haben. Chrome scheint das Ereignis nicht auszulösen, wenn eine Pfeiltaste gedrückt wird (Firefox tut das). Es gibt einen alten Chrombug im Status WontFix, der dies beschreibt: https://bugs.chromium.org/p/chromium/issues/detail?id=2606 – Alex

+0

Vielen Dank für den Hinweis! Richtig, das 'keypress'-Ereignis wird nicht ausgelöst, aber [es ist offensichtlich als veraltet gekennzeichnet] (https://www.w3.org/TR/uievents/#legacy-keyboardevent-event-types), wie auch immer. Wie ich jetzt gesehen habe, heißt es in der Spezifikation, dass Leuten empfohlen wird, stattdessen das 'BeforeInput'-Ereignis zu verwenden. Und "Keydown" wird auch kontinuierlich abgefeuert. Obwohl all diese Ereignisse zu früh gefeuert werden. Ich habe jetzt ein Problem in der UI-Events-Spezifikation (https://github.com/w3c/uievents/issues/111) in der Hoffnung, ein passendes Event dafür zu bekommen, eingereicht. –

+1

Cursorpositionen/Auswahl usw. sind immer noch eine Pita, die auf allen Browsern unterstützt wird. Es ist eine Weile her, dass ich das brauchte, aber [riggy] (https://github.com/timdown/rangy) ist sehr solide mit cursor-verwandten Sachen .. –

Antwort

5

Sie setTimeout verwenden können die keydown Ereignis asynchron zu verarbeiten:

function handleKeyEvent(evt) { 
 
    setTimeout(function() { 
 
     console.log(evt.type, window.getSelection().getRangeAt(0).startOffset); 
 
    }, 0); 
 
} 
 

 
var div = document.querySelector("div"); 
 
div.addEventListener("keydown", handleKeyEvent);
<div contenteditable="true">This is some text</div>

Diese Methode adressiert das Schlüsselverarbeitungsproblem. In Ihrem Beispiel haben Sie auch ein span Element innerhalb der div, die durch den Positionswert zurück ändert

window.getSelection().getRangeAt(0).startOffset 
+1

Mit 'setTimeout()' sieht dieser Weg eher ein Hack aus, aber es funktioniert . Also, danke dafür! Wenn in den nächsten Tagen niemand eine bessere Lösung findet, werde ich Ihre Antwort akzeptieren. Ich bin mir der Änderung des Positionswertes bewusst, ich habe in meinem Beispiel nur einen vereinfachten Code zur Verfügung gestellt. Der von 'getRangeAt (0)' zurückgegebene Bereich stellt auch den Textknoten innerhalb der 'startContainer' -Eigenschaft bereit, auf die sich' startOffset' bezieht, so dass dies kein Problem darstellt. –

+1

Der Aufruf von 'setTimeout (fn, 0)' ist eine bekannte Technik zum Ausführen von Code, nachdem der aktuelle Aufrufstapel abgeschlossen wurde. Sie können diese Artikel sehen: [Warum ist setTimeout (fn, 0) manchmal nützlich?] (Http://stackoverflow.com/questions/779379/why-is-settimeoutfn-0-sometimes-useful), [Was setzt setTimeout mit eine 0ms Verzögerung?] (https://www.quora.com/What-does-setTimeout-with-a-0ms-delay-do), [Javascript tutorial] (http://javascript.info/tutorial/events -und-Timing-Tiefe); und dieses [unterhaltsame Video] (http://2014.jsconf.eu/speakers/philip-roberts-what-the-heck-is-the-event-loop-anyway.html). – ConnorsFan

1

Hier ist eine Lösung, die die Position Korrektur des 'keyDown' Ereignis mit:

function handleKeyEvent(evt) { 
 
    var caretPos = window.getSelection().getRangeAt(0).startOffset; 
 

 
    if (evt.type === "keydown") { 
 
    switch(evt.key) { 
 
     case "ArrowRight": 
 
     if (caretPos < evt.target.innerText.length - 1) { 
 
      caretPos++; 
 
     } 
 
     break; 
 

 
     case "ArrowLeft": 
 
     if (caretPos > 0) { 
 
      caretPos--; 
 
     } 
 
     break; 
 

 
     case "ArrowUp": 
 
     case "Home": 
 
     caretPos = 0; 
 
     break; 
 

 
     case "ArrowDown": 
 
     case "End": 
 
     caretPos = evt.target.innerText.length; 
 
     break; 
 

 
     default: 
 
     return; 
 
    } 
 
    } 
 
    console.log(caretPos); 
 
} 
 

 
var div = document.querySelector("div"); 
 
div.addEventListener("keydown", handleKeyEvent); 
 
div.addEventListener("input", handleKeyEvent);
<div contenteditable="true">f<span class="highlight">oo</span></div>

Leider ist diese Lösung können wie angeboten hat mehrere Mängel auf:

  • Wenn innerhalb eines untergeordneten Elements wie dem <span> in dem Beispiel, gibt es nicht die richtige startOffset noch die richtige startContainer.
  • Es kann nicht mehrere Zeilen verarbeiten.
  • Es behandelt nicht alle Navigationsoptionen. Z.B. wörtlich springen über Ctrl + / wird nicht erkannt.

Und es gibt wahrscheinlich mehr Probleme, an die ich nicht gedacht habe. Obwohl all diese Probleme bewältigt werden könnten, ist die Implementierung sehr komplex. So ist die einfache setTimeout(..., 0) Lösung von ConnorsFan definitiv vorzuziehen, bis es eine event for caret position changes gibt.

Verwandte Themen