また、カクつきは一部のサイトでしか再現できません。LLM にプラグインのコードを読ませてみました。あとで詳しい方に見てもらって、最適化のアイデアがないか相談したいです。
プラグイン開発のアーキテクチャ/実装の観点から、LLM 翻訳によるパフォーマンス・ボトルネックは、以下の主要な観点で深く最適化できます:
- 非同期処理アーキテクチャの導入(Web Workers)
現状、プラグイン内の大量の正規表現マッチング、HTML 解析、翻訳テキストの突き合わせが、Content Script(メインスレッド)で実行されている可能性があります。
- 最適化案:直接 DOM 操作を伴わないロジック(例:LLM のストリーミング・プロトコル解析、テキスト前処理、多言語の突き合わせアルゴリズム)を Web Worker に移します。
- 効果:メインスレッドを解放し、翻訳データを処理しながらでも、マウスのスクロールやクリックに 60fps で応答できるようにします。
- 「レンダリングのバッチ処理」と「タイムスライス」の実施(Scheduling(スケジューリング))
ストリーミング(Streaming)出力で最も避けるべきなのは、1 文字受け取るたびに DOM を更新することです。
- 最適化案:
- バッファリング(Buffering):100ms〜200ms のバッファを設け、LLM が返した複数の断片を結合してから、requestAnimationFrame で 1 回の DOM 更新を行います。
- タイムスライス(Time Slicing):一度に大量の翻訳ノードを挿入する必要がある場合、React Fiber の考え方に近い形でタスクを小さなタスクに分割し、ブラウザがアイドルのとき(requestIdleCallback)に順次挿入します。
- 効果:本来は密集して発生していた再レイアウト(Reflow)をリズムのある更新に変え、CPU の瞬間的な負荷を大幅に低減します。
- ビューポート基準の「遅延レンダリング」(Intersection Observer(インターセクション・オブザーバー))
ページが長い場合、全量翻訳は、画面内にないノードも含めて数千〜数万の DOM ノードがバックグラウンドで同時更新されることになりがちです。
- 最適化案:IntersectionObserver API を利用します。
- 原文ノードがビューポートに入った(またはもうすぐ入る)ときだけ翻訳リクエストを発火し、翻訳 DOM をマウントします。
- ビューポートから十分離れた翻訳ノードは、一時的に破棄または非表示にして、ブラウザのレイアウト計算負荷を下げることを検討します。
- 効果:計算量を「ページ全体規模」から「画面規模」へ落とせます。
- DOM マウント戦略の最適化(Containment(コンテインメント))
翻訳ノードを挿入するときに元のレイアウトを崩すと、ページ全体の再レイアウトを招きます。
- 最適化案:
- CSS Containment:翻訳後のコンテナに contain: layout; または contain: content; を付与します。これにより、当該ノード内部の変化が外部レイアウトに影響しないことをブラウザに伝えられます。
- 固定プレースホルダ:翻訳開始前に原文の長さから高さを見積もってプレースホルダを確保し、翻訳テキストがストリーミングで伸びる際のページの「揺れ(振動)」を防ぎます。
- 効果:再レイアウト範囲を限定し、「一部の変更が全体に波及する」状況を防ぎます。
- MutationObserver の過剰発火を抑える
プラグイン自身が DOM を変更するため、その変更が自分自身の MutationObserver 監視を再度トリガーします。
- 最適化案:
- プラグインが翻訳ノードを挿入する際に、グローバルのフラグ(Flag)で一時的に監視を無効化する、または監視側で node.isTrusted や特定属性を使ってプラグイン自身が生成したノードをフィルタリングします。
- mutationChangeDelay の動的しきい値を引き上げ:ユーザーが高速スクロールしていることを検知したら、自動的に遅延を増やす、またはスキャンを停止します。
- ストリーミング出力は「全量置換」ではなく「増分更新」
- 最適化案:翻訳ノードがコンテナの場合、ストリーミング更新ではテキストノードを追記するだけにし、毎回 innerHTML = … で内部構造を再構築しないようにします。
- 効果:メモリのガベージコレクション(GC)負荷と DOM ツリーの解析コストを削減します。
まとめ
最も効果的な最適化ルートは、Web Worker でデータ処理 → requestAnimationFrame でバッファリングして描画 → IntersectionObserver で範囲を制限 → CSS contain でレイアウトを分離、です。これにより、LLM の「重負荷」とブラウザの「滑らかさ」をきれいに分離できます。
