2016-05-02 3 views
9

Я пишу синтаксический ярлык. Маркер должен обновлять подсветку сразу при вводе текста и навигации с помощью клавиш со стрелками.Как получить позицию текстового курсора после события нажатия клавиши?

Проблема, с которой я сталкиваюсь, заключается в том, что при запуске события «keypress» вы все равно получаете старую позицию текстового курсора через window.getSelection().

Пример:

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>

В примере, поместите курсор перед словом 'Foo', а затем нажмите (ключ Стрелка вправо).

В консоли вашего любимого DevTool вы увидите следующее:

keydown 0 
keypress 0 
keyup 1 

Это 0, кроме keypress, очевидно, старая каретки положения. Если зажать немного больше, вы получите что-то вроде этого:

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

То, что я хочу, чтобы это новое положение каретки, как я хотел бы получить его «KeyUp» или «вход». Хотя «keyup» запускается слишком поздно (я хочу выделить синтаксис при нажатии клавиши), а «вход» запускается только при наличии некоторого ввода (но не производит ввода).

Есть ли событие, которое было произведено после того, как позиция каретки изменилась, а не только на вход? Или мне нужно рассчитать положение текстового курсора, и если да, то как? (Я предполагаю, что это может быть довольно сложным, когда текст обтекает и нажимается (Стрелка вниз).)

+1

Я думаю, у вас будет другая проблема с использованием события нажатия клавиши. Кажется, что Chrome не срабатывает при нажатии клавиши со стрелкой (Firefox делает). В статусе WontFix есть старая хромовая ошибка: https://bugs.chromium.org/p/chromium/issues/detail?id=2606 – Alex

+0

Спасибо за подсказку! Правильно, событие 'keypress' не запускается, но [оно явно отмечено как устаревшее] (https://www.w3.org/TR/uievents/#legacy-keyboardevent-event-types), в любом случае. Как я увидел сейчас, в спецификации указано, что людям рекомендуется использовать событие 'beforeinput' вместо этого. И 'keydown' также запускается непрерывно. Хотя все эти события увольняются слишком рано. Теперь я [зарегистрировал проблему в спецификации пользовательских интерфейсов] (https://github.com/w3c/uievents/issues/111) в надежде получить соответствующее событие для этого. –

+1

Позиции курсора/выбор и т. Д. Все еще являются лавашами для поддержки во всех браузерах. Это было какое-то время, когда мне было нужно это, но [rangy] (https://github.com/timdown/rangy) очень прост в работе с курсором. –

ответ

5

Вы можете использовать setTimeout обработать keydown событие асинхронно:

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>

Этот метод решает проблему обработки ключа. В вашем примере, у вас также есть span элемент внутри из div, который изменяет значение положения, возвращенное

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

Использование 'setTimeout()' этот способ выглядит довольно взломанным, но он работает , Итак, спасибо за это! Если на следующий день никто не найдет лучшего решения, я приму свой ответ. Я знаю об изменении значения позиции, я просто предоставил некоторый упрощенный код в моем примере. Диапазон, возвращаемый 'getRangeAt (0)', также предоставляет текстовый узел в свойстве 'startContainer', на который ссылается' startOffset', так что это не проблема. –

+1

Вызов 'setTimeout (fn, 0)' - это хорошо известный метод запуска кода после завершения текущего стека вызовов. Вы можете видеть эти статьи: [Почему setTimeout (fn, 0) иногда полезно?] (Http://stackoverflow.com/questions/779379/why-is-settimeoutfn-0-sometimes-useful), [Что делает setTimeout с задержка 0мс?] (https://www.quora.com/What-does-setTimeout-with-a-0ms-delay-do), [Javascript tutorial] (http://javascript.info/tutorial/events и-временная глубина); и это [развлекательное видео] (http://2014.jsconf.eu/speakers/philip-roberts-what-the-heck-is-the-event-loop-anyway.html). – ConnorsFan

1

Вот решение коррекции положения с помощью «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>

К сожалению, это решение, так как есть несколько недостатков:

  • Внутри дочернего элемента, такого как <span>, в этом примере он не дает правильного startOffset и не соответствует правилу startContainer.
  • Он не может обрабатывать несколько строк.
  • Он не обрабатывает все варианты навигации. Например. перескакивая по-разному через Ctrl + / не распознается.

И, вероятно, есть больше проблем, о которых я не думал. Хотя можно было бы справиться со всеми этими проблемами, это делает реализацию очень сложной. Таким образом, простое решение setTimeout(..., 0), предоставляемое ConnorsFan, безусловно, предпочтительнее, пока не будет event for caret position changes.