designModeを利用して、ブラウザ上でリッチテキストエディタ

ブラウザ上で高機能なテキストエディタを実現したい時、ブラウザに元々備わっているdesignModeを利用することができます。

iframeを用意

ページにテキストエディタとなるiframeを配置します。

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>デザインモード</title>
</head>
<body>
<iframe id="design-area" width="500" height="500"></iframe>
</body>
</html>


designModeをON

下記のようにdesignModeをONにするだけでテキストが入力できるようになります。

var designDoc = document.getElementById("design-area").contentDocument;
designDoc.designMode = "On";

f:id:any-programming:20180627131735p:plain

execCommand

designModeをONにしただけではテキスト入力できるだけです。(貼り付けでHTMLを貼り付けることは可能です。)

例えば太字にしたい、色を付けたい、などを実現するにはexecCommandを利用します。

下記は太字化の機能を加えたものとなります。

<button onclick="bold()">bold</button>
function bold() {
    designDoc.execCommand("bold");
}

f:id:any-programming:20180627133259p:plain

貼り付け時にテキストのみ貼り付ける

デフォルトでは貼り付けで、コピーした色々なHTMLを貼り付けることができます。

テキストだけを貼り付けたい時は、下記のようにonpaste関数を置き換えます。

改行文字は削除していますが、brに置き換えてもいいと思います。

bodyDoc.body.onpaste = function(ev) {
    ev.preventDefault();
    var text = ev.clipboardData.getData("text/plain");
    text = text.replace(/\r?\n|\r/g, "");
    bodyDoc.execCommand("insertHTML", false, text);
};


画像の挿入

一度画像をアップロードしてから、execCommandでURLを貼り付ける手順となります。

FormDataにデータを詰めて、XMLHttpRequestでサーバーにリクエストを飛ばします。

今回はサーバー側にupload-imageのpostが準備してあり、成功なら画像のURLが返ってくる状況を想定しています。

inputのonchangeでvalueをクリアして、おなじファイルを選んでもイベントが実行されるようにしています。

<input type="file" accept="image/jpeg" onchange="uploadImage(this.files); this.value = ''">
function uploadImage(files) {
    if (files.length !== 1) {
        return;
    }
    var request = new XMLHttpRequest();
    request.open("POST", "/upload-image");
    var data = new FormData();
    data.append("image", files[0]);

    request.onreadystatechange = function () {
        if (request.readyState === 4) {
            if (request.status === 200) {
                var imageUrl = request.responseText;
                designDoc.execCommand("insertImage", false, imageUrl);
            } else {
                alert("アップロードに失敗しました。");
            }
        }
    };

    request.send(data);
}


デフォルトの段落要素を変更

エディタ上で改行を行うと、Chromeならdivで一文が囲まれます。

一方IEだとpで囲まれます。

これを統一するのには下記を実行します。

designDoc.execCommand("DefaultParagraphSeparator", false, "div");

しかしChromeだと最初の一文がdivで囲まれなかったり、完全統一とはいかない様子です。

内容の取得

実際のHTMLテキストは下記でアクセスできます。

designDoc.body.innerHTML