Контекст проекта и анализ требований
Как разработчик ToolMi (gongjumi.com), я постоянно стремлюсь улучшить опыт работы пользователей с онлайн-инструментами. Недавно при обновлении функции отображения кода на сайте я заметил, что нативный компонент textarea
имеет серьёзные UX-ограничения при вводе и выводе кода.
На Gongjumi есть множество инструментов для работы с кодом, таких как YAML-форматтер, Генератор Java-классов из SQL и Beautifier TypeScript. Все они требуют ввода кода и отображения результатов. Однако простые textarea
не поддерживают подсветку синтаксиса, что затрудняет поиск ошибок в сложном коде и ухудшает общий пользовательский опыт.
Выбор технологии: почему highlight.js
Из множества библиотек для подсветки синтаксиса я выбрал highlight.js по нескольким причинам:
Широкая поддержка языков
highlight.js поддерживает более 190 языков программирования — от популярных JavaScript, Python, Java до редких функциональных языков. Это важно для сайта, обрабатывающего разнообразные форматы кода.
Разнообразие тем
Библиотека предоставляет темы в стиле GitHub, VS Code, Atom и др., что позволяет подобрать оформление, соответствующее дизайну Gongjumi.
Совместимость с экосистемой
highlight.js легко интегрируется с Vue.js, установка проста. Кроме того, встроенная автоопределение языка применяет подсветку даже без указания языка пользователем.
Преимущества производительности
По сравнению с тяжёлыми редакторами (например, Monaco Editor) highlight.js легче и загружается быстрее — идеально для быстрых онлайн-инструментов Gongjumi.
Детали реализации
Первый подход: двухслойная наложенная структура
Изначально я попробовал наложить div
с подсвеченным кодом поверх прозрачного textarea
:
<div class="editor-container">
<div class="code-highlight" v-html="highlightedCode"></div>
<textarea v-model="code" class="code-textarea"></textarea>
</div>
Этот метод позволял вводить код в прозрачный textarea
, а на фоне отображался подсвеченный код. Но он имел серьёзные недостатки:
- Слои часто смещались при длинном коде или множестве переносов строк.
- Сложно было синхронизировать прокрутку.
- Трудно было добиться точного совпадения шрифтов, межстрочного интервала и отступов.
Оптимизация с помощью CSS Grid
Для выравнивания я поместил оба элемента в одну сеточную область:
.editor-container {
display: grid;
grid-template-areas: "editor";
}
.code-highlight, .code-textarea {
grid-area: editor;
}
Это улучшило выравнивание, но синхронизация прокрутки и единообразие стилей оставались проблемными.
Окончательное решение: contenteditable
div
После множества экспериментов я пришёл к выводу, что лучше всего использовать div
с contenteditable
:
<div
contenteditable="true"
class="editable-code hljs"
v-html="highlightedCode"
@input="handleInput"
/>
Преимущества:
- Используются стандартные CSS-стили highlight.js без доработок.
- Нет сложной структуры наложения, исчезают проблемы с выравниванием и синхронизацией.
- Редактирование и отображение максимально унифицированы, UX становится более плавным.
Основная реализация кода
Проектирование интерфейса компонента
Чтобы сделать редактор переиспользуемым во всех инструментах Gongjumi, я разработал следующий интерфейс:
interface Props {
modelValue: string // двунаправленная привязка кода
language?: string // необязательное указание языка
readonly?: boolean // режим только для чтения
placeholder?: string // текст-подсказка
rows?: number // число видимых строк
}
Логика подсветки синтаксиса
Ключевой момент — вызов API highlight.js и обработка ошибок:
const updateHighlight = () => {
try {
if (props.language && hljs.getLanguage(props.language)) {
const result = hljs.highlight(localValue.value, { language: props.language });
highlightedCode.value = result.value;
} else {
const result = hljs.highlightAuto(localValue.value);
highlightedCode.value = result.value;
}
} catch (error) {
console.error('Ошибка подсветки синтаксиса:', error);
highlightedCode.value = escapeHtml(localValue.value);
}
};
Режим редактирования и режим только чтения
Для ввода и отображения я реализовал два режима:
<template>
<!-- Режим только чтения для отображения результатов -->
<pre v-if="readonly" class="readonly-code hljs" v-html="highlightedCode"></pre>
<!-- Режим редактирования для ввода кода -->
<div v-else contenteditable="true" class="editable-code hljs" @input="handleInput"></div>
</template>
Ключевые технические детали
Интеграция темы
Чтобы соответствовать дизайну Gongjumi, я выбрал тему в стиле GitHub и слегка изменил цвета:
// импорт темы highlight.js
import 'highlight.js/styles/github.css';
Улучшения взаимодействия с пользователем
- Обработка клавиши Tab: вставка пробелов вместо перехода фокуса.
- Обработка вставки: удаление форматирования, сохранение только plain-text.
- Дебаунс ввода: предотвращение слишком частых обновлений при быстром наборе.
Сохранение позиции курсора
Чтобы курсор не прыгал при обновлении innerHTML, я реализовал сохранение и восстановление позиции:
const handleInput = () => {
const cursorPosition = saveCursorPosition();
updateContent();
updateHighlight();
nextTick(() => {
restoreCursorPosition(cursorPosition);
});
};
Эффект в реальных случаях
Обновление инструмента SQL → Java Entity
- Полная подсветка синтаксиса SQL в поле ввода.
- Профессиональная подсветка сгенерированного Java-кода.
- Более аккуратный интерфейс и очень положительный отзыв пользователей.
Улучшение JSON-форматтера
Подсветка помогает лучше видеть структуру JSON и быстрее находить ошибки.
Beautifier TypeScript
Переход от простых <pre>
и <code>
к полноценному компоненту, приближенному к IDE.
Оптимизации производительности и лучшие практики
Загрузка по требованию
import hljs from 'highlight.js/lib/core';
import javascript from 'highlight.js/lib/languages/javascript';
import python from 'highlight.js/lib/languages/python';
import java from 'highlight.js/lib/languages/java';
import sql from 'highlight.js/lib/languages/sql';
hljs.registerLanguage('javascript', javascript);
hljs.registerLanguage('python', python);
hljs.registerLanguage('java', java);
hljs.registerLanguage('sql', sql);
Управление жизненным циклом компонента
- Инициализация highlight.js при монтировании.
- Обновление подсветки при обновлениях компонента.
- Очистка ресурсов при размонтировании.
Адаптивный дизайн
- Подходящий размер шрифта и межстрочный интервал для мобильных.
- Удобное сенсорное управление.
- Оптимизированная прокрутка и масштабирование.
Выводы и уроки
Распространённые проблемы и решения
Смещение слоёв
Решение: отказаться от двухслойного наложения в пользу div
с contenteditable
. Если нужен двухслойный подход, используйте CSS Grid вместо абсолютного позиционирования.
Прыжки курсора
Решено сохранением/восстановлением позиции курсора и дебаунсом обновлений.
Конфликты стилей
Изолируйте стили highlight.js с помощью CSS Modules или scope-стилей.
Советы по отладке
- Используйте Elements во вкладке DevTools для проверки изменений DOM.
- Следите за ошибками highlight.js в консоли.
- Тестируйте с разными языками и длиной кода.
- Проверяйте в разных браузерах и на устройствах.
Планы на будущее
Новые функции
- Номера строк: упрощают навигацию по длинным фрагментам.
- Сворачивание кода: collapse/expand блоки.
- Поиск и замена: быстро находить и заменять текст.
- Интегрированный форматер: один клик с Prettier.
Дополнительная оптимизация производительности
- Виртуальная прокрутка для больших файлов.
- Улучшение алгоритмов подсветки для снижения задержек.
- Кэширование для предотвращения повторного анализа.
Итоги проекта и рефлексия
Технические достижения
Интеграция highlight.js позволила глубже понять внутреннюю работу современных фронтенд-редакторов, освоить сложную обработку пользовательских взаимодействий и укрепить навыки компонентного развития.
Ценность для пользователей
Подсветка синтаксиса значительно улучшила UX при поиске ошибок и понимании сложного кода, что подтверждается отзывами пользователей.
Архитектурные выводы
Хорошо спроектированный компонент не только решает текущие задачи, но и становится надёжной базой для будущего развития и повторного использования в других проектах.
Для сайта онлайн-инструментов Gongjumi важно внимание к деталям UX. Подсветка синтаксиса — небольшая функция, но она отражает глубокое понимание потребностей пользователей и стремление к качеству.