HTML でリッチテキストエディタを実現する方法

そんなの contenteditable でできんじゃん、と思っていたら全然そんなことなかったのでメモ。
まず編集可能な HTML 領域を作るには次の二種類の方法がある。

  • contenteditable="true"
  • designMode="on"

違いは contenteditable がある特定の要素の中だけを編集可能にするのに対し、designMode はドキュメント全体を編集可能にする。前者の方が使いやすいように見えるが、世にあるリッチテキストエディタは iframe 内のドキュメントに対し designMode="on" にする方法を使っている。

なぜか。それは選択範囲の扱いが難しいからだ。選択範囲は、IEであれば document.selection、その他のモダンブラウザであれば window.getSelection() で取得できるが、基本的にドキュメント全体が対象になるため、特定の要素内の選択範囲のスタイルだけを変更するといったことがとても難しくなる(ただし、ここまでは選択範囲の親要素が編集可能範囲の子要素であるか調べることで実現可能である)。

次に選択範囲を編集することを考えると、単に選択範囲内のノードを書き換えればよいだけのように思われる。しかし、例えば太字にするだけでも、現在選択中のテキストが bold か否かを判定して bold でなければ boldを指定したタグで囲む、そうでなければ解除するという面倒な処理をする必要があるうえに、現在選択中のテキストが bold であるかを判断することはとても難しい(text-weight: bold が指定されている直近の親要素を探すしかない)。

しかし、実は、IEにもその他のブラウザにも execCommand というコマンド発行のメソッドがあり、このメソッドを使うことでHTMLの加工ができる。例えば、document.execCommand('bold', false) を実行すると、スタイルエディタBボタンを押したような効果が得られる。

iframe と designMode と execCommand の三つを組み合わせることで、リッチテキストエディタの実現が可能になることがご理解いただけるだろう。

ここまで来れば、ほとんど完成したも同然であるが、実はこの方法だけでは IE で困った現象に出くわす。文字の範囲を選択して何らかのボタンを押してその中でコマンドを実行しようとすると、クリックした時点でフォーカスが移動してしまい、未選択の状態になってしまう。この問題へは、該当のボタンに unselectable="on" を付ければよいようだ。選択できないので、フォーカスが移らず想定通りの動きとなる。