Блог “У Василича”

Диагональные лайауты и обратная сторона clip-path

Приблизительное время чтения6 мин.
Фото от Diane Helentjaris

Кроссбраузерные проблемы при использовании clip-path. Рабочий вариант реализации диагональной разметки

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

Проблема проявляется на MacOS в Chrome и Edge. Любопытно, что на Windows при этом всё ровно, и в Safari проблема также не наблюдается. Firefox стоит особняком — у него при скролле основного содержимого лаги не прослеживаются, но появляются с удвоенной силой наверху рядом с промо изображением + на главной странице при ховере на карточках всё тормозит.

Дело, покрытое мраком, я вам скажу. Пришлось погрузиться в вопрос.

В поиске источника проблемы

Chrome и Edge

Чередуя отключение фрагментов разметки и их стилей пришёл к тому, что проблема вызвана добавлением CSS правила clip-path на основной контейнер. Я использую clip-path для создания диагональных секций — в шапке, футере, заголовке поста и в карточках превью на главной. Убрав это правило, лайаут порушился, но проблема в Chrome и Edge исчезла.

Видимо я как-то неправильно использую clip-path. Отправившись на поиски схожих багов в сети Интернет, наткнулся на записи о проблемах совместимости clip-path и секций со скроллом. Основной посыл: скролл внутри контейнера с повешенным на него clip-path приводит к репэинту всего блока. Получается, что чем нагруженнее блок, тем сложнее отрисовка, и тем больше лаг. В моём случае пост высотой, скажем, в 15 тысяч пикселей, включающий в себя не только текст, но и медиафайлы и блоки кода, будет полностью перерисовываться на каждой прокрутке — GPU попросту не справляется, оставляя нам белый экран. Т.к. новая версия Edge работает на Chromium, то видимо это проблема их имплементации clip-path, и судя по форуму наличие бага давно известно. Почему лагов нет на Windows без понятия — отнесу это также к нюансам реализации под Chromium.

Любопытно, что вместе с моим кейсом с расположением скроллящегося блока внутри контейнера с clip-path, встречаются упоминания об обратном: использование элемента c clip-path внутри длинного контейнера приводит к аналогичным репэинтам. Особенно эта проблема затронула авторов популярных библиотек, предоставляющих утилиту visually-hidden (визуальное сокрытие элемента на экране с сохранением доступа для скринридеров) — классическая версия этого сниппета использует clip-path. По этой причине реализация утилиты была изменена в таких библиотеках как bootstrap, html5-boilerplate, foundation и polished.

Если из этого всего нужно сделать один вывод, то он будет таков: clip-path и скролл не дружат. Ни в каком виде. Если крупный блок нужно подрезать сверху и снизу, то лучше добавить по вспомогательному элементу с обеих сторон и подрезать их, оставив основное содержимое нетронутым.

Firefox

Если включить через Chrome Devtools отображение репэинтов (Rendering → Paint flashing), то можно увидеть, что при ховере (а точнее при каждом движении карточки через transform, происходящем на ховере) перерисовывается весь контейнер, отчего конкретно Firefox’у становится тяжко.

Почему именно в Firefox? Опять же без понятия. Но отрисовка всего блока на каждое движение карточки через легковесный transform — это ненормально. Так что вывод аналогичный: не оборачиваем в clip-path крупные блоки. Убрав clip-path с основного контейнера проблема на главной исчезла.

Что касается баннера в посте, то я пришёл к тому, что как Chrome не любит совмещать clip-path со скроллом, Firefox не любит мешать его с фиксированным фоном. У изображения стоит position: fixed и на контейнер ставится clip-path — таким образом достигается занимательный эффект, когда на скролл мы видим движущееся изображение как будто через замочную скважину. Здесь я не нашёл пруфов о существовании такого бага на форумах Firefox, но природа этой проблемы очень схожа со скроллом, так что я не удивлён. В итоге попросту пришлось отказаться от фиксированного фона, сделав баннер статичным — обидно, но идея изобретательного обходного пути мне в голову не пришла.

Касательно проблем с отрисовкой в MacOS есть ещё такая тема, что когда браузер открыт в спокойном состоянии, то есть на экране статичная картинка, и вдруг мы начинаем скроллить, то мы можем увидеть всё те же белые фрагменты экрана, хоть и в гораздо меньшем количестве. Подобные “артефакты” встречаются на современных маках с двумя GPU: на этих системах в зависимости от нагрузок идёт переключение используемых графических процессоров — интегрированной менее ресурсозатратной карты и более производительной, но и тяжеловесной, дискретной. Так мак, видя на экране статичную картинку, переключается на интегрированную, чтобы сэкономить заряд батареи — мы начинаем скролл, и карточка уже не справляется, так что идёт замена на дискретную — в этот момент переключения мы и видим белые полосы. Отключить эту логику переключения карт можно через Energy Saver → Automatic graphics switching, что имеет смысл при использовании ноутбука, подключённого к зарядке — в ином случае это скорее того не стоит, но для общего развития стоит иметь в виду.

В поисках нового решения

Если мы убираем clip-path с основного контейнера, то моя концепция диагональных лайаутов рушится — нужно придумать альтернативу.

Предположим, что у нас на странице есть 4 раздела: шапка, заголовок, тело и футер. Цель: отделить секции друг от друга диагональными линиями. В процессе поисков я осознал, что создание таких линий через градиенты не особо работает на блоках, занимающих 100% ширины, т.к. чтобы эти линии были ровными градиент нужно сопроводить указанием background-size, что мы не можем сделать, т.к. ширина зависит от размера экрана — без этого линия получится угловатая. Так что clip-path здесь будет предпочтительнее. Вопрос: как эти правила расположить?

Старое решение

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

Новое решение

В новом решении цвета фона и текста, заданные в body, не меняются. Здесь линии — это отдельные элементы, а точнее псевдоэлементы диагональных секций. Эти псевдоэлементы имеют инвертированный фон и через clip-path идёт обрезка чисто линий. Контентная часть секции оборачивается в div, который также обрезается, чтобы соответствовать форме линий. Линии позиционируются абсолютно, чтобы не влиять на высоту раздела. При таком подходе в заголовок добавляются две линии и одна в футер, хэдер и основной раздел при этом остаются нетронутыми — значит наша цель достигнута.

Плюсы нового решения:

  1. У основного раздела не задаётся clip-path → никаких репэинтов. Мы сделали выводы, и диагональными секциями здесь являются миниатюрные блоки.
  2. Цвет фона не переопределяется вне body → решение легче поддерживать. Раньше с этим возникало много путаницы.
  3. Никаких отрицательных отступов → разделы более не зависят друг от друга. Это значит, что решение лучше скалируется.

Так что обращайте внимание на какие блоки вы вешаете clip-path. Если это крупный блок, то убедитесь, что браузеры под MacOS не взрываются от ваших, казалось бы, безобидных игр с формами. Ну и если вам тоже взбрело в голову применить у себя диагональные разделители, то решение из второй демки вполне рабочее.

p1t1ch.com