HTML5テクニカルノート
Vue.js + ES6: 簡単なMarkdownエディタをつくる
- ID: FN1703004
- Technique: HTML5 / JavaScript
- Library: Vue.js 2.6.10
Vue.js公式サイトの「例」に、わずかなコードでつくられた「Markdown エディタ の例」があります。使っているライブラリやVueの構文についての説明はないので、いざつくってみようとするとつまづきそうです。そこで、解説を加えながら、少しずつ組み立ててみましょう。構文はECMAScript 2015(ECMAScript 6)を使いました(サンプル001)。
サンプル001■Vue.js + ES6: Markdown Editor Example
See the Pen Vue.js + ES6: Markdown Editor Example by Fumio Nonaka (@FumioNonaka) on CodePen.
01 データを要素にバインディングする
ページを左右ふたつに分けて、左にテキストを入力し、右にフォーマットして表示するレイアウトにします(図001)。<body>
要素には、つぎのように<textarea>
要素と<div>
要素をそれぞれ入力および表示の領域として、親の<div>
要素(id
属性"editor"
)に加えます。また、スタイルは以下のとおりです(「Markdown エディタ の例」のCSSをそのまま用いました)。
<body>要素<div id="editor"> <textarea>hello</textarea> <div></div> </div>
<style>要素html, body, #editor { margin: 0; height: 100%; font-family: 'Helvetica Neue', Arial, sans-serif; color: #333; } textarea, #editor div { display: inline-block; width: 49%; height: 100%; vertical-align: top; box-sizing: border-box; padding: 0 20px; } textarea { border: none; border-right: 1px solid #ccc; resize: none; outline: none; background-color: #f6f6f6; font-size: 14px; font-family: 'Monaco', courier, monospace; padding: 20px; } code { color: #f66; }
図001■レイアウトされたページ
JavaScritpコードは、つぎのようにVueクラスのインスタンスを定めます。引数のオブジェクトのプロパティel
はVue
オブジェクトが扱う要素で、<div>
要素のid属性("editor"
)を渡しました。data
プロパティには、オブジェクトで任意のデータを納めます。プロパティや属性をデータとバインディングするのがv-bind
ディレクティブです。Vue
インスタンスのデータ(input
)は、v-bind
ディレクティブで<textarea>
要素のプロパティにバインディングしました[*1]。これでデータが要素のテキストとして加えられます(図002)。
<script>要素const app = new Vue({ el: '#editor', data: { input: '# hello' } });
<body>要素<div id="editor"> <!--<textarea>hello</textarea>--> <textarea v-bind:value="input"></textarea> </div>
図002■データが<textarea>要素のテキストとして加えられた
[*1] <textarea>
要素にはvalue
という属性はありません。バインディングしているのは、JavaScriptが<textarea>
要素を扱うHTMLTextAreaElementオブジェクトのプロパティvalue
です(「公式チュートリアルから始めるVue.js vol.1『Markdown エディタ』」の「注意:textarea要素とvalue属性、textareaオブジェクトとvalueプロパティ」参照)。
02 テキストをMarkdownして表示する
「Markdown」は、テキストのフォーマットを定める簡易な記法です。HTMLコードよりも簡単な書き方で、文字や段落の表記が整えられます。そして、markedはMarkdownのテキストをHTMLコードに変えるJavaScriptライブラリです。markedをjsDelivrからつぎのように読み込んでおきます。
<head>要素<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
要素の子となるHTMLのコード(innerHTML
)を書き替えるにはv-html
を用います。値には算出プロパティ(compiledMarkdown
)を定めて与えることにします(「Vue.js + ES6入門 05: 項目を数えて表示する」02「条件に合ったデータを数えて返す」参照)。
<body>要素<div id="editor"> <!--<div>--> <div v-html="compiledMarkdown"></div> </div>
算出プロパティは、Vue()
コンストラクタに渡すオブジェクトのcomputed
オプションに加えます。getterとして働きますのでメソッドで定め、値を返さなければなりません。HTMLコードの要素(<h1>
)にデータ(input
)をテキストとして含めました。これで、データがHTMLコードとして解釈され、バインディングした要素に加えられます(図003)。
<script>要素const app = new Vue({ computed: { compiledMarkdown() { return '<h1>' + this.input + '</h1>'; } } });
図003■データがHTMLコードとして右の領域に加えられた
HTMLコードが正しく解釈されて要素の子に加えられることが確かめられましたので、marked()
関数を使って書き替えます。引数に渡すのはMarkdownされたテキストです。HTMLコードが返されますので、算出プロパティ(compiledMarkdown
)の戻り値とします。これでMarkdownしたテキストがHTMLコードのフォーマットで示されます(図004)。ここまでの<body>
要素の記述を、以下のコード001にまとめました。
<script>要素const app = new Vue({ computed: { compiledMarkdown() { // return '<h1>' + this.input + '</h1>'; return marked(this.input); } } });
図004■MarkdownしたテキストがHTMLフォーマットで表示される
コード001■MarkdownしたテキストをHTMLのフォーマットで別の要素に表示する
<body>要素
<div id="editor">
<textarea v-bind:value="input"></textarea>
<div v-html="compiledMarkdown"></div>
</div>
<script>
const app = new Vue({
el: '#editor',
data: {
input: '# hello'
},
computed: {
compiledMarkdown() {
return marked(this.input);
}
}
});
</script>
03 テキストの入力に応じてMarkdownしたテキストをフォーマットする
v-on
ディレクティブは、コロン(:
)のあとに引数として添えたイベントにリスナーを定めます。input
は<textarea>
要素の値が変更された場合に起こるイベントです。つまり、リスナーはテキストがひと文字書き替わるたびに呼び出されます。
<body>要素<div id="editor"> <textarea v-bind:value="input" v-on:input="update"></textarea> </div>
イベントリスナー(update()
)は、Vue
オブジェクトのmethods
オプションにメソッドとして加えます。引数から得たEvent.target
プロパティはイベントが起こった<textarea>
要素を参照しますので、入力したテキストを取り出すのはvalue
プロパティです(前述注[*1]参照)。そのテキストでVue
オブジェクトのデータ(input
)を書き替えます。
<script>要素const app = new Vue({ methods: { update(eventObject) { this.input = eventObject.target.value; } } });
これで左(<textarea>
要素)にMarkdownで入力したテキストが、入力に応じて右(<div>
要素)にHTMLのフォーマットで表示されます(図004)。Markdownのエディタとして最低限の動きはできたといえるでしょう。<body>
要素の記述は、以下のコード002のとおりです。
図004■Markdownしたテキストが入力に応じてフォーマットされる
コード002■Markdownしたテキストのキー入力に応じてフォーマットする
<body>要素
<div id="editor">
<textarea v-bind:value="input" v-on:input="update"></textarea>
<div v-html="compiledMarkdown"></div>
</div>
<script>
const app = new Vue({
el: '#editor',
data: {
input: '# hello'
},
computed: {
compiledMarkdown() {
return marked(this.input);
}
},
methods: {
update(eventObject) {
this.input = eventObject.target.value;
}
}
});
</script>
04 処理を一定時間分まとめて行う
テキストを続けざまに入力したり削除しているとき、ひと文字ごとにフォーマットし直すのは、負荷が無駄に増えます。表示は少し遅れても、キー入力をある程度まとめて処理した方が効率的です。ユーティリティライブラリLodashを使えばそのように組み立てられます。このライブラリをやはりjsDelivrからつぎのように読み込んでください。
<head>要素<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.15/lodash.min.js"></script>
用いるメソッドは_.debounce()
です。第1引数の関数は、第2引数の時間(ミリ秒)遅れて実行されます。そして、その間メソッドが繰り返し呼び出された場合でも、関数の処理は1回にまとめられるのです。
_.debounce(関数, 時間)
前掲コード002でテキスト入力時(input
イベント)のリスナーメソッド(update()
)に、_.debounce()
をつぎのように組み込みます。
<script>要素const app = new Vue({ methods: { update: _.debounce(function(eventObject) { this.input = eventObject.target.value; }, 1000) } });
Vue.jsではもっともよく使われるふたつのディレクティブv-bind
とv-on
については省略した書き方ができます。つぎのように、v-bind
はコロン:
のみで済み、v-on
はイベントの前に@
を添えるだけです。書き上がった<body>
要素の記述は、以下のコード003にまとめました。
<body>要素<div id="editor"> <!--<textarea v-bind:value="input" v-on:input="update"></textarea>--> <textarea :value="input" @input="update"></textarea> </div>
コード003■テキスト入力を少しずつまとめてMarkdownする
<body>要素
<div id="editor">
<textarea :value="input" @input="update"></textarea>
<div v-html="compiledMarkdown"></div>
</div>
<script>
const app = new Vue({
el: '#editor',
data: {
input: '# hello'
},
computed: {
compiledMarkdown() {
return marked(this.input);
}
},
methods: {
update: _.debounce(function(eventObject) {
this.input = eventObject.target.value;
}, 1000)
}
});
</script>
05 サニタイジングする
公式サイト「Markdown エディタ の例」では、marked()
メソッドの呼び出しで、第2引数のオブジェクトにsanitize
プロパティをtrue
にして与えています。これはサニタイジングするためです。
<script>要素compiledMarkdown: function () { return marked(this.input, { sanitize: true }) }
sanitize
オプションがないと、テキストにタグを含めてしまうことができます。たとえば、属性onclick
にJavaScriptコードを書けば実行されてしまうのです(図005)。悪意のある攻撃を受ける危険が生じます。
図005■要素にonclick属性にJavaScriptコードを加えてクリックすると実行される
sanitize
オプションをtrue
に定めれば、タグを一般的な文字列に変える(エスケープする)ため、危険が防げるのです。けれど、marked 0.7.0からsanitize
オプションは、推奨されなくなりました。
図006■sanitizeオプションを有効にするとタグがエスケープされる
サニタイジングというにはエスケープだけでは十分とはいえません。そのため、専用のライブラリをつかうことが勧められています。そのひとつがDOMPurifyです。使い方は簡単で、ライブラリを読み込んでDOMPurify.sanitize()
メソッドにテキストを渡せば、サニタイジングして返されます。改めて、コード全体を掲げることはしません。冒頭のサンプル001には含めてありますので、そちらをご覧ください。
<head>要素<script>要素<script src="https://cdn.jsdelivr.net/npm/dompurify@2.0.7/dist/purify.min.js"></script>
const app = new Vue({ computed: { compiledMarkdown() { const sanitized = DOMPurify.sanitize(this.input); // return marked(this.input); return marked(sanitized); } }, });
作成者: 野中文雄
更新日: 2019年11月21日 構文をECMAScript 2015に改め、本文は最新情報に合わせて加筆・補正した。
作成日: 2016年03月24日
Copyright © 2001-2019 Fumio Nonaka. All rights reserved.