Upgrade to Pro — share decks privately, control downloads, hide ads and more …

RTE ライブラリの仕組みとそれを支えるブラウザ機能

Avatar for kosakin kosakin
June 21, 2026
650

RTE ライブラリの仕組みとそれを支えるブラウザ機能

Avatar for kosakin

kosakin

June 21, 2026

Transcript

  1. 自己紹介 (RTE) • インターンや内定者バイトで RTE に関わる • 移行先ライブラリの技術選定と PoC 作成に関わる

    • Zenn のリッチテキストエディタを非公式で開発 • Tiptap や Chromium(Blink)に小さくコントリビュート • 最近は Web 縦書きエディタを作ろうと戦っている 3
  2. RTE の理解はまだまだ足りない 5 アクセシビリティ / 共同編集 / AI 活用 /

    ブラウザごとの仕様 / RTE ライブラリの内部実装 / 縦書き / RTL / Web 標準 / 理 想の RTE モデル / ブラウザの共通テスト / EditContext の活 用方法 / パフォーマンス / Mobile RTE / Native App RTE / RTE のテスト設計 ...
  3. ©️ Cybozu, Inc. 10 <textarea /> <input /> プレーンテキストエディタ •

    テキストデータを扱う • 高度なハイライトができない
  4. ©️ Cybozu, Inc. 11 VSCode (Monaco) Zenn (CodeMirror) コードエディタ •

    テキストデータを扱う • テキストを元に装飾が可能
  5. ©️ Cybozu, Inc. 12 Slack (Quill) WYSIWYG エディタ • テキスト・装飾・構造などをデータにもつ

    • ユーザーが任意の場所にテキスト装飾・ノード追加が可能 Closure Library
  6. ヘッドレスエディタの ProseMirror を参考に説明します。 他のエディタも細かいとこは違えど、似たイメージ(のはず。。。) RTE ライブラリの基本原則 21 • 表示で DOM

    Event が発火し、編集操作の Transaction に変換 • Transaction によって状態が更新 • 表示に反映する 表示 状態 操作 ブラウザとRTEの橋渡し ProseMirror の基本フロー
  7. • ドキュメントはリッチテキストを表現するデータのこと • 骨格でツリー構造の Node と、テキスト装飾のフラット構造な Mark • Node には

    block と inline の2種類のタイプがある • HTML よりも編集で意味ある単位として定義することが可能 ドキュメントとは何か? 23 相互変換 ドキュメント HTML
  8. • 開発者がドキュメントのスキーマを定義し、意図しない構造になることを防ぐ機能 • ドキュメントのセマンティックを守る / 統一的なスタイル / ブラウザ未対応の機能を使わない • 複雑なHTML構造で意図しない挙動を防げる

    • 最近は特定の構造を守りたいと思うことが大半のイメージ ドキュメントを守るスキーマ 24 Node を追加するときに定義するスキーマ • Node の一意な名前 • Node のグループ(block / inline) • 子 Node の(名前 / グループ)・個数・順番
  9. Tiptap で最も基礎的なコード 27 ・Tiptap は Node 単位で Extension を作る ・Document

    ... ルートノード(特殊) ・Paragraph ... p ノード ・Text ... テキストノード(特殊)
  10. name, group, content がスキーマ部分 ドキュメントとHTMLを相互変換 • <p> → paragraph Node

    に parse • paragraph Node → <p> に変換 • 0 は子ノードの renderHTML に委任 • 今回だとテキストノードをレンダリング Paragraph Node 28
  11. • 意味単位で DOM を扱いたい • 編集操作では DOM が持つ編集文脈を考慮する必要がある • 「今の選択範囲はコードブロックの中にあるので、Enter

    は改行コードを入れる」など • テキスト装飾で顕著 • 下画像の縦棒の位置で Enter を押す → ブロックレベルで分離したい • DOM だと親の親を見ないと、段落の中にいることを認識できない • ドキュメントだと <b> はマークなので、 paragraph block を分割すればいい(次ページ) なぜ DOM を直接扱わないのか?ドキュメントに抽象化する意味は? 30
  12. 状態 (EditorState) 33 ・ドキュメント ・選択範囲 ・ドキュメント ・選択範囲 操作 State 1

    State 2 ・今いる位置から一文字後方削除 ・今いる位置に 「a」を挿入 ・位置 2 – 4 を削除 ...
  13. 再掲:RTE ライブラリの基本原則 35 表示 状態 操作 ブラウザとRTEの橋渡し ProseMirror の基本フロー •

    表示で DOM Event が発火し、編集操作の Transaction に変換 • 基本的な操作はライブラリが対応 • Transaction によって状態が更新 • 表示に反映する
  14. • 「表示 → イベント → 操作 → 状態更新」 が基本フロー •

    最初のとっかかりとして、状態を押さえよう!ドキュメントと選択範囲で構成される • 基本的な操作はライブラリが提供しているので、状態を定義すれば機能拡張可能 振り返り 37 表示 状態 操作 ブラウザとRTEの橋渡し
  15. • HTML の属性の一つで、その要素の配下を編集可能にする • キャレット表示 / キー入力で状態操作 / 範囲選択 /

    編集履歴 ... • 属性が取るべき値は HTML Living Standard に定義されているが、内部の編集ロジックは仕様がない • 例)Enter 押した時に何をする? • Web Editing WG では contenteditable の編集部分の標準化を進めず、 EditContext API に取り組んでいる Web RTE のコア:contenteditable とは 40
  16. 表示 • DOMの描画 • 現在選択中のキャレットと選択範囲を矩形で描画 そもそもブラウザがRTEを0から実装するためには? 41 入力 • キー入力(英数字・Enter・Backspace・矢印

    ...) • クリック / 範囲選択 で Selection を更新する • IME / クリップボード / DnD これらの機能は、contenteditable を ON にするだけで対応できる! 状態 • DOM / Selection(DOMベース) 操作 ・Insert / Delete / Replace / MoveSelection ...
  17. • スキーマ定義ができない • contenteditable は任意の HTML を許可してしまうため、構造化されたデータモデルが作れない • 操作ロジックの拡張が難しい •

    ブラウザの編集履歴を管理する API が無く、DOM 操作では履歴が保存されない • execCommand / queryCommandState 等の HTML 操作 API も非推奨 • ブラウザ間で挙動が揃わない • 特殊キー(Enter や Backspace 等)の処理がブラウザごとに異なる • 一つずつ吸収するより、ブラウザに処理をさせない方が楽 じゃあ contenteditable だけでよくね?→ 厳しい 42 中途半端なカスタマイズは困難。 DOM Event でフックし、ブラウザに編集をさせない方向へ
  18. 担当領域の違い (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 で双方向に状態を同期している
  19. 状態反映:ProseMirror → ブラウザ 46 入力 キー入力 / Clipboard ... ブラウザ

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

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

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

    ProseMirror (JS) ・DOM Event Transaction History 管理 状態 ・DOM (HTML) ・DOM Selection 表示 ・HTML/CSS ・キャレット ・選択ハイライト 状態 ・ドキュメント ・選択範囲 レンダリング ・Mutation Observer ・selectionchange handler ビルトインの操作 ・テキスト(IME) ・矢印キー 差分を検知 フロー図 handler ・preventDefault しない
  23. • 本来であれば ProseMirror → ブラウザの順方向だけにしたい • ブラウザ依存が減る / 状態反映が単方向のみでシンプル •

    しかし、現実は技術的にブラウザに頼る場面がいくつか存在する IME 入力 • 途中で DOM を変更すると中断されてしまうため、ブラウザに処理をさせる必要がある 矢印キー • 矢印キーによる上下移動はレイアウト依存のため、自前実装が非常に困難 • 例)width による改行がある段落だと、DOM から次の行を判断できない • bidirectional text の実装は厄介(LTR と RTL が混ざったテキスト) クリック / 範囲選択 なぜ逆方向の状態反映が必要なの? 51
  24. • ブラウザ担当の箇所を全て自前実装する必要が出てくる。厳しい • どこを追加実装必要? • HTML + CSS のレンダリングもどきを自前実装 •

    テキスト入力や移動周りのロジック • アクセシビリティ周り(そもそも自前実装可能なのか?) • 現状だと IME を使うには textarea にフォーカスを当てる必要があるため、ハックが必要 (余談) <canvas /> での実装は? 54