Unexpected caret change in Android Chrome
As I was reinventing the wheel for fun and profit , I have highlighted interesting behavior that only appears in Android Chrome.
I am formatting text input as a custom type, which means setting the input value by losing the caret position. No problem - just track the carriage position and set it back after changing the value (setting for added / removed characters) in the input event callback.
Works great! Except for Android Chrome (Android 7, Chrome 58.0.3029.83) - I set the caret position, watch the caret position where I expect it to - then in the keyup event it magically moves - after but before .
Tracking caret positions
You can see this in this (extremely simplified) fiddle . I even dump the relevant data on the screen, so you don't need to connect a debugger to see it.
Enter "abcd" and get:
Numbers are the caret positions from this .selectionStart
D = keyDown, i = Input, C = position Modified, U = keyUp
D: 0 | I: 0 | C: 1 | U: 1
D: 1 | I: 1 | C: 2 | U: 2
D: 2 | I: 2 | C: 3 | U: 3
D: 3 | I: 3 | C: 5 | U: 4 //<- This 4 is the culprit
Where are all other browsers tested (IE11, iOS Safari, Desktop Chrome):
D: 0 | I: 1 | C: 1 | U: 1
D: 1 | I: 2 | C: 2 | U: 2
D: 2 | I: 3 | C: 3 | U: 3
D: 3 | I: 4 | C: 5 | U: 5
Is there an event where I am missing? It's not in the docs I can find, although I know virtual keyboards are a little frustrating. I would like to know where exactly this is happening so I can learn it.
Also tried
beforeinput
, compositionstart
and compositionend
events. The only one that starts is compositionstart
, between events keydown
and input
. It shows the carriage position that I would have expected to see at the time.
Typing "abcd" and then hitting backspace in Android Chrome gives you:
D: 4 | I: 3 | C: 5 | U: 5
An attempt to point out that everything that moves with the carriage was not done in this case.
Don't look for a workaround. The workaround is to set the caret again in the keyup event. I am asking to know if anyone knows the wrong answer.
source to share
As you can see, input on soft keyboards in Android Chrome does not update the caret position in an event input
like PC Chrome does. This is fairly common in web development; there is always one case where it just doesn't work the same way.
D: 0 | I: 0 | C: 1 | U: 1
Based on my own testing, the caret position is updated at input
on insert, but when typing, it only updates at keyup
. This means that if you copy one letter and paste it multiple times, when you get to the fourth paste, the textbox formatting code will work as expected (and like on a PC).
This also means that if you want your code to run reliably, you will need to keep track of whether the caret is updated at input
, but also pre-update at at input
, since keyup
it won't run if you insert. This can be difficult, however, as it keydown
also fails on insert, so you won't know the previous caret position.
You say you didn't look for a workaround, but based on the editing on my post, I suspect it is needed now. I'm not sure what would be a good workaround. Perhaps keeping track of the carriage position changes and storing them manually (perhaps in a data attribute in a textbox) would be your best bet. The real difficulty comes from the fact that you can insert as many characters as you want when pasting, so you can't just assume that if input
running, when keydown
not, use currentCaretPosition - 1
.
After a little experimenting, this is the workaround I came up with:
https://jsfiddle.net/smfaenvb/10/
It's not perfect, but it does work on Android Chrome the same way your original test code worked on PC Chrome.
source to share