はじめに
contenteditable="true" を付けたdivを用意して、「よし、これでリッチテキストエディタが作れるぞ」と意気込んだことのある人、多いんじゃないでしょうか。私もその一人でした。
太字ボタンはdocument.execCommandで何とかなる。でもペーストしたときに変な書式が紛れ込む。IMEで日本語入力しているとカーソル位置がおかしくなる。Undo/Redoの挙動がブラウザごとに違う。気づけば、エディタの本質的な機能よりも、ブラウザ間の差異を吸収するコードの方が圧倒的に多くなっていました。
そこで行き着いたのがTinyMCEです。2000年代から続く老舗のWYSIWYGエディタで、Webサイトのブログ機能やCMSの管理画面など、あちこちで使われています。自前実装で消耗していた諸々の問題を、tinymce.init()を呼ぶだけで肩代わりしてくれます。
とはいえ、読むより触った方が早いと思います。実際にツールバー付きのエディタが動くサンプルを記事内に置いてあるので、先に挙動を見たい方はこちらからどうぞ。
TinyMCEとは
TinyMCEは、JavaScriptで実装されたブラウザ上で動くWYSIWYGリッチテキストエディタです。textareaやdiv要素を指定するだけで、太字・箇条書き・リンク・画像挿入といった一般的な編集機能を備えたエディタに変換してくれます。
GitHub上で16,000スターを超える人気プロジェクトで、開発元のTiny Technologies社によって継続的にメンテナンスされています。npmパッケージ版はGPLライセンスで公開されているオープンソースソフトウェアであり、無料で自前ホスティングして使えます(クラウド版のTiny Cloudは別途商用プランがあります)。
主な特徴
- 導入の手軽さ - スクリプトを読み込んで
tinymce.init({ selector: '#editor' })を呼ぶだけで、フル機能のエディタが立ち上がる - 豊富なプラグイン - 文字数カウント、画像アップロード、テーブル編集、コードブロックなど、公式プラグインだけで数十種類用意されている
- ツールバー・メニューの柔軟なカスタマイズ - 表示するボタンや項目を文字列指定で自由に組み替えられる
- React・Vue・Angular向けの公式バインディング -
@tinymce/tinymce-reactなどが提供されており、SPAにも組み込みやすい - IME・ブラウザ差異への対応 - 日本語入力やクロスブラウザの細かな挙動差を、ライブラリ側が吸収してくれる
インストール
npmもしくはyarnでインストールできます。
npm install tinymce
yarn add tinymce
Reactで使う場合は、公式バインディングも合わせて入れておくと便利です。
npm install tinymce @tinymce/tinymce-react
CDN経由で読み込む場合は、jsDelivrなどからバージョンを指定してスクリプトを読み込むだけで使えます(APIキー不要のself-hosted版)。
<script src="https://cdn.jsdelivr.net/npm/tinymce@8.8.2/tinymce.min.js"></script>
TinyMCEのサンプルを動かす
まずは実際に手を動かして、TinyMCEの編集体験を確かめてみましょう。以下のサンプルはtinymce.init()でツールバー付きのエディタをtextareaに適用し、editor.on('input', ...)で入力内容を検知して、右側にHTMLソースをリアルタイム表示するものです。
要点となるコードは次の部分です。selectorで対象のtextareaを指定し、setupコールバック内でエディタインスタンスにイベントリスナーを登録しています。
tinymce.init({
selector: '#editor',
toolbar: 'bold italic | bullist numlist | link',
menubar: false,
setup: (editor) => {
editor.on('input change', () => {
const html = editor.getContent();
document.getElementById('output').textContent = html;
});
},
});
実際に動かせるものが下です。左側のエディタで太字にしたり箇条書きを作ったりすると、右側のHTMLソースがその場で更新されます。試しに文章を打った後、ツールバーの「B」ボタンで一部を太字にしてみてください。
見てのとおり、エディタ上の見た目の編集操作が、そのまま裏側でHTML文字列として蓄積されていきます。toolbarの文字列からlinkを消せば、そのボタンだけがツールバーから消えることも確認できるはずです。
基本的な使い方
最小構成であれば、対象のtextareaやdivを指定してtinymce.init()を呼ぶだけです。
import tinymce from 'tinymce';
tinymce.init({
selector: '#mytextarea',
});
初期化が完了すると、指定した要素はTinyMCEのUIに置き換わります。エディタの内容を取得したいときは、アクティブなエディタインスタンスからgetContent()を呼び出します。
const content = tinymce.activeEditor.getContent();
console.log(content); // 編集後のHTML文字列
逆に、外部からエディタの内容を書き換えたい場合はsetContent()を使います。
tinymce.activeEditor.setContent('<p>初期文章です</p>');
実践的なユースケース
TinyMCEは設定オプションが非常に多く、用途に応じて挙動を細かく調整できます。ここでは代表的な3つのパターンを紹介します。
ツールバー・メニューのカスタマイズ
ブログ投稿フォームのように機能を絞りたい場面と、CMSの管理画面のようにフル機能を出したい場面とでは、必要なツールバーの構成が変わってきます。TinyMCEはtoolbarとmenubarのオプションに文字列を渡すだけで、表示するボタン・メニューを自由に組み替えられます。
toolbarに渡す文字列内の|はグループの区切りです。ボタン名を減らすほどツールバーはシンプルになり、menubarに指定したカテゴリを増やすほど上部のメニューが充実していくのが分かります。
プラグインで文字数カウントを表示する
投稿フォームでは「あと何文字書けるか」を出したいケースがよくあります。TinyMCEにはwordcountプラグインが標準で組み込まれており、editor.plugins.wordcountから現在の文字数・単語数を取得できます。
getCharacterCount()は文字数、getWordCount()は単語数を返します。上限の200文字を超えたときに数字が赤くなるよう分岐を入れているので、実際に長文を貼り付けて超過させてみると挙動が確認できます。
コンテンツをリアルタイムに取得して連携する
エディタの内容を保存ボタンだけで送信するのではなく、入力の都度どこかに反映させたい場面もあります(自動保存の下書き表示、別パネルへのプレビューなど)。editor.on('input', ...)とAPIのgetContent()を組み合わせれば、キー入力のたびに最新のHTMLを取得できます。
エディタ側の見出しや箇条書きが、下の「プレビュー」欄にそのままレンダリングされているのが分かるはずです。編集画面とプレビュー画面を分けたいCMSやブログエディタでは、こうした構成がそのまま実戦投入できます。
まとめ
contenteditableを自前でラップしてWYSIWYGエディタを作ろうとすると、ブラウザ差異の吸収やUndo管理など、想像以上に地味な作業に時間を取られます。TinyMCEを使えば、そのあたりを丸ごと任せて、ツールバー構成やプラグイン選定といった「アプリ側で本当に決めたいこと」に集中できます。
今回紹介したtoolbarのカスタマイズ、wordcountプラグイン、getContent/setContentによるコンテンツ連携は、いずれも実際のブログ投稿フォームやCMS管理画面でそのまま使える構成です。まずは最小構成のtinymce.init()から試して、必要なプラグインを一つずつ足していくのがおすすめです。
