DevGang
Авторизоваться

Давайте глубже погрузимся в свойство CSS Container

По сравнению с прошлыми, современные браузеры стали действительно эффективными для рендеринга запутанной сети HTML, CSS и JavaScript-кода, который предоставляет типичная веб-страница. Требуется всего миллисекунды, чтобы преобразовать код который мы создаем в то, что люди могут использовать.

Что мы, как разработчики внешнего интерфейса, могли бы сделать, чтобы на самом деле помочь браузеру быть еще быстрее при рендеринге? Существуют обычные практики, которые легко забыть с помощью нашего современного инструментария, особенно в тех случаях, когда мы можем не иметь такого большого контроля над сгенерированным кодом. Мы могли бы контролировать наш CSS, например, с меньшим количеством более простых селекторов. Мы могли бы держать наш HTML под контролем; держать дерево более плоским с меньшим количеством узлов, и особенно с меньшим количеством потомков. Мы могли бы держать наш JavaScript под контролем, будучи осторожными с нашими манипуляциями HTML и CSS

На самом деле современные фреймворки, такие как Vue и React, очень помогают в этой последней части.

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

Это свойство называется contain. Вот как MDN определяет это свойство: 

"Свойство CSS contain позволяет автору указывать на то, что элемент и его содержимое, насколько это возможно, не зависят от остальной части дерева документа. Это позволяет браузеру пересчитывать макет, стиль, цвет, размер или любую их комбинацию для ограниченной области DOM, а не всей страницы, что приводит к очевидным преимуществам производительности."  

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

Браузер не может предсказать будущие изменения макета веб-страницы, которые могут произойти из-за вставки и удаления контента JavaScript на странице. Даже простые вещи, такие как вставка имени класса в элемент, анимация элемента DOM или просто получение измерений элемента, могут вызвать переформатирование и перерисовку страницы. Такие вещи могут быть затратными и их следует избегать или по крайней мере уменьшить их настолько насколько это возможно.

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

Различные способы содержания

Свойство contain имеет три значения, которые могут быть использованы по отдельности или в комбинации друг с другом: sizelayout и paint. Он также имеет два сокращенных значения для общих комбинаций: strict и content. Давайте рассмотрим основы каждого.  

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

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

Размер содержания

Сдерживание size на самом деле просто объяснить. Когда контейнер с этим содержимым участвует в вычислениях макета, браузер может пропустить немного, потому что он игнорирует дочерние элементы этого контейнера. Ожидается, что контейнер будет иметь заданную высоту и ширину; в противном случае он разрушается и это единственное, что учитывается при разметке страницы. Считается, что в нем нет никакого содержания. 

Учтите, что потомки могут влиять на свой контейнер с точки зрения размера в зависимости от стилей контейнера. Это необходимо учитывать при расчете макета с сдерживанием size это скорее всего не будет рассматриваться. Как только размер контейнера будет определен по отношению к странице, будет рассчитан макет его потомков.

Сдерживание size на самом деле не дает много возможностей для оптимизации. Обычно он сочетается с одним из других значений. 

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

Вот совершенно надуманный пример этой концепции цикла изменения размера:

В этом примере, нажатие кнопки start приведет к тому, что красное поле начнет расти в зависимости от размера фиолетового родительского поля, плюс пять пикселей. По мере того как фиолетовое поле настраивается по размеру, наблюдатель изменения размера говорит красному квадрату снова изменить его в зависимости от размера родителя. Это приводит к тому, что родитель снова изменяет размер и так далее. Код останавливает этот процесс, как только родитель получает более 300 пикселей, чтобы предотвратить бесконечный цикл.

Кнопка сброса, ставит все обратно на место.

Щелчок флажка set size containment устанавливает размер сдерживания на фиолетовом поле. Теперь, когда вы нажмете на кнопку start, красное поле будет изменяться в зависимости от ширины фиолетового поля. Правда, оно переполняет родительский элемент, но суть в том, что оно изменяет размер только один раз и останавливается, цикла больше нет.

Если вы нажмете на кнопку resize container, фиолетовое поле станет шире. После задержки красный прямоугольник изменит свой размер соответствующим образом. При повторном нажатии кнопки, фиолетовое поле возвращается к своему первоначальному размеру, а затем красное поле снова изменяется.

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

План размещения

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

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

"Например, если содержащее поле находится рядом с концом блочного контейнера, а вы просматриваете начало блочного контейнера"

Контейнер с layout содержимым становится контейнером для размещения absolute или размещения потомков fixed. Это было бы так же, как применение позиции relative к контейнеру. Так что имейте в виду, как потомки контейнера могут быть затронуты при применении этого типа сдерживания.  

В аналогичном случае контейнер получает новый контекст укладки, поэтому z-index можно использовать так же, как если бы была применена relative, absolute или позиция fixed. Несмотря на это, установив toprightbottom или left свойства не будут оказывать никакого влияния на контейнере.  

Вот простой пример этого:

Нажмите на поле, и содержание layout будет переключено. Когда применяется сдерживание layout, две фиолетовые линии, которые находятся в абсолютном расположении, сместятся внутрь фиолетовой рамки. Это связано с тем, что фиолетовая коробка становится блоком с защитной оболочкой. Еще одна вещь, которую стоит отметить, это то что контейнер теперь укладывается поверх зеленых линий. Это связано с тем, что контейнер теперь имеет новый контекст стекирования и соответствующим образом следует этим правилам. 

Paint containment

Сдерживание paint сообщает браузеру, что ни один из дочерних элементов контейнера никогда не будет окрашен за пределами его размеров. Это похоже на размещение на контейнере overflow: hidden;, но с некоторыми отличиями.  

С одной стороны, контейнер получает ту же обработку, что и при содержании layout, он становится содержащим блоком со своим собственным стековым контекстом. Таким образом, размещение дочерних элементов paint внутри контейнера будет уважать его с точки зрения размещения. Если бы мы дублировали демонстрацию layout сдерживания выше, а вместо этого использовали сдерживание paint, результат был бы почти таким же. Разница заключается в том, что фиолетовые линии не будут переливаться через контейнер при применении защитной оболочки, а будут обрезаться у контейнера border-box.  

Еще одним интересным преимуществом сдерживания paint является то, что браузер может пропускать потомков этого элемента в вычислениях рисования, если он может обнаружить, что сам контейнер не виден в области просмотра. Если контейнер не находится в окне просмотра или каким-либо образом скрыт, это является гарантией того, что его потомки также не видны. В качестве примера, представьте навигационное меню, которое обычно находится за пределами экрана слева от страницы, и оно скользит при нажатии кнопки. Когда это меню находится в обычном состоянии вне экрана, браузер просто пропускает попытки нарисовать его содержимое. 

Контейнеры работают вместе

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

Поскольку каждый из них обеспечивает различные оптимизации, имеет смысл объединить некоторые из них. Спецификация фактически это учитывает. Например, мы могли бы использовать layout и paint вместе в качестве значений свойства содержащегося следующим образом:

.el {
  contain: layout paint;
}

Поскольку это очевидная вещь, спецификация предоставляет два сокращенных значения:

Shorthand Longhand
content layout paint
strict layout paint size

Значение content будет наиболее распространенным для использования в веб-проекте с несколькими динамическими элементами, это несколько контейнеров, содержимое которых изменяется со временем или в результате действий пользователя.  

Значение strict будет полезно для контейнеров с определенным размером, который никогда не изменится, даже если меняется содержимое. Как только он окажется на месте, он останется прежнего размера. Простым примером этого является div, который содержит сторонний, внешний, рекламный контент в отраслевых измерениях, который не имеет отношения к чему-либо еще на странице с точки зрения DOM.  

Преимущества производительности

Эту часть статьи сложно объяснить. Проблема в том, что визуальные преимущества в производительности невелики. Большинство преимуществ - это скрытые оптимизации, которые помогают браузеру решить, что делать с изменением макета или раскраски.

В качестве попытки показать преимущества contain, для производительности свойства я привел простой пример, который изменяет элемент font-size с несколькими дочерними элементами. Такие действия обычно вызывают изменение макета, что приводит к перерисовке страницы. Пример охватывает содержание значения nonecontent и strict.

Переключатели изменяют значение содержимого property, применяемого к фиолетовому прямоугольнику в центре. Кнопка change font-size переключает содержимое font-size фиолетовой рамки, переключая классы. К сожалению, это изменение класса также является потенциальным триггером для изменения макета.  

Мой совершенно ненаучный процесс состоял в том, чтобы выбрать тип содержимого, начать запись производительности в инструментах разработчика Chrome, нажать кнопку, дождаться изменения font-size, а затем остановить запись примерно через секунду или около того. Я сделал это три раза для каждого типа содержания, чтобы иметь возможность сравнивать несколько записей. Числа для этого типа сравнения даны в миллисекундах, но разницы достаточно, чтобы почувствовать преимущества. Числа потенциально могут сильно отличаться в более реальной ситуации.  

Но есть несколько вещей, которые нужно отметить, кроме простых чисел.

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

Журнал событий без содержания
Журнал событий без содержания

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

Выполнение поставленной задачи

Вот общее время для трех типов защитной оболочки, по три запуска в каждом:

Containment Run 1 Run 2 Run 3 Average  
none 24 ms 33.8 ms 23.3 ms 27.03 ms
content 13.2 ms 9 ms 9.2 ms 10.47 ms
strict 5.6 ms 18.9 ms 8.5 ms 11 ms

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

Итак, по этим числам вы можете начать видеть, что свойство contain помогает браузеру рендериться более эффективно. Теперь представьте, что мое небольшое изменение умножается на множество изменений, внесенных в DOM и стилизацию типичной динамической веб-страницы.  

Разложи один раз, нарисуй дважды

Я собираюсь использовать вышеприведенную демонстрацию в качестве основы для следующих описаний. Если вы хотите следовать дальше, то перейдите к полной версии в демо и откройте DevTools. Обратите внимание, что вы должны открыть детали “фрейма”, а не “основной” временной шкалы, как только вы запустите инструмент производительности, чтобы увидеть то, что я собираюсь описать.

Отображение деталей фрейма открыто и основные детали закрыты в DevTools
Отображение деталей фрейма открыто и основные детали закрыты в DevTools

На самом деле я делаю скриншоты из версии “fullpage”, так как DevTools лучше работает с этой версией. Тем не менее, обычная “full” версия должна давать примерно такую же идею.

Событие paint срабатывало только один раз в журнале событий для задачи, которая вообще не имела сдерживания. Как правило, событие не занимало слишком много времени, начиная от 0,2 мс до 3,6 МС. Чем глубже детали, тем интереснее оно становится. В этих деталях он отмечает, что область краски была целой страницей. В журнале событий, если вы наведете курсор на событие paint, DevTools даже выделит область окрашенной страницы. Размеры в этом случае будут соответствовать размеру видового экрана вашего браузера.

Нарисуйте детали события
Нарисуйте детали события

Обратите внимание, что область страницы слева на изображении выделяется даже за пределами фиолетового поля. Справа - размеры краски на экране. Это примерно размер области просмотра в этом случае. Для будущего сравнения отметьте #document как корень слоя.  

Имейте в виду, что браузеры имеют концепцию слоев для определенных элементов, чтобы помочь с рисованием. Слои обычно предназначены для элементов, которые могут перекрывать друг друга из-за нового контекста укладки. Примером этого, является способ применения position: relative; и z-index: 1; к элементу, заставит браузер создать этот элемент в качестве нового слоя. Свойство содержимого имеет тот же эффект. 

В DevTools есть раздел, который называется «рендеринг», и он предоставляет различные инструменты, чтобы увидеть, как браузер отображает страницу. При выборе флажка «Границы слоя» мы можем видеть разные вещи в зависимости от содержания. Если содержимое отсутствует, вы не должны видеть никаких слоев, кроме типичных статических слоев веб-страниц. Выберите content или strict и вы увидите, что фиолетовое поле преобразуется в свой собственный слой, а остальные слои соответственно меняются.  

Слои без содержания
Слои без содержания
Слои с защитной оболочкой
Слои с защитной оболочкой

Это может быть трудно заметить на изображении, но после выбора содержания content, фиолетовый прямоугольник становится его собственным слоем, а страница имеет сдвиг в слоях за прямоугольником. Также обратите внимание, что на верхнем изображении линия слоя пересекает верхнюю часть поля, а на втором изображении линия слоя находится ниже рамки.  

Я упоминал ранее, что content и strict заставляет краску срабатывать дважды. Это потому, что два процесса рисования выполняются по двум различным причинам. В моей демонстрации первое событие относится к фиолетовому ящику, а второе - к содержимому фиолетового ящика.  

Обычно первое событие окрашивает фиолетовую рамку и сообщает размеры этой рамки как его часть. Коробка теперь является своим собственным слоем и пользуется теми преимуществами, которые применяются.

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

Первое событие рисования с содержимым
Первое событие рисования с содержимым
Второе событие рисования с содержимым
Второе событие рисования с содержимым

Обратите внимание на разницу в размерах справа на обоих изображениях. Кроме того, корень слоя для обоих этих событий main.change, вместо видимого выше #document. Фиолетовый прямоугольник - это элемент main, поэтому только этот элемент был нарисован, а не весь документ. Вы можете видеть подсвеченное поле, а не всю страницу.  

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

Опять же, это можно увидеть в демоверсии.

Вернемся к той вкладке рендеринга. На этот раз вместо этого проверьте “Scrolling performance issue”. Когда для сдерживания установлено значение none, Chrome закрывает фиолетовую коробку накладкой с надписью "repaints on scroll."

Если вы хотите, чтобы это произошло вживую, установите флажок «Paint flashing».

Обратите внимание: если мигание цветов на экране может представлять для вас проблему, пожалуйста, не устанавливайте опцию «Paint flashing». В примере, который я только что описал, не так много изменений на странице, но если оставить этот сайт проверенным и посетить другие сайты, то реакции могут быть другими.

При включенном мигании краски вы должны видеть индикатор краски, покрывающий весь текст в фиолетовом поле всякий раз, когда вы прокручиваете его внутри. Теперь изменение локализации content и strict, а затем снова прокрутка. После первой начальной вспышки краски он никогда не должен появляться снова, но полоса прокрутки действительно показывает признаки рисования во время прокрутки.

Вспышка краски включена и прокрутка без сдерживания
Вспышка краски включена и прокрутка без сдерживания
Краска мигает и прокручивается с содержимым
Краска мигает и прокручивается с содержимым

Также обратите внимание, что наложение ”repaints on scroll" исчезло на обеих формах сдерживания. В этом случае сдерживание дало нам не только некоторый прирост производительности в живописи, но и в прокрутке.

Интересное случайное открытие

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

Контейнер был помечен наложением «repaints on scroll», а вспыхивающая краска была такой же, как и защитная оболочка, хотя я точно знал, что на контейнер content накладывается защитная оболочка. Поэтому я начал сравнивать свой простой тест с более стилизованной версией, которую я обсуждал выше. 

В конце концов я увидел, что если контейнер background-color прозрачен, то преимущества производительности прокрутки содержимого не произойдет.  

Я запустил аналогичный тест производительности, где я изменил бы содержимое font-size, чтобы вызвать изменение макета и перекрасить его. Оба теста имели примерно одинаковые результаты, с той лишь разницей, что первый тест имел прозрачный background-color  цвет, а второй тест имел правильный цвет фона.  Похоже, элемент не становится собственным слоем в вычислениях краски с прозрачным background-color.   

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

Я сделал еще одну версию тестовой демонстрации, которая меняет элемент контейнера background-color с прозрачного на тот же цвет, который используется для цвета фона тела. Вот два скриншота, показывающие различия при использовании различных параметров на панели рендеринга в DevTools.

  Панель рендеринга с прозрачным background-color
  Панель рендеринга с прозрачным background-color

Вы можете увидеть флажки, которые были выбраны, и результат. Даже при наложении сдерживания содержимого в коробке есть “repaints on scroll", а также зеленый наложенный слой, показывающий рисование во время прокрутки.

  Рендеринг панели с background-color   
  Рендеринг панели с background-color   

На втором изображении вы видите, что установлены те же флажки, и результат отличается от контейнера. Наложение «repaints on scroll» исчезло, зеленое наложение для рисования также исчезло. Вы можете видеть наложение paint на полосе прокрутки, чтобы показать, что оно было активным.

Вывод: не забудьте применить некоторую форму цвета фона к вашему контейнеру при нанесении защитной оболочки, чтобы получить все преимущества.

Заключение

В этой статье были рассмотрены основы свойства contain CSS с его значениями, преимуществами и потенциальным приростом производительности. Есть несколько превосходных преимуществ применения этого свойства к определенным элементам в HTML. Какие элементы нужно применить, решать только вам. Общая идея заключается в том, чтобы применять его к элементам, которые являются контейнерами других элементов, особенно с элементами динамического аспекта.  

Некоторые возможные сценарии: области CSS-сетки, элементы, стороннее содержимое и контейнеры с динамическим содержимым основанным на взаимодействии с пользователем. В этих случаях не должно быть никакого вреда при использовании свойства, если вы не пытаетесь содержать элемент, который на самом деле каким-то образом полагается на другой элемент за пределами этого содержимого.

Safari - единственная компания, которая удерживается на этом этапе. Вы все равно можете использовать свойство независимо, потому что браузер просто пропускает этот код без ошибок, если он не понимает свойство или его значение.

Источник:

#JavaScript #CSS
Комментарии
Чтобы оставить комментарий, необходимо авторизоваться

Присоединяйся в тусовку

Поделитесь своим опытом, расскажите о новом инструменте, библиотеке или фреймворке. Для этого не обязательно становится постоянным автором.

Попробовать

Напиши статью и выиграй годовую подписку на Яндекс плюс или лицензию от Jet Brains

Участвовать