Думайце працэйна модель таго, як прыгледчыкі атрымляюць сторанкі: падход на недзейнасць
Навучыцеся аналізаваць процес адражэння кантэнта ў прыгледачах, запытваючыся, якія змены парадоксалізуюць цей процес, — ад проблем з распалоўкай лейауту і вартасцю адрасавання кантэнта да процэсаў композіцыі, гідратацыі і стрімавання.
Крыніцы: The Web Page Rendering Process In Browsers: A Deep Dive
Практычна кожны разработчык фронтэнду можа пераказаць процес атрыбутавання: HTML прыводзіцца да DOM, CSS — да CSSOM, а пасля выконваюцца крокі з распакоўкі макета, атрыбутавання кольору і суплекцыі. Гэтыя крокі ўтвараюць гарны вступ, але яны майже бесполезны, калі рэальная сторана працюе з перашкодамі, адтолькі ў яных описваецца адзін процес, тады калі браузеры постаўляюць частковыя, павтаральныя процесы працы. Шэйдзер заменяе запамятаваны список на рабочую модэль, створаную навакол адной пытанні, так што вы можете поясніць проблемы з макетам, повольнае атрыбутавання кольору, некачэнія анімацый і затрымкі падчас гідратацыі, вядучыся тымі ж аргументамі, і параболіцаваць своія прыпускі за дапамогою DevTools.
Атрыбутавання ўсё час адбываецца, а не як адзін разовы процес
Старонка ніколы не адрадзіваецца раз і не застаецца без змян. Скрыпты мутуюць DOM, змены класаў прагледваюць стылі, адпраўкі і веб-шрыфты прыходзяць пазней, корыстувальнікі перамешчаюць курсор і прасуваюць старонку, анімазыі выкананыя ў кожным кадры, а фрэймворкі перадрадзіваюць компаненты пасля змян стану. Кожны з гэтых запускаець механізм адрадзівання на адпаведнае запытанне:
Што больш не ўсталявана, і скількі роботы трэба перадзрабатваць?
Згадкай пра гэтае запытанне важлівей, чым знаўство назв стадзій. Гэта прычына, чаму два застосункі, якія выглядаюць ідэнтычна, можу працаваць аблічна на однам тэлефоне: адны продовжваюць выканаваць дорогія перысчытанні, а іншыя большасцю выкарыстоўваюць ту роботу, яку браузер можа перызнаць.
Першы крок: браузер стварае внутранія модэлі
Калі прыходзіць HTML, парсер ператварае яго ў DOM. DOM — гэта не адпрацоўваны варыянт стораніцы. Це дрэва элементаў, атрыбутаў і тэкстовых вузлаў, а таксама зв’язкі между бацькімі і сяброўскімі вузламі. Возьмем маленькі фрагмент маркапаў:
<main>
<h1>Browser Rendering</h1>
<p>Understanding what happens after HTML arrives.</p>
</main>
Парсер ператварае яго ў дрэва, якое двыжак можа адзначаць і апдэйтаваць:
Document
└── main
├── h1
│ └── "Browser Rendering"
└── p
└── "Understanding what happens after HTML arrives."
Шаблоны стылю праходзяць свой процес адпрацоўкі. Браузер парсае іх, прыменяе правіла каскадавання і спадчынства, выявляе, якія правіла падходзяць да кожных элементаў, і вырахоўвае фінальную значэння кожной власці для кожного элемента. На гэты момент нічога не было надрукавана. Двыжак скарабатывае стан, і яму патрэбны як структура, так і вырахаваныя стылі, перш чым можна будзе визначыць геаметрыю. Схема, адпраўленая да залежнасцяў, выглядае так:
HTML ──→ DOM ───────────┐
├──→ Style + Layout ──→ Paint ──→ Composite
CSS ──→ Styles ────────┘
Спрытваеце тую картынку як карту залежнасцей, а не як опис рэалізацыі. Рэальныя двыгуны маюць значна большае лічба внутрашняях стадзій, кэшаў, структураў для адстежэння недзейнасці, калькольных потакоў і спецыфічных для платформы скораходоў. Тое, што правільна паказвае дыяграма, — гэта тое, якія выходны данны залежаць ад калькольных. Якщо вы хочаце болей дакладна пазнакоміцца з тым, як значэнні стылю адначынаюцца перад форматаванням, наша стаття пра месца CSS у прагоне браузера дае болей дэталізаваную інфармацыю.
Форматаванне — это вычысленне залежнасцей
Колі є DOM і вычысленыя стылі, браузер вычысляе геаметрыю. Форматаванне павінна раз’ясніць такія пытанні, як:
- Якія ў шыроце і высоце кожны блок?
- Дзе розмешчаны кожны блок?
- Дзе текст пераходзіць на новы ряд унутры контейнера?
Апошныя запитанні ўскладняюць процес форматавання. Падазроўваючы, што абарот стане вышэй чераз збільшэння размеру шрыфта, усе, што знаходзіцца пасля яго, можа патрабаваць перасунуцца вярху, а высота контейнера можа зменіцца, што паўтарыцься на яго суседзяў. Змена, якая здаецца локальной, може распрострацівацца на вялікую частку документа. Самэльга таму карыстна спрыяваць форматаванню як рашэнню прычыно-наследковых звязкаў, а не як размішчэнню касет на паверхні.
Механізмы стараюцца обмежыць перыяканні толькі афектаванай паддрэсе, але ступень выконваных задач залежыць ад типу змены і ад таго, як элементы взаімаюцца межа сабой. Напрыклад, кантэнт у контейнеры з фіксаваным размерам зазвычай меней паўтарыцься, чым кантэнт у контейнеры з автоматычным размерам.
Анулювання мае большое значэнне, чым весь процес обробкі
Уявіце, што скрыпт выкананы card.style.width = „500px“. Браузер тады розумее, што яго пярэйшая геаметрыя для тагу card, а можа і для элементаў падалей, можа быць застарэлай. Першы чым прыйдзе наступны кадр, ён должен выявіць, якія етапы трэба перывыканы. У спрасцаванай форме:
Something changes
│
▼
What does it invalidate?
│
├── Style
├── Layout
├── Paint
└── Composite
Ключовыя моменты — гэта тое, што не всі змены праходзяць па весьшым шляху процесу обробкі:
- Новая шырокасць змінюе геаметрыю, таму перывыканы лейаут, а пасля зазвычай перамальовываецца паўстаноўленая зона.
- Новы колер фону змінюе выгляд без перамешчання якога-лібо элемента, таму патрэбна перамальовка, але лейаут зазвычай не патрэбен.
- Новая значэнне
transformчаста можна застосавіць з значна меншым напроцэсам, але толькі калі рашэння браузера па шарах і абстоячныя умовы дазволяюць обрабаваць апдэйт на этапе композіцыі.
Самэткі такі загальныя парады, як "вадзіцеся ад перамальвання" чыя "выкорыстоўваеце толькі GPU", на практыцы не работаюць. Корыстным діаганастычным запитаннем завжды ёсць: якія дзеяння былі знеасабліваны гэтай конкретной змянай? Наша статыя на тэму колькі кожная змяна CSS коштуе браузеру апісвае распашчытыя властывасці па стадіях, якія яны зазвычай спрычыняюць.
Як JavaScript прымусвае рэндэрінг выконвацца раней
Пашымы спосаб з’яўлення дорогага рэндэрінгу праз абыякавасць — гэта перакладчык запісаў і чытанняў лэйауту. Ніжчэй паказаны цикл, які задае шырыню кожнай картцы і адразу ж запрашвае ўзятую шырыну гэтай карткі:
for (const card of cards) {
card.style.width = "500px";
const width = card.getBoundingClientRect().width;
console.log(width);
}
Сама функцыя getBoundingClientRect() не мае нічых проблем. Цэта звычайная, корыстная API. Проблема выклікаецца парадоксам у порядку выканання операцый. Змена стылю заставляе роботу з распаковкай элемента застацца незавершанай, а наступныя рядкі просягаюць значэння, якое можа быць правильным толькі пасля завершэння ціў гэтай роботы. Браузер не мае іншага выбору, кроме як выканаваць задачі з стылю і распаковкай сінхронна, прычаму ў той жа момент, перш чым вернуць число. У цікле выступае такі патэрн, вядомы як layout thrashing: запіс, чытанне, запіс, чытанне, прычаму ў кожной ітерацыі выканана прымусовая распаковка.
Пры пераструктураванні коду так, каб усе чытання выканаваліся першыя, а ўсе запісы — пасля, прымусовая сінхронізацыя зникае:
const widths = cards.map(card =>
card.getBoundingClientRect().width
);
for (const card of cards) {
card.style.width = "500px";
}
Цякож виміроўванні выконваюцца на адной, вялікай часткай валідным макете, а запісы залишаюцца для обробкі браузерам пад час наступнай відрисовкі. Заўважыце, што гэтыя два варіянты не ўсё ці павнаста эквівалентныя: у другам варыянце шырыні фіксуюцца да змены, таму выкарыстоўвайце такую перакантролю толькі тады, калі гэта справды неабходна. Якщо чытанне практычна залежыць ад паказванага раней запісу, звычныя методы — групаваць запісы і планаваць чытанне на наступную кадру, напрыклад, за дапамогою requestAnimationFrame.
Абсалютным урокам няма — «ніколи не вылічвайце лейаут». JavaScript і процес атрыбуцыі маюць аднаковы поток і аднаковыя даны, таму ваш код можа стварыць залежнасці, якія прыводзяць браузер да таго, каб ён негайна атрыбуціяў тое, што інакша мог бы запланаваць і атрыбуціяваць эфектыўна. У проблемы з лейаутам таксама є іншыя тонкія факторы, якія мы розглядаем у жывых колекцыях DOM і проблемах з лейаутам.
Процес атрыбуцыі — гэта аднае цэны, не залежная ад лейауту
Колі геаметрыя вядома, браузер усё рава должен вылічыць, як выглядае кожны рэгіон: фоны, рамкі, гіфікі тексту, адпраўкі, цені, градыенты, эфекты адсечкі і іншыя ефекты. Компактны спосаб раздзеліць гэтыя два этапы:
- Лейаут вяршыць, дзе знаходзяцца элементы і які ў іх розмер.
- Процес атрыбуцыі вяршыць, як выглядаюць гэтыя элементы.
Змена значэння background-color зазвычай не выклікае патрэбу ў новай геаметрыях для всьго документа, тады як змена размеру чы абстановкі можа гэта выклікваць. Аднак не трэба думаць, што це безкоштовна праця. Адражэнне вялікіх плошч, наступлення такіх эфектаў, як вялікія розмытые цені, частая змена контэнту і чрэзмерна шырока ануляцыя можа значна падвысіць час адражэння, што спрыяе затрыву падачы кадра.
Растераванне і композіцыя ствараюць фінальны кадр
Процес адражэння стварае інструкцыі для падрашчання, а не безпосередзя фінальныя пікселі. Штоўней гэтыя інструкцыі растераваюцца у піксельныя даны, якія часта дзелююцца на плиткі і обрабоўваюцца за дапамогою графічнага стеку платформы. Пасля чога процес композіцыі складае отрыманыя поверхні, або слойвы, у кадр, які выказваецца на экране.
Этап, на яком transform і opacity здобывають свою популярнасць. Калі умовы правильныя, анімацыя гэтых атрыбутаў дазволяе элементу рухацца, масштабавацца або згасаць без неабяжнага перысчытвання лей아уту докумэнта чы рэпісавання ўмесцоў элемента; браузер проста перакомбінавае існуючыя шары.
Популярны скрацанне „transform працюе на GPU, таму ён завжды быстрый“ адкрывае справжнія затраты, пры тым часу:
- Кожны дапаможні шар спрацоўвае памяць, што мае значэнне на прыстроях з нізкімі характэрыстыкамі.
- Умесцы все рава трэба растераваць у певны момант, а растераванне можа быць даскладным.
- Тое, чы рэштаець элементу свой шар, залежыць ад правіл браузера і контэкста атрыбутавання, таму той самы CSS можа вестися аднародна ў разных ситуацыях.
Ўжытныя правілы простейшыя за будзь ліст атрыбутаў: змена, якая выключае менш элементаў, дазволяе дваржыку перасчыляць меньш часу на вырахунакі.
Бюджет канструкцыі дзеяўна дзеліцца
Экран з частотай 60 Гц аднаўляе змест прыблізна кожныя 16,67 мілісекунды, таму што 1000 / 60 дэйваеся прыблізна 16,67. Пры частоте 120 Гц гэты інтэрвал зменшываеся да прыблізна 8,33 мілісекунд.
Гэты інтэрвал не ўсьмо час CPU, выдзелены для вашага коду. У ўсём гэтым часе браузер должен адрабатваць выкананне скрыптав, расчытак стылю, падзеюкі лейауту, аплётку зместу, растерызацію, композіцыю, обработку вводу, вызовы з сеті і свае сопрацоўнічкі функцыі. Таму адна дугая задача на JavaScript можа спамятваць процес аплёткі, нават калі сама робота з аплёткай была бы простая.
Гэта таксама прычына, чаму такія паказнікі, як FCP (First Contentful Paint) і INP (Interaction to Next Paint), існуюць окалічна. Шырокая прадстаўленне контэнту і шырока адпаведнае дзейства на ввод — гэта супаўзяныя цілі, але яны не выпанавляюцца з разных прычын і трэбуе меравання окалічна.
Месца стварэння UI і яго вплыв на вартасць адрасавання
Перш чым прыстрой перадае ўсё, архітэктурны выбар вялікая мерой вплывае на колькасць роботы, якая яму даведзеся выканаць: дзе ствараецца пачатковы UI?
Адрасаванне з боку кліента
Пры CSR сервер адправляе мінімальную HTML-структуру і код прыемлівай программы, а сам прыстрой выканае большую частку роботы:
Server
│
├── HTML shell
└── JavaScript + data
│
▼
Browser
│
├── Parse JS
├── Execute JS
├── Fetch data
├── Build UI
└── Render
Адрасаванне з боку сервера
Пры SSR запошчанне дадзейнаў, логіка прыемлівай программы і стварэнне HTML адбываюцца на сервере прычаму да адправкі адпаведнай адпаведзі:
Request
│
▼
Server
│
├── Fetch data
├── Execute application logic
└── Generate HTML
│
▼
Browser
│
▼
Parse + Render
SSR можа якнайшырэй распрацавіць значаменны маркап і адправіць яго да браузера, тады калі CSR перакладае вычысленні і атрыбутаванне на апаратную частку корыстніка. Ні адна з гэтых методаў не є кращай у загальным сенсе. Правы выбар залежыць ад таго, насколькі складны є прыстрой, якія даныя ёму патрабуюцца, насколькі добра можна кэшаваць адпаведзі, якія прыстроі выкарыстоўваюць корыстнікі, і сколькі JavaScript усё-такі павінна запрацоўваць у браузеры.
Гідратацыя: візуальна готовасць — не значыць фактычная готовасць
Багатыя сучасныя фрэймворкі спалююць гэтыя два падходы за дапамою гідратацыі. Сервер адправляе HTML, які можна негайна вывесіць, а JavaScript на стороне кліента запускае прыстрой і додае адпаведныя обрабнікі зманоўкаў для взаімадзеяння:
Server HTML
│
▼
Browser renders content
│
▼
JavaScript loads
│
▼
Application initializes
│
▼
Hydration
│
▼
Interactive UI
Наследкіў гэтага легка не зазначыць: стораніца можа выглядаць готовай задоўжшы час пры тым, як ёй не вдаецца адпаведаць на клік. Процес заполнення стораніцы можа включаць завантажэнне, парсаванне, компіляванне та выконання великай колькасці коду JavaScript, ініцыялізацію компонентаў та налаштаванне слухачаў, усё гэта на галоўнай вялінцы. Таму большыя дапыткі можу зробіць так, што контэнт будзе праказвацца быстрей, але водночас стварыць вялікую навантажэння на галоўную вялінцу саме тады, калі корыстувальнікі пачынаюць нажымаць на елементы.
Гэта протыярыса пояснюе ныякі напрамак стратэгій рендаровання: апрабоўкі меншага колькасці коду JavaScript на кліянтэ, роздзелення пакетаў дапыткаў та актывацыю толькі тых частак стораніцы, якія даслівна патрабуюць інтерактыўнасці, а не всей структуры стораніцы заодно.
Змены пад час стрімавання, калі праця запускаецца
У класычным SSR усі данні для адпаведзі ствараюцца ўсё раней, пры тым як жадныя з іх не адправляюцца. У стрімаванні HTML адправляецца пакетамі, калі толькі стаюць доступныя:
Server
│
├── HTML shell ──────────→ Browser
├── Content ─────────────→ Browser
├── More content ────────→ Browser
└── Remaining response ──→ Browser
Браузер можа парсаваць і адраджаваць першыя часткі кантэнту, пакуль сервер яшчэ генеруе рэшту. Стрыімаванне не робіць адраджаванне ў цэлым дешэвейшым. Яно зменшвае момент, калі можа пачацца корыстная праца, што можа скорасіць час ад навігацыі да видимага кантэнту. Прогрэсіўная гідратацыя прыменяе тую ж ідею на кліентзе, распраўляючы ініцыялізацыю працы па часу замест таго, каб актываціяўць усю прыкладку за аднам разам.
Спостерагаць за процесам, а не запамятаваць яго
Найшырэйшы спосаб развіць інтуўіцыю — гледзець на браузер безпосередна. Ачыніце панель Performance у Chrome DevTools, запісаце адно зі взаімадзейнаўанняў і пераканайцеся, што запускаецца, калі зменяецца UI. Потым створыце вымушаную перакроіць паляву, задаўшы шырыню і адразу ж прачытавшы геаметрычныя данні:
element.style.width = "600px";
element.getBoundingClientRect();
Запісаце знову, але з актуалізацыяй, якая зменяе толькі параметры трансфармацыі:
element.style.transform = "translateX(100px)";
Мета завдання не ў тым, каб адну з власнасцей прызначыць як хорашую, а іншую — як паслабленую. Для кожнага запису неабходна адпаведна працэва над запытаннямі:
- Чы быў перасчытан стыль?
- Чы запускаўся макет і чы ён быў змушаны за дапамогою скрыпты?
- Чы якая-небудзь зона была перамальована?
- Чы відбывалася растерызацыя?
- Чы можна было б усё адправіць на обробку композітару?
Калі вы будете чытаць лог-даныя такім спосабам, панель Каркаснасці перестае быць інструментам для дэбаггіну ў крайнім выпадку і стае спосабам адразу бачыць, як двіжак реагуе на кожную лінію вашага коду.
За межами шасцяхэтнага дыяграмы
Текстовая схема працы заўсёды корисна знать:
HTML → DOM → CSSOM → Layout → Paint → Composite
Рэальная система ўсё багатшая. Энжын зусім час трывога кілька супяжаных форматаванняў у сінхронны стан: ад дрэва элементаў і вырачаных стыляў чераз геаметрыю касцоў і інструкцыі для адрасавання да растэровых плитак і складзеных слоёў. Змена рэдка вымагае перзбудавы ўсіх іх. Система выявляе, што застаўся чынным, пазначае тое, што стало неактуальным, і перадаўляе все, што можа.
Ключовыя выводы
- Заменіце пытанне "Якую оптымізацыю мне застосаваць?" на "Што змянілася, што стало неактуальным, і чаму браўзер павінен быў перадарабатваць гэту роботу?"
- Распакоўкі — это проблема залежнасцяў; змены, якія выглядаюць локальнымі, можу распрострацівацца па всім докумэнту.
- Адгукаваныя чытанні і запісы вымагаюць сінхронной распакоўкі; роздзеліце іх або адкладзіце чытанні на наступную кадру.
transform і opacity є дешавымі толькі тады, калі шары і умовы супрацоўваюць разам.