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

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

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
Avatar for kosakin kosakin
June 21, 2026
790

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