noteと"contenteditable"

初めてエンジニアらしい記事を書きます。
記念すべき?初回はHTML5のcontenteditableのお話です。

contenteditable

コンテントエディタブル、と読みます。content editable。
こいつは何なのか、超ざっくり言ってしまうと、HTMLタグ内のテキストを編集可能にしてくれる属性、です。百聞は一見にしかずで例を出してみます。

<p contenteditable=true>
    コンテントエディタブル
</p>

これをブラウザで表示すると、pタグの中身が編集可能(キャレットを合わせて、キーボード入力可能)な状態になります。一般的にHTMLの世界でテキストを入力する手段といえば、inputやtextareaを思い浮かべるのではないかと思いますが、通常表示するだけのpに対してもテキスト入力可能にしてくれるわけですね。

前置きはここまでにして、お察しの通りですが、noteでテキストノートを書く際に使うエディタも、このcontenteditableを利用しています。
私の専らの担当がこのエディタの開発になるわけですが、初版を出してはや1年半経ちました。この間の積もる話や変遷なんかはまた別の記事で書かせて(吐露させて)いただければと思います。

エディタ、細かく言うとWYSIWYG(ウィジウィグ)エディタという分類になります。MS Wordなんかと同様、入力編集しながら出来映えが見られるエディタ、という事です。
先述のtextarea等ではなく、何故このcontenteditableを利用しているかというと、まさにこのWYSIWYGである事が大きな理由です。文章を書きながら最終的に表示される見た目が確認できるので、プレビュー画面のような機能が要りません。例えばmarkdown等のマークアップ言語ですと、利便性を考えた場合、同時に反映されるプレビューの実装が必須になります。
ユーザーは書きながら見た目を確認でき、開発側はプレビュー実装が不要である、このメリットは大きいですね。

noteにおけるcontenteditable

では実際にnoteのcontenteditable部分はどうなってるのかを見てみます。細かい所は端折ってますが、テキストノート本文のdom構造はこのようになっています。

<!-- note本文フォーマット -->
<div id="note-body" data-placeholder="ノート本文" contenteditable="true">
    <h3>見出し</h3>
    <p>本文段落</p>
    <p>本文<b>強調</b>段落</p>
    <blockquote>引用段落</blockquote>
    <pre><code>pre段落</code></pre>
    <figure></figure>
</div>

contenteditable属性はガワであるdivに付与し、内部に各段落要素(h3、p、blockquote、pre、figure)が存在するという形です。段落として使えるのは今の所、この5種類のみ(201810現在)となっています。
contenteditable指定は、対象要素だけでなく、ネストされている子要素にも有効なので、各段落要素も編集可能になっているわけです。

また、段落種別以外にも、本文フォーマットには以下のようなルールがあります。

・ガワdiv直下にテキストは書けない
・ガワdiv直下にインライン要素(a、b、spanなど)は配置できない
・ガワdiv直下に画像は配置できない
・段落内段落(段落要素のネスト)は出来ない
・使えない要素はだめ

NGパターンをまとめて例示するとこんな感じです。

<!-- NGフォーマット例 -->
<div id="note-body" data-placeholder="ノート本文" contenteditable="true">
    直下にテキスト
    <span>直下にインライン</span>
    <img src="直下に画像.png">
    <p>
        <p>段落ネスト</p>
    </p>
    <script>console.log('使えない要素');</script>
</div>

このようなイレギュラーフォーマットになってしまうと、正しくstyleが効きず、見た目が著しく崩れますし、有料ノートにしようと思っても有料ラインの設定が出来ません。有料ライン設定機能は、本文が正しいフォーマットで出来上がっている事が前提になっています。
見た目や機能面以外のセキュリティの観点からも、使える要素を想定内に制限しておく事は、XSS等に対する脆弱性対策の意味でも非常に重要になってきます。

エディタの役割・使命はまずは、テキストを入力して意図通りに本文を作成できる事(それも可能な限り快適に)ですが、システム的観点からいくと、「サーバーに送信可能な正しいフォーマットの本文データを作成すること」になります。

デフォルトのcontenteditable

上記の種々のフォーマットルール、これらをデフォルトの状態(divにcontenteditable="true"をつけただけ)で実現できるでしょうか?
明白だと思いますが、結論から言うともう全くもって圧倒的に不可能です。(そもそもそれでいけるならエディタなんぞ要らんわけですよね…)

素のcontenteditableがどのような塩梅か知っていただくだめに、挙動を再確認しようと、noteのテキストノート作成画面を模したstaticページを用意しました。

<!DOCTYPE html>
<head>
    <link rel="stylesheet" href="https://d2l930y2yx77uc.cloudfront.net/assets/application-ccd00b3d152a3b95b5572b33bbed04d49f5244fe86c67b97150a0c92fcd2fad2.css">
</head>
<body class="ns-note bg-white">
<div class="clearfix space--under-header">
    <div id="note-editor-screen">
        <div class="note-container space--under-header">
            <div class="fude-content note-new-edit">
                <div class="note">
                    <div class="note-body">
                        <div id="note-body" class="body-wrapper editable" data-placeholder="ノート本文" contenteditable="true">
                            <h3>見出し</h3>
                            <p>段落</p>
                            <blockquote>引用</blockquote>
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

scriptは一切読み込んでいない、素の状態のcontenteditableです。
ここで実際に幾つかの簡単なテキスト編集をやってみましょう。エディタがサポートしているブラウザ、Chrome、Safari、Firefox、Edgeでそれぞれ挙動を見てみます。

1. テキスト入力

これは問題ないですね。どのブラウザでも、p、h3、blockquoteへ問題なくテキスト入力出来ます。正直、この時点で各ブラウザ間で挙動がバラバラなようだと匙を投げたくなってしまいます…。

2. 改段落

このへんから雲行きが怪しくなってきます。
段落内でEnterキーを入力すると、新たに次の段落を作成し、キャレットを移動させます。この時、作成される新段落は必ずpになるのがnoteの仕様です。
これが素の状態だとどうなるか…

・pの中で改段落
どのブラウザでも次段落はpになります。セーフですね。

・blockquoteの中で改段落
Chrome、Safariでは次段落はblockquoteになります。

Firefoxはどうなるのか。こうなります。

改段落になりません。段落内改行になります。pre段落に入力した時と同じような挙動です。ではどうやって改段落すればよいか。
分かりません!

※ noteのエディタではpre段落末尾で2回enterを入力する(末尾の未入力行でenter)事により改段落できるようになっています。

次にEdgeではどうか。

??
改段落にはなっていない。Firefoxと同様、改行になっているようにも見えるが、なにか違う。DOMを確認してみると…

…blockquote内にdivが。元々あった"引用"のテキストもdivで囲われています。どうもblockquote内で子段落を作ってしまったような挙動に見えます。

Firefoxは改行、Edgeは独自の挙動となりました。
Foxの改行はまだ分からんでもないですが、Edgeの挙動はちょっと先を行き過ぎて?いますね。名は体を表すということなのか。数あるEdgeの闇の一端をここから感じ取る事ができます。

・H3の中で改段落
ChromeとSafariの挙動(他2つは置いときましょう…)から、どうやら今の段落と同じ要素が作られるのではないかなという予想が立ちます。
H3が出来そうな気がしてきますよね。
しかし、そうは問屋が卸さない。

改段落は出来て、何かしらの段落が出来ました。
ただ、そこに入力してみると明らかに見出しH3ではない。
ではpになったのか?残念ながらそうでもないのです。

…divです。
どのブラウザでやってもこうなります。pやblockquoteとの差は何なのか、H3は見出しだから改段落によって複数できるのはおかしいという事なのか、もうこれがcontenteditableの素の仕様であると理解する他ありません。

3. 段落内改行

改段落に対して段落内の改行をやってみます。
Shift + Enterキー入力により、素の状態のcontenteditableでも改行してくれる、事を期待しますよね。

この通り、Safari以外のブラウザでは段落内で問題なく改行が出来ます。
ではSafariはどうなるのかというと、改段落になります
入力対象の段落がh3だろうがpだろうが関係なく、SafariではShift + Enterによる改行が効きません。

4. イレギュラー

最後に、フォーマット違反になる本文を予め用意してどうなるか、Chromeで見てみます。

<div id="note-body" data-placeholder="ノート本文" contenteditable="true">
    イレギュラーテキスト
    <h3>見出し</h3>
    <p>段落</p>
    <blockquote>引用</blockquote>
</div>

divの直下にテキストを配置しました。

それとなく表示できており、編集も出来ます。
ここでEnter(改段落)するとどうなるか。

改段落によって出来たdivに、元々あった段落達が放り込まれました。もはやnoteフォーマットの面影は無いですね。これはChromeでの例ですが、他のブラウザでもフォーマットが壊れる結果になるのは同じです。

contenteditable対象div直下にテキストが出来てしまうという事象は、想定外ではありますが起こりえないとも言い切れません。こういうケースで正常フォーマットに修正する機能が無いという事は致命的です。

まとめ

以上、デフォルトのcontenteditableがどのようなものなのか簡単なケースを使って見てみました。これら簡単な操作例だけでも、そのままではとても使えない、圧倒的不可能であるという事が分かっていただけたのではないかなと思います。

・改段落で想定しない要素が出来る場合がある
・特定ブラウザで挙動が違う
・想定外フォーマットの対応

エディタの機能は多岐に渡りますが、まず最も重要な基本機能は、今回挙げたこれら事象を含む、あらゆるテキスト入力操作をクロスブラウザで差異・問題なく行えるようにすること、です。この土台が安定しないと、装飾やembed等の高級機能の実装も覚束ないですから。
contenteditableは簡単にWYSIWIGを実現してくれる便利なものではありますが、「テキストが入力できる」を超える部分、システム仕様へのマッチング、ブラウザ間で動作共通化、これらは地道に作り込んでいく他ないんですね。

最後に余談になりますが、記事にも書いた対応ブラウザ4種について、開発側として利用推奨する順位はこのようになります。
Chrome > Safari・Firefox >>> Edge

Edgeはblockquoteの項目でも触れましたが、非常に闇が多く、Windowsデフォルト搭載ブラウザという事で対応させ続けているものの、なかなか苦労が耐えません…。

そんなEdgeの苦労や、エディタの細かい機能の話等、また書いていければと思います。

今回の検証に使ったブラウザのバーション
・Google Chrome 69.0.3497.100
・Firefox 62.0.3
・Safari 12.0
・Microsoft Edge 42.17134.1.0



この記事が気に入ったら、サポートをしてみませんか?気軽にクリエイターを支援できます。

ありがとうございます!
95

ct8ker

エディタの話

noteテキストエディタについての話
5つのマガジンに含まれています
コメントを投稿するには、 ログイン または 会員登録 をする必要があります。