Кроме того, фризы воспроизводятся только на некоторых сайтах. Я попросил LLM пробежаться по коду плагина; позже попрошу опытного человека посмотреть, есть ли идеи по оптимизации.
С точки зрения архитектуры и реализации разработки плагина, для глубокої оптимизации узких мест производительности, вызванных LLM‑переводом, можно идти по следующим ключевым направлениям:
- Внедрение асинхронной архитектуры обработки (Web Workers)
Сейчас большое количество регулярных сопоставлений, HTML‑парсинга и сравнения переведённого текста, вероятно, выполняется в Content Script (главном потоке).
- Решение по оптимизации: перенести логику, не связанную с прямыми DOM‑операциями (например, разбор streaming‑протокола LLM, предобработка текста, алгоритмы сопоставления многоязычных версий), в Web Worker.
- Эффект: разгрузить главный поток, чтобы браузер, обрабатывая данные перевода, всё равно мог с 60fps отзываться на прокрутку и клики мышью.
- «Пакетная отрисовка» и «временное нарезание» (Scheduling)
При потоковом выводе (Streaming) хуже всего обновлять DOM при получении каждого символа.
- Решение по оптимизации:
- Буферизация обновлений (Buffering): задать буфер 100–200 мс, объединять несколько фрагментов, возвращаемых LLM, и затем инициировать одно DOM‑обновление через requestAnimationFrame.
- Временное нарезание (Time Slicing): если нужно разом вставить большое количество узлов перевода, использовать идею наподобие React Fiber — разбить задачу на множество мелких подзадач и вставлять их по одной в моменты простоя браузера (requestIdleCallback).
- Эффект: превратить исходно плотные перерасчёты (Reflow) в ритмичные обновления и существенно снизить пиковую нагрузку на CPU.
- «Ленивая отрисовка» на основе видимой области (Intersection Observer)
Если страница очень длинная, полный перевод приводит к тому, что тысячи DOM‑узлов одновременно обновляются в фоне, даже если они не на экране.
- Решение по оптимизации: использовать API IntersectionObserver.
- Запускать запрос перевода и монтировать DOM перевода только когда узел оригинала входит в видимую область (или скоро войдёт).
- Для узлов перевода, которые находятся далеко вне видимой области, можно временно уничтожать или скрывать их, чтобы снизить давление на вычисления раскладки в браузере.
- Эффект: снизить объём вычислений с «масштаба всей страницы» до «масштаба экрана».
- Оптимизация стратегии монтирования DOM (Containment)
При вставке узлов перевода, если нарушается исходная раскладка, это может привести к перерасчёту всей страницы.
- Решение по оптимизации:
- CSS Containment: добавить для контейнера после перевода contain: layout; или contain:
content; Это сообщит браузеру, что внутренние изменения этого узла не влияют на внешнюю раскладку. - Фиксированный плейсхолдер: до начала перевода оценить высоту по длине исходного текста и зарезервировать место, чтобы при потоковом «подпрыгивании» текста перевода страница не «дрожала» из‑за постоянных изменений.
- CSS Containment: добавить для контейнера после перевода contain: layout; или contain:
- Эффект: ограничить область перерасчёта, чтобы не получалось «потянешь за ниточку — дёрнется всё».
- Снижение чрезмерных срабатываний MutationObserver
Поскольку сам плагин изменяет DOM, он снова триггерит собственное наблюдение MutationObserver.
- Решение по оптимизации:
- Когда плагин вставляет узлы перевода, через глобальный флаг (Flag) временно отключать наблюдение, либо в слушателе отфильтровывать узлы, созданные самим плагином, через node.isTrusted
или по специфическим атрибутам. - Динамически повышать порог mutationChangeDelay: когда обнаруживается, что пользователь быстро прокручивает страницу, автоматически увеличивать задержку или приостанавливать сканирование.
- Когда плагин вставляет узлы перевода, через глобальный флаг (Flag) временно отключать наблюдение, либо в слушателе отфильтровывать узлы, созданные самим плагином, через node.isTrusted
- Для streaming‑вывода — «инкрементальные обновления», а не «полная замена»
- Решение по оптимизации: если узел перевода — это контейнер, то при потоковом обновлении следует лишь дописывать текстовые узлы, а не каждый раз делать innerHTML = …
и заново пересобирать внутреннюю структуру. - Эффект: уменьшить нагрузку на сборку мусора (GC) и накладные расходы на разбор DOM‑дерева.
Итог
Самый эффективный путь оптимизации: Web Worker обрабатывает данные →
буферизированная отрисовка через requestAnimationFrame →
ограничение области через IntersectionObserver →
изоляция раскладки через CSS contain. Так можно идеально развязать «тяжёлую нагрузку» от LLM и «плавность» браузера.
