Reactでも使用できるWYSIWYGエディタ「Editor.js」を使ってみた
はじめに
みなさん、ブラウザで使えるWYSIWYGエディタ Editor.js
をご存知でしょうか?
私は国産のパーソナルデータストアOSS「Personium」の開発コミュニティに参加しているんですが、簡単な記事投稿アプリを作ってみたくなり、今回EditorJSについて調べてみましたので、ここに書いていきたいと思います。
WYSIWYGとは
一言でいえば「編集時に出力される見た目を直接いじるような編集感を得られるエディタ」でしょうか。イメージとしては Microsoft Word に近い操作感と考えてもらえればいいと思います。
ちょっとHTMLに詳しい人がいれば「contenteditableとの違いはなに?」となるかもしれませんが、contenteditableはブラウザの実装に依る部分が大きく、抽象化して扱いやすくしてくれているOSSはとても尊いものなのです(下記noteのエンジニアのnoteがとても参考になります)。
有名なOSSでは Draft.js
や Editor.js
といったものがありますが、今回は後者 「Editor.js」を取り扱っていきたいと思います。
Editor.js でできること
Editor.js は読んで字のごとく「エディタ」です。エディタなので文書をエディットすることができるんですが、操作感はすごく note.com のエディタに近いです。
公式サイトに書いてある特徴としては
とあり、それぞれ簡単に説明していくと…
ブロックスタイルエディタ
ブロックスタイルとは、つまり「ここは見出し」「ここは本文」「ここは図」といった形でブロックを挿入し、文書を形成していく方式です。
このブロックでは何をどう表示する、といったことを決め文章を書いていきます。
また、当然WYSIWYGがウリなので、それぞれのブロックがどのようなスタイルで将来レンダリングされるかを見ながら編集することができます。
JSON形式でデータを出力すること
Editor.js で出力されるデータ形式はだいたいこんな感じです。
{ "time": "<timestamp>", "blocks": [ "<block>" ], "version": "<version>" }
このように出力されたJSONを編集時と同じ方法でレンダリングすることでWYSIWYGが実現できるのです。
「JSONでなくHTMLで出してくれればいいのに」って思うこともあるかもしれませんが、WebアプリでHTMLを直接表示できる画面を作るのは誰しも避けて通りたい道かと思います。
拡張可能なシンプルなAPI
Editor.js はブロックのレンダリングや、ブロック内のインライン要素のレンダリングにプラグインを導入できる拡張性を持っています。上記リポジトリにはそういったサードパーティのOSSがまとまっていて、例えばコードブロックを挿入できるプラグインや、Youtube動画などをブロックに埋め込めるプラグイン、蛍光ペン的なマーカーを実現できるプラグインなどがあります。
- https://github.com/editor-js/code
- https://github.com/editor-js/embed
- https://github.com/editor-js/marker
Editor.jsのデメリット
そんなエディタがOSSで自分のサービスに導入できちゃう!すごい!!となりがちですが、Editor.js を SPA に組み込もうと思ったときに、ライフサイクルがReactのそれと異なっているのが課題となることがあります。
もしかすると他のOSSの方がレンダリングのパフォーマンスが高かったり、編集をハンドリングしやすかったりするかもしれません。それに対して自分は解を持ち合わせていません。
導入する
Reactのコンポーネントとはライフサイクルが異なるので、扱いづらい とはいえ、世の中にはReactで使用できるライブラリを提供してくれている人がいますので、今回はそれをお借りします。
使い方は簡単で、
import EditorJS from 'react-editor-js';
としてインポートしたうえで
<EditorJS data={data} onChange={handleChange} >
といった感じで使っていきます。
カスタムフックの作成
onChange
でステート更新を入れると再描画されてしまうので、useRef
で refを使うとか取り回しに考慮が必要になるため、ここらへんの検討事項はカスタムフックに固めてしまって使いまわせるようにしました。
import EditorJS from 'react-editor-js'; function useEditor(initialData, tools, editorId) { const editorInstanceRef = useRef(null); const getData = useCallback(async () => { return await editorInstanceRef.current.save(); }, []); const handleRef = useCallback((ref) => { editorInstanceRef.current = ref; }, []); const renderEditor = () => ( <EditorJS instanceRef={handleRef} enableReInitialize data={initialData} holder={editorId} tools={tools} > <div id={editorId} /> </EditorJS> ); return { getData, renderEditor }; }
initialData
には編集元のデータを入れ、tools
にはプラグインを指定します(後述)。editorId
は複数Editorが動くようにIDを指定できるようにしました(動作未確認)
そしてデータを取りに行くときは、getData
関数を経由して、EditorJS内部のインスタンスで save()
した結果を取得するようにしています。
プラグイン用のカスタムフックの作成
とりあえずMemo化して返すカスタムフックを作りましたが、、、意味あるのかコレ?
import Header from '@editorjs/header'; import Marker from '@editorjs/marker'; function useTools() { const tools = useMemo( () => ({ header: Header, marker: Marker, }), [] ); return { tools }; }
カスタムフックを使用する
上記で作成したカスタムフックを使用する部分です。メインのコンポーネントになります
export function EditorContainer() { const { tools } = useTools(); const [data, setData] = useState({ time: 0, blocks: [ { type: 'header', data: { text: 'initial header', level: 3, }, }, ], version: '', }); const { getData, renderEditor } = useEditor(data, tools, 'editor-main'); const handleClick = useCallback(() => { getData().then((dat) => setData(dat)); }, [setData, getData]); return ( <> <>{renderEditor()}</> <button onClick={handleClick}>push</button> <div>{JSON.stringify(data || {})}</div> </> ); }
ステート data
に初期値を入れ、カスタムフック useEditor
に渡します。渡した結果、renderEditor
という関数が返ってくるので、それで EditorJS
用のDOMを挿入します。(styleとかどうしよう)
そしてボタンを押したら、ステート data
に編集中のデータが格納され、表示されるようになっています。
結果
こんな感じにWYSIWYGなエディタを表示することができました。
マーカープラグインをしれっと導入してみましたが、ちゃんと使えています。
ReadOnlyモード
WYSIWYGでは、表示する際のスタイルと編集する際のスタイルが一致していないと当然見た目が変わってしまいます。そこで、EditorJSでは v2.19.0
より、「Read-onlyモード」が追加されています。
これはどういうものかというと、編集に使用したEditorJSを表示にも使用できるということで、表示用と編集用を別々に開発する必要がなくなる、ということです。これは画期的ですね。(当然、プラグインもそれに対応している必要があるんですが・・・)
終わりに
以上、EditorJSでした。「WYSIWYGを扱いたいけど、ナイーブにHTMLを扱うことはしたくないなぁ」という人や、「なんでもいいから簡単に拡張できるエディタを導入したい」という人がいたらぜひ試してみてはいかがでしょうか?
次回は、IndexedDBとEditorJSを組み合わせて、下書き機能のある記事投稿アプリについて記事にしてみたいと思います。
Personiumコミュニティについて
国産パーソナルデータストアOSS「Personium」のコミュニティでは、こういったWebアプリの開発なども行っています。パーソナルデータストア(PDS)を使うことで、どういうことが実現できるのかをイメージしやすくするアプリを作っていきたいと考えています。興味のある方がいらっしゃいましたら、ぜひお声がけください。