Slide 1

Slide 1 text

RTE ライブラリの仕組みと それを支えるブラウザ機能 2026.05.26 (火) #rich_text_editor_study 1

Slide 2

Slide 2 text

所属:サイボウズ株式会社 (26卒) 出身:札幌 最近の興味:家系・ポーカー 悩み 気合いを入れた名刺入れを購入したが 渡す機会がない 2 自己紹介 コサキン @karintou74073

Slide 3

Slide 3 text

自己紹介 (RTE) • インターンや内定者バイトで RTE に関わる • 移行先ライブラリの技術選定と PoC 作成に関わる • Zenn のリッチテキストエディタを非公式で開発 • Tiptap や Chromium(Blink)に小さくコントリビュート • 最近は Web 縦書きエディタを作ろうと戦っている 3

Slide 4

Slide 4 text

RTE の理解はまだまだ足りない 5 アクセシビリティ / 共同編集 / AI 活用 / ブラウザごとの仕様 / RTE ライブラリの内部実装 / 縦書き / RTL / Web 標準 / 理 想の RTE モデル / ブラウザの共通テスト / EditContext の活 用方法 / パフォーマンス / Mobile RTE / Native App RTE / RTE のテスト設計 ...

Slide 5

Slide 5 text

以下の雰囲気を理解して、関連技術に触れるときに思い出せるように この発表の目標 6 • RTE ライブラリがどのブラウザ機能を使い、何を補っているのか • Web の RTE 開発の概要 • RTE で独自機能を作る上で必要な最低限の知識 基本 探求

Slide 6

Slide 6 text

Web RTE 概要 7

Slide 7

Slide 7 text

シンプルなテキスト編集に加えて、文字の装飾や画像などリッチな 入力が可能になったエディタのこと リッチテキストエディタ(RTE)はなにもの? 8 エディタの種類は主に以下がある ・プレーンテキストエディタ ・コードエディタ ・WYSIWYG エディタ ( RTE? ) 用途や実装方法が色々あり、定義が難しい!!

Slide 8

Slide 8 text

©️ Cybozu, Inc. 実例を見てみよう 9

Slide 9

Slide 9 text

©️ Cybozu, Inc. 10 <input /> プレーンテキストエディタ • テキストデータを扱う • 高度なハイライトができない

Slide 10

Slide 10 text

©️ Cybozu, Inc. 11 VSCode (Monaco) Zenn (CodeMirror) コードエディタ • テキストデータを扱う • テキストを元に装飾が可能

Slide 11

Slide 11 text

©️ Cybozu, Inc. 12 Slack (Quill) WYSIWYG エディタ • テキスト・装飾・構造などをデータにもつ • ユーザーが任意の場所にテキスト装飾・ノード追加が可能 Closure Library

Slide 12

Slide 12 text

©️ Cybozu, Inc. 13 Notion(独自) Note (ProseMirror) WYSIWYG エディタ

Slide 13

Slide 13 text

意外と身近に RTE が溢れている 14

Slide 14

Slide 14 text

開発者目線の視点では? 15

Slide 15

Slide 15 text

基本的にライブラリを使うのが主流 • 最初から見た目も整っているエディタ:Quill, Mantine, CKEditor, TinyMCE • コア機能だけ提供して、見た目を柔軟にできるヘッドレスエディタ:Tiptap・Lexical RTE が出力する形式(HTML, JSON)を決めて、表示側で XSS などのセキュリティ対策をするだけ サービスに WYSIWYG エディタを導入したい!となったら? 16

Slide 16

Slide 16 text

©️ Cybozu, Inc. 17 Quill のシンプルなコード例

Slide 17

Slide 17 text

• RTE ライブラリの力を借りることで導入はだいぶ簡単! • 用意されたものを使うだけなら仕組みを理解しなくてもいいが、機能拡張は RTE ライブラリの構成要素 を把握する必要がある • メンション機能・テーブルの編集操作用 UI ・差分コードブロック・サービス固有のノード サービスに RTE 導入することは簡単? 18

Slide 18

Slide 18 text

• RTE とはシンプルなテキスト編集に加えて、文字の装飾や画像などリッチな入力が可能になったエディ タのこと • 用意された機能を使うだけなら仕組みを理解しなくてもいいが、機能拡張は RTE ライブラリの構成要素 を把握する必要がある 振り返り 19

Slide 19

Slide 19 text

RTE ライブラリで知っておくべき仕組み 20 機能拡張をするためには最低限何から理解すればいい?

Slide 20

Slide 20 text

ヘッドレスエディタの ProseMirror を参考に説明します。 他のエディタも細かいとこは違えど、似たイメージ(のはず。。。) RTE ライブラリの基本原則 21 • 表示で DOM Event が発火し、編集操作の Transaction に変換 • Transaction によって状態が更新 • 表示に反映する 表示 状態 操作 ブラウザとRTEの橋渡し ProseMirror の基本フロー

Slide 21

Slide 21 text

まずは状態 (EditorState) を知る 22

Slide 22

Slide 22 text

• ドキュメントはリッチテキストを表現するデータのこと • 骨格でツリー構造の Node と、テキスト装飾のフラット構造な Mark • Node には block と inline の2種類のタイプがある • HTML よりも編集で意味ある単位として定義することが可能 ドキュメントとは何か? 23 相互変換 ドキュメント HTML

Slide 23

Slide 23 text

• 開発者がドキュメントのスキーマを定義し、意図しない構造になることを防ぐ機能 • ドキュメントのセマンティックを守る / 統一的なスタイル / ブラウザ未対応の機能を使わない • 複雑なHTML構造で意図しない挙動を防げる • 最近は特定の構造を守りたいと思うことが大半のイメージ ドキュメントを守るスキーマ 24 Node を追加するときに定義するスキーマ • Node の一意な名前 • Node のグループ(block / inline) • 子 Node の(名前 / グループ)・個数・順番

Slide 24

Slide 24 text

スキーマ例 25 スキーマ 正しい 不正な構造 ルートの下に一つ以上の

, テキストノードを子に持つ

Slide 25

Slide 25 text

©️ Cybozu, Inc. Tiptap (ProseMirror) の実装例を見る 26

Slide 26

Slide 26 text

Tiptap で最も基礎的なコード 27 ・Tiptap は Node 単位で Extension を作る ・Document ... ルートノード(特殊) ・Paragraph ... p ノード ・Text ... テキストノード(特殊)

Slide 27

Slide 27 text

name, group, content がスキーマ部分 ドキュメントとHTMLを相互変換 •

→ paragraph Node に parse • paragraph Node →

に変換 • 0 は子ノードの renderHTML に委任 • 今回だとテキストノードをレンダリング Paragraph Node 28

Slide 28

Slide 28 text

• 意味単位で DOM を扱いたい • 編集操作では DOM が持つ編集文脈を考慮する必要がある • 「今の選択範囲はコードブロックの中にあるので、Enter は改行コードを入れる」など • テキスト装飾で顕著 • 下画像の縦棒の位置で Enter を押す → ブロックレベルで分離したい • DOM だと親の親を見ないと、段落の中にいることを認識できない • ドキュメントだと はマークなので、 paragraph block を分割すればいい(次ページ) なぜ DOM を直接扱わないのか?ドキュメントに抽象化する意味は? 30

Slide 29

Slide 29 text

©️ Cybozu, Inc. 31 ドキュメントの例

Slide 30

Slide 30 text

• 今どの位置を選択しているのかを示す • Web API では DOM で表現するが、ProseMirror では扱いやすいように絶対位置の番号が割り当て • 細かいところは割愛 ... 選択範囲(Selection) 32

Slide 31

Slide 31 text

状態 (EditorState) 33 ・ドキュメント ・選択範囲 ・ドキュメント ・選択範囲 操作 State 1 State 2 ・今いる位置から一文字後方削除 ・今いる位置に 「a」を挿入 ・位置 2 – 4 を削除 ...

Slide 32

Slide 32 text

状態 (EditorState) がわかった! 34

Slide 33

Slide 33 text

再掲:RTE ライブラリの基本原則 35 表示 状態 操作 ブラウザとRTEの橋渡し ProseMirror の基本フロー • 表示で DOM Event が発火し、編集操作の Transaction に変換 • 基本的な操作はライブラリが対応 • Transaction によって状態が更新 • 表示に反映する

Slide 34

Slide 34 text

• 他にも沢山の拡張要素がある • 特定キーを入力した時 • 特定の文字列を入力した時 • 編集画面のみ見た目を一部変えたい(テーブル編集UI) • プレースホルダーをノードの種類ごとに切り替えて表示したい • ブロック単位で DnD したい • Etc ... 他の拡張要素 36

Slide 35

Slide 35 text

• 「表示 → イベント → 操作 → 状態更新」 が基本フロー • 最初のとっかかりとして、状態を押さえよう!ドキュメントと選択範囲で構成される • 基本的な操作はライブラリが提供しているので、状態を定義すれば機能拡張可能 振り返り 37 表示 状態 操作 ブラウザとRTEの橋渡し

Slide 36

Slide 36 text

RTE ライブラリの ブラウザの機能を用いた実現方法 38

Slide 37

Slide 37 text

ブラウザの機能だけで実現できないの? 39 ・そもそも RTE を0から開発するには何が必要?

Slide 38

Slide 38 text

• HTML の属性の一つで、その要素の配下を編集可能にする • キャレット表示 / キー入力で状態操作 / 範囲選択 / 編集履歴 ... • 属性が取るべき値は HTML Living Standard に定義されているが、内部の編集ロジックは仕様がない • 例)Enter 押した時に何をする? • Web Editing WG では contenteditable の編集部分の標準化を進めず、 EditContext API に取り組んでいる Web RTE のコア:contenteditable とは 40

Slide 39

Slide 39 text

表示 • DOMの描画 • 現在選択中のキャレットと選択範囲を矩形で描画 そもそもブラウザがRTEを0から実装するためには? 41 入力 • キー入力(英数字・Enter・Backspace・矢印 ...) • クリック / 範囲選択 で Selection を更新する • IME / クリップボード / DnD これらの機能は、contenteditable を ON にするだけで対応できる! 状態 • DOM / Selection(DOMベース) 操作 ・Insert / Delete / Replace / MoveSelection ...

Slide 40

Slide 40 text

• スキーマ定義ができない • contenteditable は任意の HTML を許可してしまうため、構造化されたデータモデルが作れない • 操作ロジックの拡張が難しい • ブラウザの編集履歴を管理する API が無く、DOM 操作では履歴が保存されない • execCommand / queryCommandState 等の HTML 操作 API も非推奨 • ブラウザ間で挙動が揃わない • 特殊キー(Enter や Backspace 等)の処理がブラウザごとに異なる • 一つずつ吸収するより、ブラウザに処理をさせない方が楽 じゃあ contenteditable だけでよくね?→ 厳しい 42 中途半端なカスタマイズは困難。 DOM Event でフックし、ブラウザに編集をさせない方向へ

Slide 41

Slide 41 text

RTEライブラリが必要なのはわかった。 ブラウザとの分担は? 43

Slide 42

Slide 42 text

担当領域の違い (ProseMirror) 44 入力 キー入力 / Clipboard ... ブラウザ ProseMirror (JS) ・DOM Event ・DOM の差分反映 ・Selection API Transaction History 管理 状態 ・DOM (HTML) ・DOM Selection 表示 ・HTML/CSS ・キャレット ・選択ハイライト 状態 ・ドキュメント ・選択範囲 レンダリング ・Mutation Observer ・selectionchange handler ビルトインの操作 ・テキスト(IME) ・矢印キー ・Enter などの特殊キー ・NodeSelectionになる場合の移動など ブラウザと ProseMirror で双方向に状態を同期している

Slide 43

Slide 43 text

©️ Cybozu, Inc. データ反映:ProseMirror → ブラウザ 45

Slide 44

Slide 44 text

状態反映:ProseMirror → ブラウザ 46 入力 キー入力 / Clipboard ... ブラウザ ProseMirror (JS) ・DOM Event ・DOM の差分反映 ・Selection API Transaction History 管理 状態 ・DOM (HTML) ・DOM Selection 表示 ・HTML/CSS ・キャレット ・選択ハイライト 状態 ・ドキュメント ・選択範囲 レンダリング handler ブラウザと ProseMirror で双方向に状態を同期している ・Enter などの特殊キー ・preventDefault でブラウザに処理させない フロー図

Slide 45

Slide 45 text

状態反映:ProseMirror → ブラウザ 47 入力 キー入力 / Clipboard ... ブラウザ ProseMirror (JS) ・DOM Event ・DOM の差分反映 ・Selection API Transaction History 管理 状態 ・DOM (HTML) ・DOM Selection 表示 ・HTML/CSS ・キャレット ・選択ハイライト 状態 ・ドキュメント ・選択範囲 レンダリング handler ブラウザと ProseMirror で双方向に状態を同期している ・Enter などの特殊キー ・preventDefault でブラウザに処理させない フロー図

Slide 46

Slide 46 text

©️ Cybozu, Inc. データ反映: ブラウザ → ProseMirror 48

Slide 47

Slide 47 text

状態反映:ブラウザ → ProseMirror 49 入力 キー入力 / Clipboard ... ブラウザ ProseMirror (JS) ・DOM Event Transaction History 管理 状態 ・DOM (HTML) ・DOM Selection 表示 ・HTML/CSS ・キャレット ・選択ハイライト 状態 ・ドキュメント ・選択範囲 レンダリング ・Mutation Observer ・selectionchange handler ビルトインの操作 ・テキスト(IME) ・矢印キー 差分を検知 フロー図 handler ・preventDefault しない

Slide 48

Slide 48 text

状態反映:ブラウザ → ProseMirror 50 入力 キー入力 / Clipboard ... ブラウザ ProseMirror (JS) ・DOM Event Transaction History 管理 状態 ・DOM (HTML) ・DOM Selection 表示 ・HTML/CSS ・キャレット ・選択ハイライト 状態 ・ドキュメント ・選択範囲 レンダリング ・Mutation Observer ・selectionchange handler ビルトインの操作 ・テキスト(IME) ・矢印キー 差分を検知 フロー図 handler ・preventDefault しない

Slide 49

Slide 49 text

• 本来であれば ProseMirror → ブラウザの順方向だけにしたい • ブラウザ依存が減る / 状態反映が単方向のみでシンプル • しかし、現実は技術的にブラウザに頼る場面がいくつか存在する IME 入力 • 途中で DOM を変更すると中断されてしまうため、ブラウザに処理をさせる必要がある 矢印キー • 矢印キーによる上下移動はレイアウト依存のため、自前実装が非常に困難 • 例)width による改行がある段落だと、DOM から次の行を判断できない • bidirectional text の実装は厄介(LTR と RTL が混ざったテキスト) クリック / 範囲選択 なぜ逆方向の状態反映が必要なの? 51

Slide 50

Slide 50 text

ブラウザだけでは無理 しかし、RTE ライブラリだけでも厳しい 52

Slide 51

Slide 51 text

• 知的好奇心が大きいかもしれない • ブラウザ editing の強みと辛い箇所を知れる • ライブラリを作る側に回るときに役に立つかも? • 自分は深海まで潜れるぞという安心感がある ブラウザとRTEの役割分担を知って意味ある? 53

Slide 52

Slide 52 text

• ブラウザ担当の箇所を全て自前実装する必要が出てくる。厳しい • どこを追加実装必要? • HTML + CSS のレンダリングもどきを自前実装 • テキスト入力や移動周りのロジック • アクセシビリティ周り(そもそも自前実装可能なのか?) • 現状だと IME を使うには textarea にフォーカスを当てる必要があるため、ハックが必要 (余談) での実装は? 54

Slide 53

Slide 53 text

• contenteditable 単体では現代の要件を満たせない • RTEライブラリはブラウザに基本任せない方針だが、任せる領域も存在している • DOMやキャレットの描画 • IME 含むキー入力 まとめ 55

Slide 54

Slide 54 text

©️ Cybozu, Inc. 56