Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get text cursor position after keypress event happened?

Tags:

I am writing a syntax highlighter. The highlighter should update the highlighting immediately while entering text and navigating with the arrow keys.

The problem I'm facing is that when the 'keypress' event is fired, you still get the old position of the text cursor via window.getSelection().

Example:

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>

In the example, place the caret before the word 'foo', then press (the Right Arrow key).

Within the console of your favorite DevTool you'll see the following:

keydown 0 keypress 0 keyup 1 

That 0 besides keypress is obviously the old caret position. If you hold down a bit longer, you'll get something like this:

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

What I want to get is the new caret position like I would get it for 'keyup' or 'input'. Though 'keyup' is fired too late (I want to highlight the syntax while the key is pressed down) and 'input' is only fired when there is actually some input (but doesn't produce any input).

Is there an event that is fired after the caret position has changed and not only on input? Or do I have to calculate the position of the text cursor and if so, how? (I assume this can get quite complicated when the text wraps and you press (the Down Arrow key).)

like image 496
Sebastian Zartner Avatar asked May 02 '16 08:05

Sebastian Zartner


People also ask

How can get current cursor position in textbox using jQuery?

For exactly what you're after, there's the jQuery Caret (jCaret) plugin. For your code to get the position you could do something like this: $("#myTextInput"). bind("keydown keypress mousemove", function() { alert("Current position: " + $(this).

What is the difference between jQuery's change () and keypress () events?

The change event occurs if the value has been changed when the element loses focus. The keypress event occurs every time you press down and release a (non control) key.

Are keypress events deprecated?

Deprecated: This feature is no longer recommended. Though some browsers might still support it, it may have already been removed from the relevant web standards, may be in the process of being dropped, or may only be kept for compatibility purposes.

Which event is called when key is pressed?

The keydown event is fired when a key is pressed. Unlike the keypress event, the keydown event is fired for all keys, regardless of whether they produce a character value. The keydown and keyup events provide a code indicating which key is pressed, while keypress indicates which character was entered.


2 Answers

You can use setTimeout to process the keydown event asynchronously:

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>

That method addresses the key processing problem. In your example, you also have a span element inside of the div, which alters the position value returned by

window.getSelection().getRangeAt(0).startOffset 
like image 200
ConnorsFan Avatar answered Sep 20 '22 09:09

ConnorsFan


Here's a solution correcting the position using the 'keydown' event:

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>

Unfortunately this solution as is has several flaws:

  • When inside a child element like the <span> in the example, it doesn't provide the correct startOffset nor the correct startContainer.
  • It cannot handle multiple lines.
  • It doesn't handle all navigation options. E.g. jumping word-wise via Ctrl+/ is not recognized.

And there are probably more issues I didn't think of. While it would be possible to handle all those issues, it makes the implementation very complex. So the simple setTimeout(..., 0) solution provided by ConnorsFan is definitely preferable until there is an event for caret position changes.

like image 30
Sebastian Zartner Avatar answered Sep 22 '22 09:09

Sebastian Zartner