HTML
この記事では、HTML要素の内容をブラウザ上で直接編集できるようにするcontenteditable属性の基本を説明します。
この記事でわかること
contenteditableで要素を編集可能にする方法- 編集状態・キーボード操作・フォーカスを利用者に分かりやすくする考え方
- 編集内容を保存するときの検証とサニタイズの注意点
It's an HTML attribute that allows an element to be treated like a directly editable text area within the browser.
contenteditable 属性
contenteditable 属性とは
概要
HTML要素に contenteditable="true" を指定すると、その要素内のテキストがブラウザ上で直接編集できるようになります。これは、ユーザーがWebブラウザ上で自由にテキストを変更できるインラインのエディタのような状態を作ることができるということです。
たとえば、下記のような要素をHTMLファイル内に書くと、該当要素をクリックして内容を編集できるようになります。
HTML
<div contenteditable="true" role="textbox" aria-label="編集可能なサンプル">
このテキストはブラウザ上で編集可能です。
</div>
属性値の種類
contenteditable には大きく分けて以下の2種類の値を指定できます。
contenteditable="true"- 要素内を編集可能にする
contenteditable="false"- 要素内の編集を禁止する
また、明示的に指定しない場合でも、JavaScriptを用いて動的に値を設定することで編集可能にできます(例:element.contentEditable = "true")。
インライン要素・ブロック要素への適用
どのような要素であっても、contenteditable 属性を付与すればその要素自体が編集可能領域になります。
<div>や<section>などのブロック要素でもOK<span>や<a>などのインライン要素でもOK
ただし、要素の見た目や振る舞いが大きく変わる可能性があるため、どの要素に編集機能を付けるか検討する必要があります。
基本的な使い方
最小限の例
最もシンプルな利用方法は、以下のように contenteditable="true" を直接HTML要素に記述することです。
HTML
<div contenteditable="true" role="textbox" aria-label="編集できるテキスト" style="border: 1px solid #ccc; padding: 10px;">
ここをクリックしてテキストを編集できます。
</div>
上記のように書くだけで、ユーザーは該当の要素をクリックし、テキストを直接編集できます。
編集内容の取得と表示
ユーザーが編集した内容を取得し、サーバーに送信したり、別の場所に表示したりしたい場合は、JavaScriptを利用して要素の innerHTML や textContent を取得できます。
HTML
<div id="editable" contenteditable="true" role="textbox" aria-label="編集対象のテキスト">
編集したいテキスト
</div>
<button id="showResult">編集内容を表示</button>
<pre id="resultArea" role="status" aria-live="polite"></pre>
<script>
const editable = document.getElementById('editable');
const resultArea = document.getElementById('resultArea');
const showResult = document.getElementById('showResult');
showResult.addEventListener('click', () => {
// innerHTMLで取得すると、HTMLタグ込みの文字列が得られる
// textContentで取得すると、テキストのみが得られる
const content = editable.innerHTML;
resultArea.textContent = content;
});
</script>
このようにすれば、編集後の内容を取得して、pre 要素に表示することができます。HTML構造まで含めて取得したい場合は innerHTML を使い、単純なテキストだけを取得したい場合は textContent を使うとよいでしょう。
よくある注意点・落とし穴
余計なHTML要素やスタイルの混入
contenteditable で編集すると、ユーザーがコピー&ペーストをしたり、リッチテキスト編集が有効になっているブラウザを使って入力したりすると、余計なHTMLタグやスタイル属性が混在することがあります。これはブラウザやユーザーの操作によって異なるため、意図しないタグが入り込んでしまう恐れがあります。
対策としては、入力内容をサーバーへ送信する際にHTMLのサニタイズを行う、またはクライアント側でHTMLをクリーニングする仕組みを用意するなどが挙げられます。
改行・空白の扱いの違い
ブラウザやOSによって、改行時に自動で <br> が入る場合や <div> が入る場合など、挙動が異なることがあります。想定外のマークアップが入り込む場合があるため、取り扱いには注意が必要です。
また、連続する空白はHTMLの仕様上、連続空白がまとめられてしまうケースがあるため、ユーザーが意図したとおりにテキストが整形されないこともあります。
モバイル端末の挙動
モバイル端末では、ソフトウェアキーボードの表示タイミングや、予測変換による入力支援などの結果が思わぬ形で反映されることがあります。PCと同じ感覚でテキスト編集エリアを提供しても、思い通りに動かないケースがあるため、事前のテストが重要です。
セキュリティ上の注意
ユーザーが自由にHTMLを入力できるということは、潜在的にXSS(クロスサイトスクリプティング)などのリスクが高まる可能性があります。編集した結果にスクリプトなどが埋め込まれていないか、送信前や表示前に適切に検証・サニタイズする必要があります。
応用的な機能と関連API
Document.execCommand() と document.designMode
contenteditable は、昔からある document.execCommand() API と組み合わせて利用されることが多かった機能です。しかし、execCommand() は現在非推奨となっており、将来的に廃止される可能性があります。
かつては、execCommand('bold') のように呼び出すことで、選択中のテキストを太字にするなどの簡易的なリッチテキストエディタを実装できました。今後はブラウザごとに代替のAPIが登場していく可能性がありますが、現時点では完全な代替となる標準APIは整備中の段階です。
Selection API と Range API
近年では、より低レベルなテキストの範囲指定を行うために、Selection API や Range API を使って操作する方法が推奨されています。ユーザーが選択したテキストを取得したり、その範囲を置換したりすることが可能です。
JavaScript
// 選択されているテキスト範囲を取得
const selection = window.getSelection();
if (selection.rangeCount > 0) {
const range = selection.getRangeAt(0);
// Rangeオブジェクトを用いて、選択範囲を囲む要素を作るなどの操作が可能
}
これらのAPIを活用することで、contenteditable 領域内のテキストをより柔軟に操作することができます。
Undo/Redo機能
contenteditable 自体にはUndo/Redoを行う機能がブラウザレベルで備わっています。ユーザーはキーボードショートカット(例えばCtrl+Z/Command+Zなど)で元に戻すことができますが、アプリケーション側で細かくUndo/Redoの挙動を制御するのは容易ではありません。
より高度な制御を行いたい場合、操作の履歴を自前で管理する仕組み(例えばSquireやProseMirrorなどのライブラリを利用)を用意して、テキスト編集の状態を監視しながらUndo/Redoを実装することが考えられます。
実装例:簡易リッチテキストエディタ
ここでは、contenteditable を活用した簡易リッチテキストエディタの例を紹介します。上記で述べたとおり、execCommand() は将来的に非推奨ですが、シンプルに理解する例としては有効です。
HTML
<div id="toolbar">
<button type="button" onclick="document.execCommand('bold')">太字</button>
<button type="button" onclick="document.execCommand('italic')">斜体</button>
<button type="button" onclick="document.execCommand('underline')">下線</button>
</div>
<div id="editor" contenteditable="true">
ここをクリックして編集開始
</div>
<script>
// シンプルな例につき、特別な処理は割愛
</script>
CSS
#toolbar {
margin-bottom: 10px;
}
#editor {
border: 1px solid #ccc;
min-height: 100px;
padding: 10px;
/* contenteditableによる入力に合わせてスタイルを整える */
}
上記のボタンをクリックしてテキストを選択している状態であれば、太字、斜体、下線の操作が可能です。現時点でのブラウザでは動作しますが、将来的には標準APIとして別のアプローチが推奨される見込みです。
より高度な編集機能を実現するには
専用ライブラリの利用
contenteditable だけでリッチなテキストエディタを実装するのは容易ではありません。ブラウザの標準挙動を網羅的に把握しなければならないからです。
実際には、既存の強力なエディタライブラリ(WYSIWYGエディタ)を利用することが多いです。たとえば次のようなものがあります。
これらのライブラリは、内部的には contenteditable の仕組みやSelection APIなどを活用しつつ、Undo/Redo管理やプラグインシステム、コピーペーストの制御、複雑なスタイリングなどを整備しており、実装者の負担を大きく軽減してくれます。
フォーカスの管理
複数の contenteditable 要素がある場合、どこにフォーカスがあるのかを管理することが重要です。フォーカスのある要素と選択範囲を適切に把握し、スタイリングやボタンのアクティブ状態を更新してユーザーにわかりやすくフィードバックを返す必要があります。
これはエディタライブラリにおいても大きなポイントであり、単純に contenteditable="true" の要素を増やすだけでは管理が複雑になるケースがあります。
マークダウンなど別形式への変換
HTMLのままテキストを扱うのではなく、マークダウンや独自の文章構造で管理したい場合があります。その場合は、編集が終わったタイミングでHTMLをパースして、所望のテキスト形式に変換する必要があります。
逆に、マークダウンをHTMLに変換して contenteditable 上で表示・編集し、保存時に再度マークダウン形式に変換するといったフローも考えられます。いずれにしても、変換ロジックを正しく実装することが求められます。
セキュリティ面の考慮
contenteditable は、ユーザーが自由にHTMLを入力できる仕組みのため、セキュリティリスクに注意しなければなりません。主に以下の点を考慮しましょう。
- XSS対策
- 悪意のあるユーザーがスクリプトタグやJavaScriptイベントハンドラなどを仕込む可能性があります。サーバーに送信されたデータをそのままHTMLとして再表示すると、XSS攻撃につながる恐れがあります。
- HTMLサニタイズ
- 入力内容を一度HTMLパーサを通し、危険なタグや属性を除去することでXSSを防ぐ方法が一般的です。フロントエンド側でもバックエンド側でもチェックを行う二重防御が望ましいです。
- コンテンツの制限
- 必要なタグだけを許可して、それ以外は強制的に削除、もしくはエンコードするというアプローチを取る場合もあります。これはホワイトリスト方式と呼ばれ、厳密に制御したい場合に有効です。
まとめ
contenteditableはHTML要素を簡易的なテキストエディタにできる便利な属性ですが、ブラウザ間での挙動の違いやリッチテキスト編集ゆえの複雑さが存在します。- 基本的な使い方としては、
<div contenteditable="true">のように指定し、その要素のinnerHTMLやtextContentを取得して処理します。 - リッチテキスト編集に際しては、過剰なタグやスタイルが混入すること、改行や空白の扱いなど、思わぬトラブルが発生しやすい点に注意が必要です。
- 高度な機能(Undo/Redo、フォーマット制御など)を実装する場合は、Selection API / Range API を活用するか、既存のWYSIWYGエディタライブラリを利用するのが一般的です。
- セキュリティ面では、XSSなどのリスクを十分に理解し、サニタイズ処理を行うことが不可欠です。