EverProduct
Фронтенд

Этап 01 · Между кодом и экраном

Конвейер: от адреса до пикселей

Ты ввёл адрес — появилась страница. Между этими двумя событиями работает машина с жёстким порядком операций, и большинство багов, которые тебе предстоит ловить годами, живут на одной из её станций.

Жмёшь Enter на адресе — и через полсекунды есть страница. Ничего волшебного в этой полусекунде нет, и ничего в ней не происходит одновременно.

Машина отработала жёсткую последовательность: найти сервер, попросить документ, получить текст, превратить текст в дерево, выяснить, как это дерево должно выглядеть, вычислить, где стоит каждая коробка, залить коробки цветом, отдать результат экрану. А потом проделала бо́льшую часть заново, несколько раз, потому что скрипт что-то поменял.

Почти всё, что будет тебя дальше сбивать с толку, — вспышка неоформленного текста, скрипт, который не находит кнопку, страница, прыгающая под пальцем, дёргающаяся анимация — это та же машина, прогоняющая свои станции в единственном порядке, который она знает. Выучи порядок один раз — и загадки превратятся в адреса.

Три входа, три разные работы

Браузер принимает три вида материала и с каждым делает принципиально разное. Новички их смешивают, и до тех пор, пока они не разведены, ничего дальше не встаёт на место.

  • HTML — что это такое. Заголовок, список, форма, кнопка. Структура и смысл.
  • CSS — как это выглядит. Размер, цвет, положение, отступы.
  • JavaScript — что происходит. Поведение во времени: реагировать, менять, запрашивать.

Проверка на то, развёл ли ты их по-настоящему, — вопрос, с которым ты будешь сталкиваться еженедельно: это надо сделать на HTML, на CSS или на JS? Выпадашка, открывающаяся по наведению, — CSS. Выпадашка, которая открывается по клику и остаётся открытой, — JS. Текст, который является заголовком, — HTML, и никакой размер шрифта не превратит div в заголовок. Неверный выбор всегда работает сначала и всегда обходится дорого потом.

Поездка до сервера

Твой адрес — это имя, а сеть умеет двигать только числа. Поэтому браузер спрашивает у DNS-сервера, какая машина скрывается за example.com, открывает к ней соединение, договаривается о шифровании, если адрес начинается с https, и только потом отправляет собственно запрос: дай мне этот документ.

Обратно приходит текст. Не страница — текст, с кодом статуса (200 — вот он, 404 — такого нет, 500 — сервер сломался) и набором заголовков, описывающих, что это за текст и как долго его можно переиспользовать.

Из этой поездки сразу окупаются два факта. Первый: каждый отдельный файл — каждая картинка, стиль, скрипт и шрифт — это отдельный запрос, а запросы стоят времени даже когда файлы крошечные. Второй: браузер кэширует, и добрая половина того, что при втором заходе кажется мгновенным, вообще не загружалась. По этой же причине пользователи видят старую версию файла, который ты, готов поклясться, починил.

Парсер не ждёт

HTML приходит потоком, и браузер начинает строить, не дожидаясь последнего байта. Он читает сверху вниз, превращая теги в DOM — дерево объектов, по узлу на элемент, вложенных ровно так, как были вложены твои теги.

Это дерево — не твой файл. Это живая модель, построенная по файлу, и с этого момента существует только она. HTML-файл — семя; DOM — растение.

По дороге вниз парсер прерывают две вещи, и обе важны до чрезвычайности:

Таблица стилей блокирует отрисовку. Браузер отказывается показывать страницу, внешний вид которой ему ещё не известен: нарисовать сначала, а переоформить потом — значит мигнуть пользователю в лицо неоформленным текстом. Поэтому CSS в шапке — это шлагбаум: пока он не приехал, не показывается ничего.

Обычный скрипт блокирует разбор. Встретив тег script, парсер останавливает построение дерева, скачивает файл, выполняет его до конца и только потом продолжает. Иначе нельзя: скрипту разрешено писать в документ ровно в этой точке. Это механическая причина двух правил, которые иначе пришлось бы зубрить: скрипты ставят в конец body или помечают defer, а скрипт, запустившийся раньше нужного ему элемента, упадёт — элемента действительно ещё нет.

Параллельно CSS разбирается в собственную структуру — CSSOM, та же идея, только для правил, а не для элементов.

Раскладка и отрисовка: куда уходит время

Теперь у браузера два дерева, и он собирает из них дерево рендеринга: все узлы, которые реально будут видны, с посчитанными для них стилями. Элементы с display: none выпадают именно здесь — они есть в DOM, но не в дереве рендеринга, и потому не занимают места.

Дальше — дорогая часть, две станции:

Раскладка (reflow) вычисляет геометрию: для каждой коробки — какая ширина, какая высота, в каких координатах. Это цепная реакция: абзац, выросший на строку, толкает всё под собой, отчего может измениться высота контейнера, отчего может сдвинуться соседний блок. Поэтому мелкое изменение оказывается неожиданно дорогим.

Отрисовка заливает пиксели: фоны, рамки, текст, тени. Потом композиция складывает слои в правильном порядке и отдаёт результат.

Два практических следствия, которыми ты будешь пользоваться постоянно. Анимация того, что меняет геометрию, — ширины, высоты, top, left — заставляет пересчитывать раскладку в каждом кадре, а при шестидесяти кадрах в секунду у тебя на кадр шестнадцать миллисекунд; так рождается дёрганье. Анимация transform и opacity раскладку не трогает вовсе и идёт гладко. А картинка без объявленных размеров не имеет известной высоты, пока не приедет: браузер раскладывает страницу вокруг коробки нулевой высоты, а потом раскладывает заново, — это и есть прыжок страницы под пальцем на каждом новостном сайте, который ты когда-либо проклинал.

И конвейер идёт заново

Последняя станция — не конец. Страница теперь живая, и у неё один главный поток, который делает всё: выполняет твои скрипты, обрабатывает клики, пересчитывает раскладку, рисует.

Один. Он такой один. Любой JavaScript, работающий 300 миллисекунд, — это 300 миллисекунд, в течение которых ничего не скроллится, ничего не откликается и никакая анимация не двигается. «Страница зависла» почти всегда означает, что одна функция делает слишком много на этом потоке.

И каждый раз, когда твой код меняет что-то видимое, нужные станции отрабатывают снова: поменял текст узла — заново раскладка и отрисовка затронутой части; поменял цвет — только отрисовка. Оптимизировать это в первый день не нужно. Знать, что цикл, тысячу раз трогающий страницу, тысячу раз просит машину сделать дорогую работу, — нужно.

Ты не рисуешь страницу. Ты кормишь конвейер, который нарисует её — и не один раз.

Почему карта дороже приёма

Можно месяцами писать фронтенд, ничего этого не зная, и копировать починки с форумов. Работает ровно до момента, когда что-то повело себя странно, — и тогда думать нечем: каждый баг одинаково удивителен, потому что нет модели, которую он мог бы нарушить.

Сфера «Как учиться» называет это разницей между заученным снимком и настоящим пониманием: один верный ракурс бесполезен, как только ситуация сдвинулась, а понимать — значит уметь менять масштаб, отъезжать к целому конвейеру и въезжать в отдельную станцию, не теряя объект. Эта статья — намеренно первый, дальний план. Всё в следующих двадцати статьях крепится к станции на этом конвейере: HTML строит дерево, CSS кормит стили, раскладка расставляет коробки, JavaScript правит всё это на ходу.

Это ещё и твой первый чанк в этой области. Сейчас «как грузится страница» — девять отдельных шагов, которые пришлось бы перечислять. Через несколько недель работы это станет одним блоком, которым ты думаешь в одно движение, — и только благодаря такому сжатию опытный разработчик удерживает в голове целую систему.

На практике

Посмотри, как грузится настоящая страница. Открой любой крупный сайт, открой панель Network в инструментах разработчика, перезагрузи и разгляди водопад. Найди сам документ, потом стили, потом скрипты, потом картинки. Спроси себя, почему порядок получился именно таким. Десять минут здесь стоят больше главы чтения.

Сначала предскажи, потом смотри. Перед перезагрузкой проговори вслух, что что заблокирует. Потом проверь. Ошибиться, поставив предсказание на кон, учит в разы сильнее, чем получить готовый ответ: это извлечение, а не перечитывание.

Нарисуй конвейер по памяти. На бумаге, без статьи: запрос, HTML, DOM, CSSOM, дерево рендеринга, раскладка, отрисовка, композиция. Пропала станция — ты нашёл, что именно стоит перечитать. Нарисуй ещё раз через три дня и через неделю: правило интервалов из сферы «Как учиться» работает и для схемы, а не только для слов.

Разложи поведение страницы по трём входам. Возьми сайт, которым пользуешься ежедневно, и выпиши пять вещей, которые он делает: что здесь HTML, что CSS, чему нужен JS? Там, где не можешь определить, — следующее, что стоит выучить.

Объясни кому-нибудь. Две минуты, вслух, без заметок. Объяснение развалится ровно там, где разваливается понимание, — самая дешёвая диагностика во всей сфере.

Проверь себя

Закрой статью и ответь своими словами:

  1. Какие три входа принимает браузер и за что отвечает каждый?
  2. Что такое DOM и чем он отличается от твоего HTML-файла?
  3. Почему обычный тег script останавливает парсер и что из этого следует про место скриптов?
  4. В чём разница между раскладкой и отрисовкой и почему одна дороже другой?
  5. Почему анимация left дёргается, а анимация transform — нет?
  6. Что значит «главный поток один» и как это выглядит для пользователя?
  7. Почему картинка без объявленных размеров заставляет страницу прыгать?

Коротко

  • Между адресом и пикселями идёт жёсткая последовательность: запрос, HTML в DOM, CSS в CSSOM, дерево рендеринга, раскладка, отрисовка, композиция.
  • HTML — что это такое, CSS — как выглядит, JavaScript — что происходит, и «кто из троих должен это делать» — еженедельное решение.
  • Парсер строит дерево по мере поступления; стили блокируют отрисовку, обычные скрипты — разбор, отсюда правила про место скрипта и defer.
  • В дерево рендеринга попадает только видимое, поэтому display: none не занимает места.
  • Раскладка считает геометрию и расходится каскадом по странице, отрисовка заливает пиксели. Анимируй transform и opacity, а не width и left.
  • У страницы один главный поток на скрипты, события, раскладку и отрисовку — длинная функция подвешивает всё.
  • Картинки без размеров дают сдвиг вёрстки: высота неизвестна, пока файл не приехал.
  • Учи карту раньше приёмов: это дальний план, к которому крепится каждая следующая статья, и твой первый настоящий чанк в этой области.