Edited at

30分で出来るDraft.js+React.js リッチエディタ作成入門

More than 3 years have passed since last update.


目的と概要

Draft.jsはReact.js上でリッチテキストエディタを作るためのフレームワークです。日本であまり流行ってない気がするので、始めやすいように入門記事を書いてみることにしました。

最近ではReact.jsを採用しているサイトも増えてきて、Webフロントエンドは更にリッチ化の流れが激しくなっています。

今回はReact.jsでリッチなテキストエディタを簡単につくれるDraft.jsを利用して30分でテキストエディタを作っていきます。


この記事で作れるもの

こんな感じのエディタ。すごいよくある感じのWYSIWYGエディタ。

screencast 2016-07-10 02-23-51.gif


手順


環境構築

React.js、Draft.jsの環境を作りましょう。

React.jsの環境を整えている方は

bash

npm install draft-js --save



で大丈夫です。

環境構築が面倒な方にはテンプレート用意しておきました

お納めください

mottox2/draft-js-bootstrap

webpack + webpack-dev-server + reactjsみたいな構成になっています。

npm install

npm run start

した後にlocalhost:4000にアクセスしてもらえれば大丈夫です。


Editorコンポーネントを配置する

Draft.jsが提供するのはリッチなエディタを作るためのコンポーネントとエディタの状態を管理するeditorStateです。

なので設置するだけで最低限のエディタとしての機能が提供されます。

import React from 'react'

import { render } from 'react-dom'

import {Editor, EditorState} from 'draft-js';

export default class App extends React.Component {
constructor(props) {
super(props)

this.state = {
editorState: EditorState.createEmpty(),
}
}

onChange(editorState) {
this.setState({editorState})
}

render() {
return <div>
<h1>Draft.js example</h1>
<Editor
editorState={this.state.editorState}
onChange={this.onChange.bind(this)}
/>
</div>
}
}

render(
<App/>,
document.getElementById('app')
)

この状態でindex.htmlを表示するとこんな感じで普段のTextareaと代わりないですね。

screencast 2016-07-10 01-39-52.gif

ここでeditorStateというものをdraft.jsからimportしていますが、これがEditorの状態を表すStateになります。

基本的にeditorStateが文章の状態、カーソルの管理、戻る・進むの履歴管理などを管理していきます。

これだけでは、リッチなエディタとは言えないのでスタイルを付けられるようにしていきます。


キーボードショートカットを追加

Cmd+bで太字、Cmd+iで斜体になるのはエディタに期待することの1つだと思います。

Draft.jsにはそういったコマンドを簡単に扱えるようになっています。

EditorコンポーネントにはhandleKeyCommand(command)というpropを渡せるようになっています。名前の通りキーボードが押された時に実行される関数を渡すことになっています。

ここでhandleKeyCommand(editorState, command)というcommandに対応した装飾を付加した新たなEditorStateを作ってくれる関数が提供されているので使ってみます。

コードはこんな感じ

import React from 'react'

import { render } from 'react-dom'

import {Editor, EditorState, RichUtils} from 'draft-js';

export default class App extends React.Component {
constructor(props) {
super(props)

this.state = {
editorState: EditorState.createEmpty(),
}
}

onChange(editorState) {
this.setState({editorState})
}

handleKeyCommand(command) {
const newState = RichUtils.handleKeyCommand(this.state.editorState, command)
if (newState) {
this.onChange(newState)
return true
}
return false
}

render() {
return <div>
<h1>Draft.js example</h1>
<Editor
editorState={this.state.editorState}
onChange={this.onChange.bind(this)}
handleKeyCommand={this.handleKeyCommand.bind(this)}
/>
</div>
}
}

render(
<App/>,
document.getElementById('app')

文字列選択してcmd+b, cmd+iとかでスタイルが変わります。

補足

EditorStateとcommandだけで装飾が行われるか疑問にもつ方もいるかもしれませんが、EditorState自体が現在選択しているカーソルの状態も管理しているので、選択範囲に特定のスタイルを適用するみたいな内部処理になっています。

これでキーボードショートカットも使えるエディタです。

ここまでだとcontenteditable属性のついた要素と一緒ですね。


スタイルを変更するボタンを作る

リッチなエディタの真骨頂だと思います。

太字ボタンを押したら太字に、斜体ボタンを押すと斜体になるボタンを作っていきます。

先ほど付けたキーボードショートカットと一緒でRichUtilが提供してくれる関数群の1つにtoggleInlineStyle(editorState, inlineStyle)というのがあります。

inlineのstyleをtoggleしてくれる名前通りの関数です。

ここで inlineStyleというのはStringで定義されています。

デフォルトで定義されているのはBOLD, CODE, ITALIC, STRIKETHROUGH, UNDERLINEの5つだと思っています。

Stringが入るように実装すればいいので実装すると以下のようになります。

全部書くと長いのでrenderだけ書きます。inlineでevent書いてますがお許しを…

   ...

render() {
return <div>
<h1>Draft.js example</h1>
<button onMouseDown={(e) => {
this.onChange(
RichUtils.toggleInlineStyle(this.state.editorState, 'BOLD')
)
e.preventDefault()
}}>Bold</button>
<button onMouseDown={(e) => {
this.onChange(
RichUtils.toggleInlineStyle(this.state.editorState, 'ITALIC')
)
e.preventDefault()
}}>Italic</button>
<Editor
editorState={this.state.editorState}
onChange={this.onChange.bind(this)}
handleKeyCommand={this.handleKeyCommand.bind(this)}
/>
</div>
}

コードを短くするためにrenderで直接処理を書いています。

補足

ここでonClickではなくonMouseDownを使っているのはonClickではボタンをクリックした時にボタンにフォーカスを奪われてしまい不快な動作になってしまいます。

onMouseDownでe.preventDefault()を行うことでカーソルを奪う動作を防ぎつつこちらの意図したスタイル変更を行うことが出来ます。


見出しをつける

ブログとか文章書くためなら見出しが欲しいと思います。

Draft.jsのQuickStartでは言及されていませんが、自分的には見出しは必須機能だと思うのでつけていきます。

先ほど付けたStyleがinlineStyleでインライン要素に対応するスタイルでした。

ブロック要素に対してはblockTypeという概念があってtypeによってh1, h2, code, figureなど判別していきます。

blockTypeを変更する方法としてtoggleBlockType(editorState, blockType)が提供されています。blockTypeもinlineStyleと同じでStringで渡すことになっています。定義はこのファイルにあります

先ほどと同様にボタンのクリックイベントとしてブロック要素が変更されるようにします。


render() {
return <div>
<h1>Draft.js example</h1>
<button onMouseDown={(e) => {
this.onChange(
RichUtils.toggleInlineStyle(this.state.editorState, 'BOLD')
)
e.preventDefault()
}}>Bold</button>
<button onMouseDown={(e) => {
this.onChange(
RichUtils.toggleInlineStyle(this.state.editorState, 'ITALIC')
)
e.preventDefault()
}}>Italic</button>
<button onMouseDown={(e) => {
this.onChange(
RichUtils.toggleBlockType(this.state.editorState, 'header-two')
)
e.preventDefault()
}}>H2</button>
<button onMouseDown={(e) => {
this.onChange(
RichUtils.toggleBlockType(this.state.editorState, 'unordered-list-item')
)
e.preventDefault()
}}>List</button>
<Editor
editorState={this.state.editorState}
onChange={this.onChange.bind(this)}
handleKeyCommand={this.handleKeyCommand.bind(this)}
/>
</div>
}

こんな感じ

screencast 2016-07-10 02-23-51.gif


まとめ

以上で最低限のWYSIWYGエディタが出来たのではないでしょうか?

結構簡単だと思ったと思います。

今回紹介したもの以外にもリッチエディタを簡単に作るために様々なものが準備されています。

今回の流れはDraft.jsのQuickStartの流れにブロック要素の扱いを加えたものです。

ざっくりしか説明していないので興味を持った方はちゃんと原文を読んでみてください。

Draft.js、使いやすいのでみんな使うべきだと思います。

完成品も一応アップしておきます