CSS-live.ru

Что может поломать макет на гридах (CSS Grid Layout)

Перевод статьи BREAKING THE GRID с сайта daverupert.com для CSS-live.ru, автор — Дейв Руперт

Два способа поломать CSS-гриды, и как их починить

Как фронтенд-разработчика, меня ничто так не раздражает, как неожиданное появление горизонтального скроллбара на сайте. Однако, выстраивая раскладку страницы заказа с помощью CSS-гридов, я на удивление обнаружил что-то мистическое, что поломало мой контейнер. Я полагал, что с размерами у гридов всё решается автоматически.

В итоге я нашёл два способа поломать CSS-гриды. Как это бывает, я умудрился в одной раскладке сделать и то, и другое. 

№1: Элементы с overflow-x поломают грид

Для нашего магазина использовался паттерн подменю, которое могло бы прокручиваться с помощью overflow-x, чтобы показать больше вариантов оплаты. Даже с примененным overflow-x грид-элемент растягивался до scrollWidth подменю. Оказывается, любой элемент с overflow-x, будь то блок <code> или адаптивная таблица, поломали бы грид. Вот наглядный пример.

See the Pen Overflow-x Elements Inside CSS Grid (bug? behavior?) by Максим (@psywalker) on CodePen.

Обычно с решением проблемы горизонтального переполнения справляется overflow-x: auto. Но не в этот раз. Ещё идея — обернуть элемент, попробовав всякие трюки с overflow: hidden, чтобы заставить раскладку подчиняться. Тоже нет.

Дело в том, что по умолчанию у грид-элементов стоит min-width: auto и их размер автоматически устанавливается по контенту в них. Оно устанавливает их min-width по ширине переполняющего блока. Поэтому для исправления глубоко вложенного переполняющего дочернего элемента вы, вопреки интуиции, не обращаете внимания на родительский элемент, а идете вверх по DOM-дереву до самого грид-элемента и обнуляете ему min-width.

.grid > * { min-width: 0; }

Благодаря этому размеры грид-элемента с переполнением контента установятся правильно. Но не всегда.

№2: Контролы формы поломают грид

В нашей раскладке тоже использовался грид для размещения полей формы бок о бок. Я создал тестовый пример на CodePen с различными типами полей, чтобы изолировать эту проблему. При уменьшении окна браузера до момента появления горизонтального скролла (~380px) можно видеть, как поля выходят за границы грид-полосы.

See the Pen CSS Grid PRBLMZ by Максим (@psywalker) on CodePen.

В итоге, чтобы пофиксить подобные поля, придётся применить max-width: 100%, и хотя это в основном исправляет такое поведение в Chrome и Safari, некоторые элементы в этих браузерах и все подобные элементы в Firefox и Edge по-прежнему вываливаются за грид-полосу.

Потому что элементы <input> и им подобные (<img>, <progress>, <select>, <video>) обладают потенциальной способностью быть чем-то, называемым «Замещаемыми элементами»

Что ещё, чёрт возьми, за «Замещаемые элементы»?

Согласно MDN, замещаемый элемент — элемент, представление которого выходит за рамки CSS. Это ситуация, когда браузер получает разметку и внедряет элемент с чем-то вроде Shadow DOM. <video> — подходящий пример, но большинство контролов формы также попадают под это описание.

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

replacedelements

В нашей ситуации у некоторых элементов input есть фантомные «внутренние размеры», применяемые браузером. Они не отображаются в качестве браузерного стиля, ни во вкладке «Стили», ни в «Вычисленные стили», но их min-width около ~180px для стандартного <input type="text">.

Чтобы кроссбраузерно пофиксить «вываливание» элементов за границы грид-полосы, придётся переопределить этот внутренний размер замещаемых элементов.

/* Применяем max-width для замещаемых элементов и контролов формы. */
img, video, audio, canvas, input,
select, button, progress { max-width: 100%; }

/* Заставляем поля с типом file и submit переносить текст */
input[type="file"],
input[type="submit"] { white-space: pre-wrap; }

/* Чиним прогрессбар и поле-ползунок */
progress,
input[type="range"] { width: 100%; }

/* Фиксим поля с типом Number в Firefox */
@supports (--moz-appearance: none) {
  input[type="number"] { width: 100%; }
}

Здесь есть побочный эффект, когда прогрессбар, и поля range и number теперь всегда занимают 100% грид-полосы. Это можно переопределить и настроить.

Можно ли назвать это багами гридов? Не совсем.

Такое поведение, как указал некто на StackOverflow, соответствует спецификации. Гриды перенимают поведение флексбоксов относительно min-width: auto, а у замещаемых элементов их min-width зависит от внутренних размеров. Так что само по себе это не баг.

Тем не менее, почти для каждой из моих раскладок мне приходилось применять вспомогательный класс к капризным грид-элементам. Чтобы у него было запоминающееся фирменное название, я назвал его «Fit Grid» (по-русски можно передать как «Влит-в-грид» — прим. перев.).

/*
 _______  ___   _______    _______  ______    ___   ______  
|       ||   | |       |  |       ||    _ |  |   | |      | 
|    ___||   | |_     _|  |    ___||   | ||  |   | |  _    |
|   |___ |   |   |   |    |   | __ |   |_||_ |   | | | |   |
|    ___||   |   |   |    |   ||  ||    __  ||   | | |_|   |
|   |    |   |   |   |    |   |_| ||   |  | ||   | |       |
|___|    |___|   |___|    |_______||___|  |_||___| |______| 
Дейв Руперт
Читайте ещё: https://daverupert.com/2017/09/breaking-the-grid/
*/

/* 
 * Удаляем `min-width: auto` из элементов грида.
 * Чиним элементы с overflow-x.
*/
.fit-grid > * { min-width: 0; }

/* Применяем max-width к замещаемым элементам и контролам формы */
.fit-grid img,
.fit-grid video,
.fit-grid audio,
.fit-grid canvas,
.fit-grid input,
.fit-grid select,
.fit-grid button,
.fit-grid progress { max-width: 100%; }

/* Заставляем поля с типом file и submit переносить текст */
.fit-grid input[type="file"],
.fit-grid input[type="submit"] { white-space: pre-wrap; }

/* Чиним прогрессбар и поле-ползунок */
.fit-grid progress,
.fit-grid input[type="range"] { width: 100%; }

/* Фиксим поля с типом Number в Firefox */
@supports (--moz-appearance: none) {
  .fit-grid input[type="number"] { width: 100%; }
}

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

Как по мне, так замещаемые элементы всегда должны подчиняться и вмещаться в грид-полосы. Я был бы рад, если бы браузеры сошлись на этом. Единственный вопрос, который крутится в моей голове, это должны ли контролы формы, такие как прогрессбар, ползунок и числовые занимать всю грид-полосу. Решение — input, select { width: 100% }, но нутром чую, что не все этого захотят.

Если у вас есть улучшения, дайте мне знать, и я опубликую их на GitHub, но, пожалуйста, перед требованием исправить что-то, всё же проверяйте это в каждом браузере. Спасибо, d00dz.

Спасибо Грегу Уитворту из Microsoft Edge за помощь в отладке и лучшем понимании замещаемых элементов.

P.S. Это тоже может быть интересно:

3 комментария

  1. Clearfix умер, да здравствует clearfix!

    Во втором примере фраза «this is a really wide select box» скорее несёт смысл «действительно широкий select box», правда я не знаю, переводится ли сам «select box» по-нормальному.

  2. Такое впечатление, что Web не инженеры разрабатывали, а гуманитарии. Или математики. Что, впрочем, одно и то же… Сколько же еще в нем скрытых багов, пока не выявленных, разгуливает?

  3. Ещё макет может поломать column-gap, он почему-то не учитывается даже при box-sizing: border-box если ширина контейнера установлена в относительных величинах (% или vw). Расползается на сумму гепов, и как это пофиксить непонятно:(

Оставить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *

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