Show HN: Modern Web Primitives Replace 2019 JavaScript
Browsers now natively support CSS.highlights, <dialog> with showModal, and field-sizing, removing 50‑line JS hacks for text, dialogs, and auto‑growing inputs.
Почему стоит перейти от legacy JS к нативным примитивам
Почему стоит перейти от legacy JS к нативным примитивам
Когда я начинал работать с фронтендом, любая задача, выходящая за рамки отображения статического текста, требовала написания целого пласта JavaScript-кода. Нужно сделать модальное окно? Пиши логику переключения классов, обрабатывай клики вне области окна, следи за фокусом. Нужно, чтобы текстовое поле росло вместе с контентом? Готовься к расчётам высоты через scrollHeight и обработчикам событий input.
Сегодня ситуация меняется. Браузеры развиваются, и многие вещи, которые мы раньше решали «костылями» на JS, теперь стали частью спецификации. Переход на нативные примитивы — это не просто дань моде, а способ сделать инфраструктуру вашего приложения более надёжной.
Уменьшение количества кода и упрощение поддержки
Главная проблема старых подходов — избыточность. Чем больше строк кода вы пишете для решения базовых задач, тем выше вероятность ошибки и тем сложнее поддерживать проект. С практической точки зрения, замена 50 строк JavaScript на одну строку CSS или один HTML-атрибут радикально снижает когнитивную нагрузку на разработчика.
Возьмём, к примеру, автоматическое изменение размера текстового поля (field-sizing). Раньше для этого приходилось вешать слушатель событий и манипулировать DOM:
javascript// Старый подход: JS-хак для автовысоты textarea
const textarea = document.querySelector('.auto-grow');
textarea.addEventListener('input', () => {
textarea.style.height = 'auto';
textarea.style.height = textarea.scrollHeight + 'px';
});
Сейчас это решается одной строкой в CSS:
css/* Современный подход: нативный field-sizing */
.auto-grow {
field-sizing: content;
min-height: 40px;
}
Тот же эффект достигается без единой строчки скрипта. Это не только экономит место, но и избавляет от проблем с производительностью, когда браузеру приходится постоянно пересчитывать стили через JS-коллбэки. Аналогично обстоят дела с элементом <dialog>. Вместо управления видимостью через display: none или классы, вы используете метод .showModal(), который браузер понимает нативно, включая управление фокусом и закрытие по клавише Esc.
Улучшение предсказуемости и стабильности
Для меня как для системного инженера ключевым словом всегда была предсказуемость. В контексте веб-разработки это означает, что поведение элемента должно быть детерминированным и не зависеть от состояния сторонних скриптов.
Когда логика интерфейса завязана на JavaScript, возникает цепочка зависимостей. Если какой-то сторонний модуль или библиотека «упала» с ошибкой, ваш интерфейс может «замерзнуть»: модальное окно не закроется, кнопка не сработает, а поле ввода перестанет реагировать на ввод.
Использование нативных примитивов (таких как CSS.highlights для выделения текста или нативные диалоги) переносит ответственность за отрисовку и поведение на движок браузера. Браузер — это крайне оптимизированная и протестированная среда. Его реализация showModal() будет работать стабильнее, чем любой самописный скрипт, потому что она интегрирована в основной цикл рендеринга.
Если мы посмотрим на архитектуру современных приложений, то увидим, что чем меньше «клея» в виде JavaScript нам требуется для базовых вещей, тем меньше поверхность атаки и тем меньше шансов, что мелкий баг в логике превратит интерфейс в нерабочую массу. Это делает систему более устойчивой к изменениям и упрощает отладку.
CSS.highlights: выделение текста без span
CSS.highlights: выделение текста без span
Раньше, если нам нужно было подсветить синтаксис в блоке кода или выделить ключевые слова в тексте, мы шли по пути наименьшего сопротивления: писали парсер на JavaScript, который оборачивал каждое совпадение в <span> с определённым классом. С точки зрения архитектуры это было не очень красиво: лишние узлы в DOM, лишняя нагрузка на браузер при рендеринге и сложность поддержки. Если вы когда-нибудь занимались автоматизацией разбора логов или анализом структур данных, например, через анализ бинарников и приложений с помощью morluto/rea, вы понимаете, как важно минимизировать лишние сущности в процессе обработки данных.
Теперь браузеры умеют делать это нативно через API CSS.highlights. Это позволяет нам задавать правила подсветки прямо в CSS, не загрязняя структуру документа лишними тегами.
Базовый синтаксис и примеры применения
Работа с новым API строится в три этапа: создание объекта Highlight, определение стилей в CSS и регистрация этого объекта через JavaScript.
Сначала подготовим CSS. Вместо того чтобы описывать стили для классов вроде .keyword, .string, мы используем псевдоэлементы ::highlight.
css/* Определяем стили для наших групп подсветки */
::highlight(syntax-keyword) {
color: #d73a49;
font-weight: bold;
}
::highlight(syntax-string) {
color: #032f62;
background-color: #e6f0ff;
}
Затем нам нужно наполнить этот объект данными. Для этого используется метод Range. Допустим, у нас есть блок кода, и мы хотим подсветить в нём слово const.
javascript// Находим текстовый узел, в котором будем работать
const textNode = document.querySelector('#code-block').firstChild;
// Создаём диапазон (Range), который охватывает нужное слово
const range = new Range();
range.setStart(textNode, 0); // Начало слова 'const'
range.setEnd(textNode, 5); // Конец слова 'const'
// Создаём объект подсветки и добавляем в него наш диапазон
const syntaxHighlight = new Highlight(range);
// Регистрируем подсветку в глобальном реестре CSS
CSS.highlights.set('syntax-keyword', syntaxHighlight);
С практической точки зрения это даёт предсказуемость: DOM-дерево остаётся чистым, а логика отображения полностью отделена от логики парсинга текста. Если вам нужно реализовать что-то более сложное, например, визуализацию потоков данных при реверс-инжиниринге с AI-агентами, использование нативных Highlight API позволит избежать проблем с производительностью при работе с большими текстовыми массивами.
Совместимость с браузерами и fallback
На текущий момент поддержка CSS.highlights активно растёт, но она всё ещё не является повсеместной. В браузерах на базе Chromium поддержка реализована хорошо, однако в Safari и Firefox могут потребоваться дополнительные проверки.
Чтобы ваше решение не превратилось в «костыль», который ломает интерфейс в старых версиях, всегда используйте проверку наличия API. Если CSS.highlights не поддерживается, лучше откатиться к классическому методу с <span> или просто оставить текст без подсветки, чем получить ошибку в консоли.
javascriptif ('highlights' in CSS) {
// Используем современный подход
applyNativeHighlighting();
} else {
// Fallback: используем старый добрый метод с обертками в span
applyLegacyHighlighting();
}
Если вы строите сложную систему мониторинга или инструменты для анализа приложений и бинарных файлов, где визуализация критически важна, я рекомендую внедрять этот функционал постепенно. Начните с полифиллов или проверенной библиотеки, которая умеет переключаться между нативным API и классическим DOM-рендерингом. Это обеспечит стабильность интерфейса независимо от того, какой браузер использует ваш пользователь.
Элемент <dialog> и метод showModal()
Элемент <dialog> и метод showModal()
Раньше создание полноценного модального окна требовало написания десятков строк JavaScript. Нам приходилось вручную управлять слоями z-index, блокировать прокрутку страницы и, что самое важное, следить за фокусом, чтобы пользователь не мог «уйти» за пределы окна с помощью клавиши Tab. С появлением нативной поддержки элемента <dialog> эта задача перешла на уровень браузера. Это делает поведение интерфейса предсказуемым: браузер сам знает, что диалог — это отдельный контекст.
Создание модального окна и управление фокусом
Главное отличие между обычным открытием элемента и полноценным модальным окном заключается в методе, который вы вызываете. Если использовать метод .show(), диалог откроется как обычный блок, и пользователь сможет взаимодействовать с элементами под ним. Для создания настоящего модального окна, которое перекрывает всё остальное, нужно использовать .showModal().
С практической точки зрения, .showModal() делает две важные вещи:
- Создаёт так называемый «top layer» (верхний слой), который гарантированно отрисовывается поверх всех остальных элементов, независимо от их
z-index. - Автоматически захватывает фокус. Когда окно открывается, фокус перемещается внутрь диалога, а нажатие клавиши
Escзакрывает его.
Вот базовый пример реализации:
html<!-- Разметка модального окна -->
<dialog id="server-status-dialog">
<h2>Статус системы</h2>
<p>Все сервисы в домашней лаборатории работают штатно.</p>
<button id="close-dialog">Закрыть</button>
</dialog>
<button id="open-dialog">Проверить статус</button>
<script>
const dialog = document.querySelector('#server-status-dialog');
const openBtn = document.querySelector('#open-dialog');
const closeBtn = document.querySelector('#close-dialog');
// Открываем именно как модальное окно
openBtn.addEventListener('click', () => {
dialog.showModal();
});
closeBtn.addEventListener('click', () => {
dialog.close();
});
</script>
Чтобы стилизовать фон (затемнение), который появляется под модальным окном, используется псевдоэлемент ::backdrop. Это гораздо чище, чем создавать отдельный div для оверлея.
css/* Стилизация затемнения фона */
#server-status-dialog::backdrop {
background-color: rgba(0, 0, 0, 0.7);
backdrop-filter: blur(4px); /* Добавляем размытие для эстетики */
}
#server-status-dialog {
border: none;
border-radius: 8px;
padding: 20px;
}
Обработка закрытия и события закрытия
Когда мы работаем с системными компонентами, важно понимать, как завершается их жизненный цикл. Элемент <dialog> генерирует событие close, которое срабатывает при вызове метода .close() или при нажатии Esc.
Если вам нужно выполнить какую-то логику после того, как пользователь закрыл окно (например, обновить данные в интерфейсе или отправить лог в систему мониторинга), лучше всего подписаться на это событие.
Пример обработки закрытия с передачей значения:
javascriptconst dialog = document.querySelector('#server-status-dialog');
// Можно передать значение при закрытии, например, результат выбора пользователя
dialog.addEventListener('close', () => {
const returnValue = dialog.returnValue;
console.log(`Диалог закрыт. Результат: ${returnValue}`);
// Здесь можно инициировать обновление состояния интерфейса
// или запустить проверку доступности сервисов
});
// Пример закрытия с передачей данных
function confirmAction() {
dialog.returnValue = 'confirmed';
dialog.close();
}
Если вы занимаетесь более глубоким анализом того, как работают современные веб-приложения и их внутренние механизмы, рекомендую изучить материалы по реверс-инжинирингу с помощью ИИ-агентов в REA. Это поможет лучше понимать, как браузер обрабатывает сложные состояния интерфейса «под капотом».
Использование нативного <dialog> вместо самописных решений — это не просто вопрос экономии строк кода. Это вопрос надёжности. Вы доверяете управление фокусом и слоями браузеру, который протестирован миллионами пользователей, а не надеетесь на то, что ваш скрипт правильно обработает нажатие Esc в специфическом контексте.
field-sizing для автogrow текстовых полей
field-sizing для авторасширяющихся текстовых полей
Долгое время создание текстового поля, которое подстраивается под объем введённого текста, было классической задачей «на коленке». Чтобы textarea росла вместе с пользователем, приходилось писать JavaScript-функцию, которая на каждое событие input вычисляла scrollHeight элемента и тут же перезаписывала его высоту в пикселях. С точки зрения архитектуры это не самый изящный подход: мы заставляем браузер выполнять лишние вычисления и манипуляции с DOM там, где это можно решить на уровне отрисовки.
С появлением свойства field-sizing: content эта проблема уходит в прошлое. Теперь браузер сам понимает, что размер элемента должен зависеть от его содержимого, обеспечивая ту самую предсказуемость интерфейса, которой нам не хватало.
Настройка атрибута field-sizing
Чтобы активировать автоматическое изменение размера, достаточно добавить одно свойство в CSS. Это работает для стандартных элементов форм: input, textarea и select.
Если мы хотим, чтобы текстовое поле вело себя динамично, конфигурация будет выглядеть следующим образом:
css/* Применяем ко всем textarea, чтобы они росли по мере ввода текста */
textarea {
field-sizing: content;
/* Рекомендую также задать минимальную и максимальную высоту,
чтобы верстка не «поплыла» при экстремальных объемах данных */
min-height: 3lh; /* Высота в 3 строки */
max-height: 20lh; /* Ограничение в 20 строк */
width: 100%;
resize: none; /* Отключаем ручной ресайз, так как он теперь не нужен */
}
Здесь я использую единицы измерения lh (line-height). Это позволяет привязать высоту поля к высоте строки, что делает поведение элемента более стабильным. Если вы используете старые браузеры, свойство просто будет проигнорировано, и элемент сохранит свой стандартный размер — это хороший пример того, как современные фичи обеспечивают обратную совместимость без лишних костылей.
Практические сценарии использования
С практической точки зрения, field-sizing полезен в интерфейсах, где важна легкость и отсутствие лишнего визуального шума.
Во-первых, это чаты или мессенджеры. Вместо того чтобы бороться с фиксированной высотой окна ввода, вы просто позволяете полю расти. Во-вторых, это формы обратной связи или поля для комментариев. Пользователь видит контекст своего сообщения целиком, не прибегая к прокрутке внутри маленького окошка.
Пример реализации простого компонента сообщения:
html<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<style>
.chat-input {
field-sizing: content;
min-height: 40px;
max-height: 150px;
width: 300px;
padding: 8px;
border: 1px solid #ccc;
border-radius: 4px;
font-family: sans-serif;
resize: none;
}
</style>
</head>
<body>
<textarea class="chat-input" placeholder="Введите сообщение..."></textarea>
</body>
</html>
С точки зрения производительности, это решение выигрывает у JS-подхода. Браузер выполняет расчеты в рамках цикла рендеринга, что исключает микро-фризы при быстром наборе текста.
Хотя эта технология касается фронтенда, системный подход к автоматизации и пониманию того, как работают низкоуровневые механизмы (будь то отрисовка в браузере или анализ бинарных файлов), важен в любой инженерной дисциплине. Если вас интересует более глубокое погружение в анализ поведения программ, рекомендую изучить анализ бинарников и приложений с помощью morluto/rea.
Внедрение field-sizing — это не просто способ сократить количество строк кода. Это переход к декларативному описанию интерфейса, где мы говорим браузеру, что мы хотим получить, а не как именно он должен пересчитывать пиксели при каждом нажатии клавиши.
Часто задаваемые вопросы
Стоит ли сразу отказываться от JavaScript-библиотек в пользу нативных решений?
С практической точки зрения, переходить на нативные примитивы стоит там, где они полностью закрывают задачу, например, для модальных окон через <dialog>. Однако не стоит рассматривать это как повод для полного удаления JS из проекта. Сложная бизнес-логика и управление состоянием приложения всё ещё требуют скриптов, а нативные возможности лучше использовать как фундамент для упрощения базового интерфейса.
Как обеспечить совместимость, если часть пользователей использует старые браузеры?
Если ваша аудитория использует устаревшие версии браузеров, необходимо использовать стратегию Progressive Enhancement. Вы пишете базовый функционал на чистом HTML и CSS, который будет работать везде, а затем добавляете современные примитивы или JS-полифиллы для тех, кто поддерживает новые спецификации. Это обеспечит предсказуемость поведения интерфейса: пользователь со старым браузером увидит работающее окно, пусть и без некоторых визуальных нюансов.
Не снизит ли использование нативных примитивов гибкость кастомизации интерфейса?
Напротив, использование нативных инструментов часто даёт больше контроля над поведением элементов, так как вы опираетесь на стандарты браузера, а не на сторонние реализации. С точки зрения архитектуры, это делает код чище: вместо того чтобы бороться с побочными эффектами сторонних библиотек, вы используете встроенные механизмы, такие как field-sizing. Если стандартный примитив не позволяет реализовать специфический дизайн, вы всегда можете дополнить его небольшим количеством CSS или JS.
Повлияет ли переход на нативные возможности на производительность приложения?
Переход на нативные решения почти всегда положительно сказывается на производительности. Браузер выполняет встроенные задачи, такие как отрисовка модального окна или расчет высоты поля, на уровне своего движка, что гораздо эффективнее, чем выполнение пользовательского JavaScript-кода в основном потоке. Это снижает нагрузку на процессор и делает интерфейс более отзывчивым, особенно на устройствах с ограниченными ресурсами.