🦔

チップ画像をテキストフォームで表示させる方法

2021/11/07に公開

始めに

送信時に名前などが挿入されて送る際、入力フォームは以下のような感じにチップ画像になっているものを見たことがあるかもしれません。

これが実際にどのように実装されているか気になって調べてサンプルコードを書いてみたので、それについてまとめたいと思います。
サンプルはjsfiddleで書きましたので、動きが気になる方は先にこちらを触ってみてください。

実装方法

contenteditableを設定して画像ごと編集できるようにする

まず普通のinput(type="text")では画像の表示ができないので、divタグにcontenteditable属性を付与してその中身が編集できるようにします。

<div contenteditable>Hello, <img class="tag-image" src="image url">World!</div>

テキストも編集できつつ、画像の削除もできるようになります。

チップ画像を挿入し、カーソル位置を調整する

後はこのdiv要素に対してチップ画像を挿入できるようにします。現在のカーソル位置の取得はdocument.getSelection()で取得したSelectionオブジェクトのgetRangeAtメソッドを使用します。これは選択した場所を教えてくれるオブジェクトで、DOMとoffset位置を教えてくれます。DOMはテキストも対象となっており、大体はテキストノードが選ばれることになります。


あるテキストノードの2文字目にカーソルがある

ただしチップ画像が連続していたりテキストノードが参照されないケースがあり、その時は1つ親のノードが選ばれます。この時のoffsetはchildNodesの番地を指しており、何番目のチップ画像に位置しているか判断することができます。


div要素に含まれている2番地のDOMにカーソルがある

なのでそれぞれのパターンで挿入するコードを書く必要があります。
挿入するとカーソル位置がずれてしまうので、Selectionオブジェクトのcollapseメソッドを使用して調整します。

テキストノードに挿入

テキストノードはsplitTextメソッドがあり、2つに分割することでその間にチップ画像を差し込むことができます。

const splittedNodeText = range.startContainer.splitText(range.startOffset);
// 分割された後ろのテキストノードの前に画像を差し込む
range.startContainer.parentNote.insertBefore(elImage, splittedNodeText);
// 逆に分割された前のテキストノードの後ろに画像を差し込んでも同じ
// range.startContainer.after(elImage);
// カーソル位置の調整
document.getSelection().collapse(splittedNodeText, 0)

それ以外

それ以外の時は選択しているDOMが分かるので、それの前に差し込みます。

const insertedBeforeNode = range.startContainer.childNodes[range.startOffset];
range.startContainer.insertBefore(elImage, insertedBeforeNode);
// カーソル位置の調整
document.getSelection().collapse(range.startContainer, range.startOffset + 1);

チップ画像をテキストに反映する

エディタ上でチップ画像を挿入しても最終的にはプログラムで解析できるようにテキストに変換する必要があります。これは単純にDOMツリーを解析して画像はあらかじめ決めておいたテキストに変換するだけでOKです。

function makeTextTag(elEditForm) {
  const makeTextInLine = (node) => {
    let text = '';
    node.childNodes.forEach((childNode) => {
      if (childNode instanceof HTMLImageElement) {
        text += '#{tag}';
        return;
      }
      if (childNode instanceof Text) {
        text += childNode.textContent;
        return;
      }
      if (childNode instanceof HTMLDivElement) {
        text += '\n' + makeTextInLine(childNode);
        return;
      }
    })
    return text;
  };
  return makeTextInLine(elEditForm);
}

終わりに

以上がチップ画像をテキストフォームに表示させる方法でした。contenteditableという普段使わなそうなものを使って比較的簡単にチップ画像も出せるテキストフォームを作ることができました。サンプルは簡易的なのでちゃんとする場合はもう少ししっかり作り込む必要がありますが、参考になれば幸いです。

参考記事

https://www.pg-fl.jp/program/jsdom/domtext.htm

Discussion