.execCommand(insertText...) .execCommand(insertHTML...) の代替実装を考える

JavaScript でカーソルがある位置に文字列を挿入したいときは execCommand を使用すると簡単に実現することができました。

developer.mozilla.org

しかし、execCommand はブラウザ間での不具合も多く、現在は非推奨のAPIになっています。

今回は非推奨になった execCommand を使用せずに、JavaScript でカーソルがある位置に文字列を挿入する実装を考えてみます。

文字が入力できる要素

ユーザーが文字を入力できる要素には textarea, input などがあります。標準で文字入力できない要素でも、contenteditable 属性を true にすると要素内の文字を編集できるようになります。

execCommand のinsertText,insertHTMLを使った場合は、対象の要素を意識することなく文字列を挿入することができていましたが、DOM操作で execCommand と同じ挙動を実現するには、対象の要素ごとに分岐を入れる必要があります。そのため、textarea, input と contenteditable それぞれの実装方法を考えます。

textarea, input 要素の場合

const text = '入力したい文字列';
const target = document.querySelector('textarea');
const selectionStart = target.selectionStart;
const selectionEnd = target.selectionEnd;
const startText = target.value.slice(0, selectionStart);
const endText = target.value.slice(selectionEnd);
target.value = startText + text + endText;
// 挿入文字列の末尾にカーソルを移動させる
target.selectionEnd = selectionStart + text.length;

テキストの選択開始の位置をselectionStart、選択終了の位置をselectionEndで取得します。

テキストの選択範囲より前の文字列をstartText変数、後の文字列をendText変数に保持して、最後に挿入したい文字列と組み合わせて入力欄を上書きします。選択していない場合はselectionStartselectionEndが同じ値を返します。

contenteditable の場合

const text = '入力したい文字列';
const selection = window.getSelection();
const range = selection.getRangeAt(0);
range.deleteContents();
const node = document.createTextNode(text);
const range.insertNode(node);
// 挿入文字列の末尾にカーソルを移動させる
selection.collapseToEnd();

div や li などの非インタラクティブ要素に contenteditable 属性が付与されているときは textarea や input のようにselectionStartでカーソル位置を取得することができません。この場合は Selectionオブジェクト を使用して、選択範囲やカーソル位置を取得します。

HTML を挿入したい場合は、DOM を生成して innerHTML で書き換えた node を insertNode の引数に渡してあげます。

const html = '入力したいHTML文字列';
const selection = window.getSelection();
const range = selection.getRangeAt(0);
range.deleteContents();

const node = document.createElement('div');
node.style.whiteSpace = 'pre';
node.innerHTML = html;
range.insertNode(node);

// 挿入文字列の末尾にカーソルを移動させる
selection.collapseToEnd();

参考