2016-05-02 24 views
9

Sto scrivendo un evidenziatore di sintassi. L'evidenziatore dovrebbe aggiornare immediatamente l'evidenziatura durante l'immissione del testo e la navigazione con i tasti freccia.Come ottenere la posizione del cursore del testo dopo l'evento della pressione di un tasto?

Il problema che sto affrontando è che quando viene attivato l'evento "keypress", si ottiene comunque la vecchia posizione del cursore di testo tramite window.getSelection().

Esempio:

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>

Nell'esempio, posizionare il cursore prima della parola 'foo', quindi premere (il tasto freccia destra).

all'interno della console del vostro DevTool preferito vedrete il seguente:

keydown 0 
keypress 0 
keyup 1 

Che 0 oltre keypress è ovviamente la vecchia posizione del cursore. Se si tiene premuto un po 'più a lungo, si otterrà qualcosa di simile:

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

Quello che voglio ottenere è la nuova posizione del cursore come vorrei farlo per 'KeyUp' o 'input'. Anche se "keyup" viene attivato troppo tardi (voglio evidenziare la sintassi mentre il tasto è premuto) e "input" viene attivato solo quando c'è effettivamente un input (ma non produce alcun input).

C'è un evento che viene attivato dopo che la posizione del cursore è cambiata e non solo in ingresso? O devo calcolare la posizione del cursore del testo e, in caso affermativo, come? (Suppongo questo può diventare piuttosto complicato quando il testo avvolge e si preme ↓ (il tasto freccia giù).)

+1

Penso che avremo un altro problema usando l'evento keypress. Chrome non sembra attivare l'evento quando si preme un tasto freccia (Firefox lo fa). C'è un vecchio bug chromium nello stato WontFix che descrive questo: https://bugs.chromium.org/p/chromium/issues/detail?id=2606 – Alex

+0

Grazie per il suggerimento! A destra, l'evento 'keypress' non viene attivato, ma [è ovviamente contrassegnato come obsoleto] (https://www.w3.org/TR/uievents/#legacy-keyboardevent-event-types), comunque. Come ho visto ora, la specifica afferma che si consiglia alle persone di utilizzare invece l'evento 'beforeinput'. Anche il 'keydown' viene sparato continuamente. Anche se tutti quegli eventi sono stati sparati troppo presto. Ho ora [archiviato un problema sulla specifica degli eventi UI] (https://github.com/w3c/uievents/issues/111) nella speranza di ottenere un evento adeguato per questo. –

+1

Le posizioni del cursore/selezione ecc. Sono ancora una pita da supportare su tutti i browser. È stato un po 'che avevo bisogno di questo, ma [rangy] (https://github.com/timdown/rangy) è molto solido con le cose relative al cursore .. –

risposta

5

È possibile utilizzare setTimeout per elaborare l'evento keydown in modo asincrono:

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>

Questo metodo risolve il problema di elaborazione dei codici. Nel tuo esempio, hai anche un elemento span all'interno del div, che altera il valore della posizione restituito da

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

L'uso di 'setTimeout()' in questo modo sembra piuttosto un hack ma funziona . Quindi, grazie per quello! Se nessuno trova una soluzione migliore nei prossimi giorni, accetterò la tua risposta. Sono consapevole dell'alterazione del valore di posizione, ho appena fornito un codice semplificato nel mio esempio. L'intervallo restituito da 'getRangeAt (0)' fornisce anche il nodo di testo all'interno della proprietà 'startContainer', a cui fa riferimento' startOffset', quindi non è un problema. –

+1

Chiamare 'setTimeout (fn, 0)' è una tecnica ben nota per l'esecuzione del codice dopo che lo stack di chiamate corrente è stato completato. Puoi vedere questi articoli: [Perché è utile impostare setTimeout (fn, 0)?] (Http://stackoverflow.com/questions/779379/why-is-settimeoutfn-0-sometimes-useful), [Cosa fa setTimeout con un ritardo di 0 ms?? (https://www.quora.com/What-does-setTimeout-with-a-0ms-delay-do), [esercitazione JavaScript] (http://javascript.info/tutorial/events -e-timing-profondità); e questo [video divertente] (http://2014.jsconf.eu/speakers/philip-roberts-what-the-heck-is-the-event-loop-anyway.html). – ConnorsFan

1

Ecco una soluzione correggere la posizione utilizzando l'evento 'keydown':

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>

Purtroppo questa soluzione ha come sono diversi difetti:

  • Quando all'interno di un elemento figlio come <span> nell'esempio, non fornisce lo startOffset corretto né lo corretto.
  • Non può gestire più linee.
  • Non gestisce tutte le opzioni di navigazione. Per esempio. saltando in senso letterale tramite Ctrl + / non viene riconosciuto.

E ci sono probabilmente più problemi a cui non ho pensato. Mentre sarebbe possibile gestire tutti questi problemi, rende l'implementazione molto complessa. Quindi la semplice soluzione setTimeout(..., 0) fornita da ConnorsFan è decisamente preferibile fino a quando non c'è un event for caret position changes.

Problemi correlati