javascriptcontenteditableonkeydown

isssue detecting backspace from mobile keyboards in js


So i am using a <span> with contenteditable="true". onkeydown generates new elements with the key pressed as text. I use preventDefault() to not show the pressed character in the input field. If "backspace" is pressed, the last new element ist deleted.

It works really well on desktop browsers, but it has an issue on mobile. When testing firefox for android, if I type some letters and then press backspace, none of the events are triggered in js. The virtual keyboard (default and swiftkey tested) somehow keep the last letters and only remove them within the keyboard - only after these are all gone the events get passed to js again.

It is not easy to describe - the scribble shows it. The problem only exists on mobile!!

Resetting the "contenteditable" attribute prevents this behaviour but it also flashes the keyboard, which is not ideal.

//EDIT: fiddle is updated: catching 229, reset caret position, reset input content to " "

let editableSpan = document.querySelector('#inp');

editableSpan.onkeydown = function(e){
    console.log('key pressed', e.which, e.key);
    if(e.key === 'Backspace' || e.key === 'Process'){
    var keyEls = document.querySelectorAll('#out .kbd');
    keyEls.length > 0 && keyEls[keyEls.length - 1].remove();
  }else if(e.key.length === 1){
    var keyEl = document.createElement('span');
    keyEl.classList.add('kbd');
    keyEl.textContent = e.key;
    document.querySelector('#out').append(keyEl);
  }
  e.preventDefault();
}

editableSpan.onkeyup = function(e){
  var letterInp = document.querySelector('#inp');
  var selection = window.getSelection();
  var range     = document.createRange();
  letterInp.innerHTML = '&nbsp;';
  range.setStart(letterInp, letterInp.childNodes.length);
  range.collapse(true);
  selection.removeAllRanges();
  selection.addRange(range);
}
#inp {
  background-color: goldenrod;
  border: solid 3px black;
}
.kbd {
  display:inline-block;
  background-color:darkgrey;
  font-size: 2em;
  padding: .5em;
  margin-right: .3em;
  margin-top: .3em;
  border-radius:.3em;
  font-style:monospace;
}
<span id="out"></span>
<span class="kbd" id="inp" contenteditable="true">&nbsp;</span>



Solution

  • With some pointers from @Yogi I got a working solution.

    issue 1: no events fired in js - only happens when caret is placed before "space" -> always set caret position to end of element

    issue 2: key=229 is returned in keydown -> handle it the same as backspace and reset the inputs content to " " in onkeyup