Браузеры — это очень странный мир. Хотя WebAssembly добился успеха, в том числе и на серверах, клиент по-прежнему ощущается примерно таким же, как и десять лет назад.
Энтузиасты будут говорить вам, что доступ к нативным веб-API через WASM — это решённая задача, достаточно лишь минимального клея JS.
Но никто не задаёт вопрос, зачем нам вообще нужно получать доступ к DOM. Это лишь один из вариантов. В этой статье мне бы хотелось объяснить, почему уже настало время отправить DOM и всевозможные API на радугу, а также поделиться некоторыми идеями о том, как это сделать.
Не буду притворяться, что знаю о браузерах всё. Сегодня уже никто не знает всего, в этом-то и проблема.

Модель «документа»
Немногие из нас осознают, насколько плоха DOM на самом деле. В Chrome document.body
сегодня имеет более 350 ключей, сгруппированных примерно так:

И сюда не включены свойства CSS из document.body.style
, которых... ещё 660.
Граница между свойствами и методами очень размыта. Многие из них — просто фасады с невидимым сеттером позади. Некоторые геттеры могут запускать изменение структуры just-in-time. Это древнее легаси, как и все свойства onevent
, которыми уже никто не пользуется.
DOM не компактна и продолжает кабанеть. И это сильно зависит от того, создаёте ли вы веб-страницы или веб-приложения.
Большинство разработчиков сегодня избегает работать с DOM напрямую, однако время от времени какой-нибудь пурист восхваляет чистую DOM, как превосходящую различные фреймворки компонентов/шаблонизации JS. Декларативные улучшения DOM наподобие innerHTML
вообще не похожи на современные паттерны UI. В DOM есть слишком много способов выполнения одной задачи, и ни один из них не удобен.
connectedCallback() {
const
shadow = this.attachShadow({ mode: 'closed' }),
template = document.getElementById('hello-world')
.content.cloneNode(true),
hwMsg = `Hello ${ this.name }`;
Array.from(template.querySelectorAll('.hw-text'))
.forEach(n => n.textContent = hwMsg);
shadow.append(template);
}
Здесь стоят упоминания Web Components — веб-нативный эквивалент библиотек компонентов JS. Но они появились слишком поздно и не стали популярными. Их API кажется неуклюжим из-за Shadow DOM, добавляющей новые слои вложенности и областей видимости. Заявления их поклонников похожи на бездумные восхваления.
Ахиллесова пята DOM — это наследие SGML/XML, из-за которого всё становится строкотипизированным. У библиотек наподобие React такой проблемы нет, их синтаксис лишь выглядит, как XML. Разработчики приучились не хранить состояние в документе, потому что он для этого не подходит.
Что касается самого HTML, то его особо не за что критиковать, потому что за 10-15 лет в нём ничего не поменялось. Примечательна только ARIA (accessibility), да и то только потому, что её задачу должен был решить семантический HTML, но он с этим не справился.
Семантический HTML так и не достиг своей цели. Несмотря на то, что его развитие началось где-то в 2011 году, отсутствуют, например, тэги <thread>
и <comment>
, хотя они были уже хорошо устоявшимися идиомами. Вместо этого комментарием, вероятно, может быть article
внутри article
. К тому же его гайдлайны довольно... странные, скажем так.
Складывается ощущение, что HTML всегда завидовал бумаге и не смог достаточно принять или полностью определить свою гипертекстовую природу; к тому же он не верит, что его пользователи будут следовать чётким правилам.
С тех пор управление развитием HTML перешло к WHATWG, то есть к разработчикам браузеров, которым не удалось определить что-то более конкретное в качестве видения, поэтому вместо этого они начали пришивать новые возможности по краям.
Тем временем даже в CSS появились выражения, потому что любой язык шаблонизации мечтает стать языком программирования.

С редактируемостью в HTML по-прежнему всё печально. Хотя технически она поддерживается через contentEditable
, на самом деле превратить эту фичу во что-то удобное для приложений — тёмное искусство. Уверен, что разработчики Google Docs и Notion могут поделиться ужасными историями.
Сегодня уже никто по-настоящему не верит в древних богов progressive enhancement и отделения разметки от стилизации.
Большинство современных приложений склеивает HTML/CSS/SVG во что-то более-менее приемлемое. Но это приводит к огромному оверхеду, а результаты всё меньше и меньше напоминают приличный UI-тулкит.


Списки и таблицы необходимо виртуализировать вручную, заменяя структуру, изменение размеров, перетаскивание и так далее. Каждый раз разработчикам приходится мучиться с тем, чтобы полоса прокрутки окна чата была прикреплена к низу. И чем больше вы виртуализируете, тем больше приходится изобретать заново функцию поиска на странице, меню правой клавиши мыши и так далее.
Веб размыл различия между UI и fluid-контентом, который в то время был новинкой. Но это всё больше теряет смысл, потому что относящееся к UI устарело на десяток лет, а контент по большей мере стал однородным.

CSS изнутри наружу
CSS тоже не имеет хорошей репутации, но немногие могут назвать конкретные причины этого.
Многие люди ошибаются, начиная с неверной мысленной модели и обращаясь с CSS, как с солвером ограничений. Это проще всего показать на примере:
<div>
<div style="height: 50%">...</div>
<div style="height: 50%">...</div>
</div>
<div>
<div style="height: 100%">...</div>
<div style="height: 100%">...</div>
</div>
Первый случай может показаться разумным: мы разделяем родителя на две половины по вертикали. Но что насчёт второго?
Если рассматривать его, как множество ограничений, то результат получится противоречивым: родительский div
вдвое выше... самого себя. На самом деле, в обоих случаях height
игнорируется. Высота родителя неизвестна, а CSS здесь не возвращается назад и не выполняет итерации. Он просто переносит контент.
Если, например, задать для родителя height: 300px
, то это сработает, но второй случай всё равно перельётся через край.

Вместо этого надо рассуждать о CSS так: мы применяем два прохода ограничений, сначала двигаясь снаружи внутрь, а затем изнутри наружу.
При создании окна приложения процесс происходит снаружи внутрь: имеющееся пространство разделяется, а внутренний контент не влияет на задание размеров панелей.
Когда параграфы идут друг за другом на странице, процесс происходит изнутри наружу: текст растягивает содержащий его родительский элемент. Именно так хочет себя вести HTML.
При такой структуризации CSS-компоновки вычислительно довольно просты. Можно распространять ограничения родителя на дочерние элементы, а затем суммировать размеры дочерних элементов в другом направлении. Это удобно и позволяет веб-страницам хорошо масштабироваться с точки зрения элементов и текстового контента.
По умолчанию CSS всегда работает изнутри наружу, отражая свою ориентированную на документы природу. Движение снаружи внутрь неочевидно, потому что вы сами должны передавать все ограничения вниз, начиная с body { height: 100%; }
. Именно поэтому всегда говорят, что вертикальное выравнивание CSS реализуется сложно.

Показанный выше сценарий лучше реализовать с помощью flex box CSS3 (display: flex
), обеспечивающего явный контроль над способом разделения пространства.
К сожалению, использование flex запутывает простую модель CSS. Для auto-flex алгоритм вычисления компоновки должен замерять «естественный размер» каждого дочернего элемента. Это означает, что компоновка создаётся дважды: сначала спекулятивно, как будто она парит в космосе, а затем ещё раз, после увеличения или уменьшения под размер:

Это кажется разумным, но из-за рекурсивности возможны сюрпризы. Для создания спекулятивной компоновки родителя часто требуется полная компоновка неподогнанных под размер дочерних элементов, например, способ переноса текста. При определённой вложенности это теоретически может вызвать экспоненциальный взрыв; впрочем, я никогда не слышал, чтобы это становилось проблемой.
Скорее, вы обнаружите это, когда кто-то вставит большой контент и всё внезапно окажется растянутым наперекосяк. Это противоположно проблеме, показанной на известной кружке.
Чтобы избежать рекурсивной зависимости, необходимо изолировать содержимое дочерних элементов от внешнего, благодаря чему спекулятивная компоновка становится тривиальной. Это можно сделать при помощи contain: size
или вручную задав размер flex-basis
.
В CSS появились конструкции наподобие contain
и will-change
, хорошо работающие напрямую с системой компоновки и создающие иллюзию большой красивой структуры. Это раскрывает часть внутренней ориентированной на слои структуры; эти конструкции можно использовать в качестве замены, например, обёрткам position: absolute
.
Но они урезают часть семантики и разрушают поток ограничений, применённых ко всей DOM. Для более простых случаев они по умолчанию слишком широки и слишком ориентированы на документ.
На самом деле, это метафора для всех DOM API.

Что же есть хорошего?
Тем не менее, flex box достаточно хорош, если вы понимаете эти тонкости. Интуитивно понятно, как создавать компоновки из вложенных строк и столбцов с промежутками; к тому же такая система хорошо адаптируется под изменения размеров. Это хорошая сторона CSS, которую вы при достаточном старании можете сделать эргономичной. Grid в CSS тоже работают подобным образом, только синтаксис их... уж слишком CSS-ный.
Но если вы проектировали компоновку CSS с нуля, то так делать не будете. Вместо этого вы разобьёте поведение на ячейки компонентов и будете использовать их порционно. И «снаружи внутрь», и «изнутри наружу» будут вполне понятны, как разные виды моделей контейнеров и размещения.
Это иллюстрируют модели отображения inline-block
и inline-flex
: block
или flex
находится внутри, но элемент inline
— снаружи. Это два ортогональных (по большей мере) аспекта box в модели box.
На самом деле, в гипертексте стили текста и шрифтов — это пятое колесо в телеге. Свойства наподобие размера шрифта наследуются от родителя к ребёнку, поэтому могут работать тэги форматирования наподобие <b>
. Но большинство из этих 660 свойств CSS не ведёт себя так. Создание границы вокруг элемента не применяет рекурсивно ту же самую границу ко всем его дочерним элементам, это было бы глупо.
Видно, что в CSS объединены как минимум две концепции: система стилизации текста с форматированием на основе наследования... и система компоновки для элементов block и inline, вложенных рекурсивно, но без наследования. Они используют одинаковый синтаксис и API, но на самом деле не каскадируются одинаково. Их объединение под одним зонтиком стилизации было ошибкой.
Стоит отметить, что первоначальные идеи по относительному масштабированию em
по большей мере стали неактуальными. Теперь мы рассуждаем в рамках логических пикселей и пикселей устройств, что гораздо более здравое решение, более близкое к тому, чего ожидает пользователь на самом деле.
SVG тоже нативно интегрирован. Наличие SVG в DOM вместо простых тэгов <img>
полезно для динамической генерации фигур и настройки стилей значков.
Но несмотря на свою мощь, SVG — это и не подмножество, и не надмножество CSS. Пусть они и пересекаются, между ними есть небольшие различия, например, аффинное transform
. SVG тоже имеет свои неприятные стороны, например, сериализацию всех координат в строки.
CSS тоже получил возможность скруглять углы, рисовать градиенты и применять произвольные маски усечения: очевидно, что он стремится быть похожим на SVG, но ему это совершенно не удаётся. SVG может, например, выполнять полигональную проверку нажатий для событий мыши, чего CSS не умеет, а ещё у SVG есть собственное множество графических эффектов слоёв.
Выбор того, что использовать для рендеринга конкретного элемента (HTML/CSS или SVG), зависит от раздражающих компромиссов, даже несмотря на то, что и то, и другое по сути своей — векторная графика.
У любого из вариантов есть недостатки. Упомяну только три:
text-ellipsis
может использоваться для усечения текста без переносов, но не целых параграфов. Распознавать усечённый текст ещё сложнее, как и просто измерять текст: API неадекватен задаче. Все просто считают буквы.position: sticky
позволяет элементам оставаться на месте при скроллинге без дёрганья. Хотя оно специально создано для этой цели, в нём есть небольшая поломка. Чтобы элементы оставались закреплёнными безусловно, требуется абсурдный хак с вложенностью, хотя это должно быть тривиальным.Свойство
z-index
определяет слои по абсолютному индексу. Это неизбежно приводит кz-index-war.css
, когда все добавляют новое число +1 или -1, чтобы слои были наложены друг на друга правильно. Концепция относительного позиционирования по Z отсутствует.
Для каждой из этих фич мы вынуждены использовать версию 1 того, что заставит их работать, вместо того, чтобы предоставлять правильные примитивы.
Настроить всё это непросто, это трудная часть проектирования API. Можно разбираться с этим только итеративно, создавать реальные проекты, прежде чем финализировать изменения, а потом искать дырки.
Холст, масло
Итак, DOM плоха, CSS хорош на считанные проценты, а SVG неудобен, но необходим... И никто не может с этим ничего поделать?
Вообще-то нет. Диагноз таков: промежуточные слои не особо устраивают сегодня никого. Хорошим началом был бы просто HTML6, в котором концепции наконец-то начнут удалять.
Но по большей мере необходимо высвободить уже имеющуюся функциональность. Это можно сделать хорошим или плохим способом. В идеале нужно проектировать систему так, чтобы «аварийным выходом» для произвольного применения был бы тот же API, с помощью которого вы создавали элементы для пользовательского пространства. Именно для этого нужен догфудинг, и именно так можно получить хорошие ядра.
Последнее предложение в этой сфере — HTML in Canvas, позволяющий отрисовывать HTML-контент в <canvas>
с полным контролем за визуальным выводом. Это не очень хорошо.
Хоть это и может показаться полезным, API имеет свой современный вид только потому, что привязана к DOM: чтобы полноценно участвовать в компоновке и стилизации, а также для обеспечения accessibility элементы должны быть наследниками <canvas>
. Также существуют «технические вопросы» об использовании этой системы вне экрана.
Один из примеров — вот этот крутящийся куб:

Чтобы сделать его интерактивным, нужно добавить прямоугольники проверки нажатий и реагировать на события отрисовки. Это новый вид API проверки нажатий. Но он работает только в 2D... так что, похоже, использование в 3D — это только косметика? У меня есть много вопросов.
Да, если бы мы проектировали всё с нуля, то делали бы это не так! В частности, абсурдно то, что необходимо брать всю ответственность за взаимодействие с элементом и его потомками просто для того, чтобы настроить его внешний вид, то есть рендеринг. Особенно в браузере, где есть проективные 3D-преобразования CSS.
При этом не полностью охватываются сценарии использования; например, при искривлённом повторном проецировании потребуется более сложная проверка нажатий, нежели прямоугольники. Продумали ли это разработчики? Что произойдёт, если добавить сюда раскрывающийся список?
Мне кажется, они не смогли разобраться, как объединить фильтры CSS и SVG или как добавить в CSS шейдеры. Остался единственный вариант. «Ну, по крайней мере, эту систему можно программировать». Но так ли это? Скриншотинг контента DOM — это единственный хороший сценарий использования, но нам обещали совсем не это.
Все обоснования реализации «сложных UI на canvas» заключаются в возможности делать то, что DOM не делает, например, виртуализацию контента, компоновку и стилизацию just-in-time, визуальные эффекты, настраиваемые жесты и проверку нажатий и так далее. Всё это техническая сторона. Необходимость предварительной подготовки всего контента DOM, который нужно отрисовывать — это... очень контрпродуктивно.
С точки зрения реактивности маршрутизация всего этого обратно через то же дерево документа — столь же плохая идея, потому что она создаёт потенциальные циклы с наблюдателями. Canvas, который рендерит контент DOM, обычно уже не является элементом документа, это нечто совершенно иное.

Подлинная ахиллесова пята canvas заключается в том, что у нас нет реального доступа к системным шрифтам, API компоновки текста и утилитам UI. Его простота довольно абсурдна. Чтобы получить текст с переносами, необходимо реализовать всё с нуля, в том числе разбиение Unicode-слов.
Предложение заключается в том, чтобы просто использовать DOM в качестве «чёрного ящика» для контента. Но мы уже знаем, что таким образом нельзя получить ничего, кроме дополнительного нагромождения CSS/SVG. text-ellipsis
и его друзья остаются поломанными, а чтобы исправить их, всё равно необходимо реализовывать с нуля UI в стиле 1990-х.
Ситуация «всё или ничего», а нам бы хотелось чего-то прямо посередине. Именно поэтому нужно открыть более низкие уровни.
Куда двигаться дальше
Цели «HTML in Canvas» действительно привлекательны, но не стоит тащить с собой двадцать лет бесполезного багажа, не обеспечивая при этом ничего поистине нового.
Веб-разработка в стиле склеивания разных элементов привела к ужасной стагнации и утере качества UI в целом. Когда поведения UI приходится выводить из div
, это ограничивает выбор возможных решений. Решение этой проблемы в рамках DOM/HTML кажется непродуманным, потому что внутри уже слишком много бардака. Вместо этого следует открывать новые поверхности снаружи.


Здесь я обычно указываю на рендерер в стиле HTML Use.GPU, реализующий полную flex-модель X/Y, несущественно повышая при этом сложность и код. Не хочу сказать, что моё решение великолепно. Нет, оно довольно минималистичное и нишевое... но оно определённо удобнее. Центрирование по вертикали в нём реализуется просто, а позиционирование логично.
Там нет семантического HTML или каскада CSS, просто компоновка первого класса. И для border*
там не нужен 61 аксессор. Можно просто добавлять шейдеры к div. Разве не это нужно людям? Можете изучить структуру, по большей мере это просто SDF.
Вопросы о шрифтах и разметке возникают только на листьях дерева, где находится текст. Поразительно, что здесь можно реализовать 90% функциональности DOM без мешанины HTML/CSS/SVG, достаточно лишь заново изобрести велосипед. И всё это сделано одним человеком (да, я в курсе закона остальных 90% функциональности).
Классическая модель данных здесь состоит из дерева видимых элементов и дерева рендеринга. Как должно выглядеть дерево видимых элементов? И куда его можно опустить? Куда оно опускается прямо сейчас, под огромной кучей легаси-мусора?


Здесь могут сделать хорошие предложения проекты альтернативных браузеров наподобие Servo или Ladybird. В них содержатся самые свежие реализации и упор в первую очередь делается на самые необходимые фичи. Разработчики популярных браузеров тоже могут принять участие, но здесь важен вкус. Качественные большие системы вырастают из хороших маленьких, а не плохих больших. Возможно, если бы Mozilla не потерпела поражение... но увы.
Ситуация такова, что нативные для платформы UI-тулкиты по-прежнему играют в догонялки с декларативными и реактивными UI. Полезны могут быть нативные альтернативы Electron наподобие Tauri, но их разработчики не считают обязательным требованием origin isolation, что напрягает отделы безопасности.
Однако существует привлекательная для них и вполне реализуемая «морковка»: улучшенная изоляция процессов. Из-за эксплойтов CPU наподобие Spectre многопоточность через SharedArrayBuffer
и веб-воркеры всё равно неактуальна, и это влияет на весь WASM.
Изобретение DOM заново с целью отказа от всего легаси-багажа должно совпасть по времени с перепроектированием для создания более многопоточного, multi-origin и асинхронного веба. Движки браузеров уже многопроцессные... но чему научились их разработчики? Со времён Netscape произошло уже многое: был серьёзный прогресс в структурированной конкурентности, семантике владения, влиянии функционального программирования. Всё это может здесь пригодиться.
Впрочем, первым этапом должна просто стать модель данных, у которой нет по 350 с лишним свойств для каждого узла.
Не надо поддаваться заблуждению, что эту проблему никак не решить.

Комментарии (0)
jobless
24.09.2025 09:50Не буду притворяться, что знаю о ( б̶р̶а̶у̶з̶е̶р̶а̶х̶ нужное вписать) всё. Сегодня уже никто не знает всего, в этом-то и проблема.
Так можно начинать любой не художественный текст. Беру на вооружение :)
ImagineTables
24.09.2025 09:50Сегодня уже никто по-настоящему не верит в древних богов … отделения разметки от стилизации.
Я, посвятивший этому богу большую часть жизни:
user-book
Как будто веб это про оптимизацию или что то иное?
Сколько стандартов css висит до сих пор просто потому что браузеры хотят тянуть одеяло в свою сторону? И это не вспоминаем яблочников с их альтернативным устаревшим браузером.
Или может вспомним про куки, которые по сути были костылем для удобного "сшивания" фронта и бека, но "продали" настолько хорошо что сейчас уже предпочитают строить сайты без поддержки куков чтоб не лезть во все это.
---
Не будет никаких правок по стандарту потому как корова все еще продолжает давать молоко, как бы сильно не обрезали паек. Я скорее поверю по всякие "нововведения ради безопасности" что бы не давать работать блокировщикам рекламы и анонимизаторам.
Не однократно время от времени мелькает в правительствах разных стран идея "запретить https а то он небезопасный" и все эти православные сертификаты как раз продолжение этих дебатов.
У великих мужей болит голова совершенно о другом (привет поисковый движок гугла) и если хочется новый более рассчитанный на глобальность интернет то вам не к http увы. Всякие игры с протоколом и децентрализацией идут повсеместно, но я до сих пор не видел ничего прям адекватного потому как у интузиастов два лагеря - или крипта и все вокруг нее (и до интернета с котиками там так и не доходит) или простота и безопасность и получаем очередной форк идей xmpp