Help us understand the problem. What is going on with this article?

CodeMirrorでスマホからの日本語入力をなんとかする

More than 1 year has passed since last update.

前提

  • CodeMirror 5.31.0 について記述しています。
  • iPhone6s、iOS10.3.3、Mobile Safari で遭遇した問題について記述しています。

問題点

CodeMirrorには2つの入力モードが用意されており、それはinputStyleというオプションで切り替えることができる。
なのだけれど、スマホから使用した時、日本語入力(というかIME)に関しては、この2つの入力モードのどちらでも何かしらの問題が発生する。

inputStyle = textarea

見えないテキストエリアを使用して入力を行う方式。
入力しているテキストエリアと、表示されているcontenteditableなエディタ部分の要素との違いが問題を発生させる。文章をタッチするとtextareaが起動してイベントを拾ってしまうため、文字選択ができない問題がある。
入力は問題なくできるように思えるが、IMEで確定前の文字を削除すると文字が二重で入力されたりする。

inputStyle = contenteditable

contenteditableの要素に直接入力する方法。直接といってもキー入力をエミュレートしてCodeMirror内で複雑なことをしているのだけれど・・。
この入力方式の場合は致命的な問題がある。なんとIMEがきかない。たとえば、「て」と入力した時点で「て」が確定されてしまい、「手」のような漢字に変換することができない。

まとめるとこうなる。つまりCodeMirrorを日本で使う場合は、モバイルでまったく使い物にならない。

textarea contenteditable
IME入力 ×
IME入力中の文字削除 × ×
文字選択 ×
絵文字キーボードでの入力 ×
3Dタッチによるカーソル移動 ×

この問題は本家でIssueにもなっている。

解決方法

イベントの流れをみると、どうやらIME入力中にkeypressとかkeydownが発生するのが主な原因だったようなので、IME入力中のkeypressはキャンセルするようにした。

cm = CodeMirror.fromTextArea(textarea, {inputStyle: 'contenteditable'})

if(UserAgentがモバイルであれば) {
  const origOnKeyPress = cm.display.input.onKeyPress // 元のkeypressを取得
  cm.display.input.onKeyPress = function(e) {
    // iOSの絵文字キーボードのように、サロゲートペアの直接のkeypressは、CodeMirrorの処理を通すと文字化けるのでキャンセルする
    if(e.which >= 0x10000) {
      return
    }

    if(!cm.display.input.composing) { // 文字入力中かどうかはcomposingでわかる
      cm.keyPressTimer = setTimeout( () => {
        origOnKeyPress.call(this, e)
      }, 30) // 30ms以内(適当)にcompositionstartが呼ばれなければkeypressは実行して良い
    }
  }

  const inputArea = cm.display.input.div || cm.display.input.textarea // 一応、inputStyleがどちらの状態でも大丈夫な書き方にした
  inputArea.addEventListener('compositionstart', (_cm, e) => {
    if(cm.keyPressTimer) {
      clearTimeout(cm.keyPressTimer)
    }
  }, false)

  const inputField = cm.display.input.getField()
  // IME入力中のkeydownをCodeMirrorに渡さないようにする
  window.addEventListener('keydown', function(e){
    if(e.target == inputField && cm.display.input.composing) {
      e.stopPropagation()
    }
  }, true)

  // バーチャルキーボードの「完了」ボタンを押した時に、キーボードだけが消えないようにする
  inputField.addEventListener('blur', function(e){
    if(e.relatedTarget) { // 「完了」ボタンでのblurかどうか
      return
    }
    e.stopPropagation() // stopしないと文字が二重で入力される

    if (cm.display.input.composing) {
      // 再focusが確定したらblurさせる
      inputField.focus()
      setTimeout(function(){
        inputField.blur()
      }, 1)
    }
  }, false)
}

残る問題点

とりあえずこれだけやれば日本語入力や文字の選択が普通にできた。のだけれど、IME入力中にエディタの内容が外部から書き換わった場合(CodeMirrorのAPIを使って書き換えたりした時)に、MobileSafariのタブ単位でエラーが発生し、タブが強制リロードされるという致命的な問題がある。

どうやらMobileSafariでは、contenteditableの要素に対してIMEで入力中に、対象の要素が削除されると入力した文字が行き場を失ってしまいエラーになってしまうみたいだった。
通常の使い方であれば、IME入力中にエディタの中身を書き換えるようなことはしないので大丈夫ではあるのだけれど、たとえば同時編集機能にCodeMirrorを使っていて、他のユーザからエディタ内の文字が書き換えられるようなシチュエーションでは問題になる。

これの解決方法は・・・・!! わからない。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした