Разработать страницу, состоящую из трех разноцветных столбцов. Левый столбец шириной 100 пикселей, центральный и правый занимают все оставшееся до края страницы место равномерно. Высота всех трех 100% страницы. Не должно быть скроллбара и белых полос вокруг страницы.
После нескольких лет работы во фронтенд-разработке, задачи подобные этой классифицируются как «5 минут, сто раз так делал», ведь, казалось бы, страница в три столбца, что может быть проще. Но проблемы у студентов возникли на всех стадиях решения задачи: от понимания сути и разработки структуры до защиты лабораторной работы.
И если, обучение чтению условия задачи и ораторскому мастерству лежит за гранью предмета «Основы веб-технологий», то отрефлексировать поразившее меня многообразие идей структурирования такой простой раскладки кажется интересным.
Студенты продемонстрировали в общей сложности около десяти разных вариантов решения этой задачи. От совсем никуда не годных до вполне приемлемых. Рассматривать я, разумеется, буду только вменяемую половину, то есть будет пять примеров, воспроизведенных мной + несколько, предложенных сообществом в комментариях.
I вариант
Первый вариант — совсем студенческий. Обычно подобным образом все и выглядело. И по понятным причинам: студент, имея в качестве бэкграунда только понимание общего принципа работы связки технологий HTML+CSS и самых простых CSS-селекторов, наверно, ничего другого и не мог написать.
<body>
<div class="one"></div>
<div class="two"></div>
<div class="three"></div>
</body>
html, body {
width: 100%;
height: 100%;
margin: 0;
display: flex;
}
.one {
width: 100px;
height: 100%;
background: red;
}
.two {
width: calc(50% - 50px);
height: 100%;
background: green;
}
.three {
width: calc(50% - 50px);
height: 100%;
background: blue;
}
CODEPEN
Причем надо понимать, что некоторые юные программисты слыхом не слыхивали о правилах оформления кода, поэтому его «красота» и читабельность требовали рефакторинга.
Итак, здесь мы имеем несколько антипаттернов: незначащие имена классов, многократное повторение кода, а также отсутствие контейнера и, как следствие, стилизация
body
. На этом этапе студенты погружались в изучение соглашения по именованию в БЭМ и приципов DRY, DIE, KISS, SOLID, YAGNI.Хочется отдельно рассказать, почему я запрещаю навешивать стили на
body
, так как однозначного ответа в сети с первого раза не нашлось. Такое решение пришло из личной практики: изначально в одном из проектов было задано примерно следующее:body {
max-width: 1024px;
}
Вследствие чего при создании новой страницы, на которой по дизайну располагался блок растянутый на всю ширину, возникли проблемы. Так как по правилам CSS дочерний элемент физически не может быть больше родительского. На исправление этой проблемы было потрачено достаточно много времени и, как следствие, сформулировано правило «никаких значащих стилей на тело страницы не навешивать».
Далее для краткости вынесем общие повторяющиеся стили. Здесь определяются цвета для заливки столбцов. Эти стили будут использоваться во всех последующих примерах.
.red {
background: red;
}
.green {
background: green;
}
.blue {
background: blue;
}
II вариант
Другим самым очевидным оказался вариант раскладки в таблицу. Сыграло свою роль ключевое слово «столбец» в постановке задачи.
<table class="blocks">
<td class="blocks--block__fixed-width red"></td>
<td class="blocks--block green"></td>
<td class="blocks--block blue"></td>
</table>
html, body {
margin: 0;
width: 100%;
}
.blocks {
height: 100vh;
width: 100%;
border-collapse: collapse;
}
.blocks--block__fixed-width {
width: 100px;
}
.blocks--block {
width: calc(50% - 50px);
}
CODEPEN
Этот вариант, хоть он и рабочий, отвергался, так как таблицы должны использоваться для представления табличных данных, которые не имеют место быть. Студентам предлагалось изучить Flexbox Layout, как более простое и изящное решение поставленной задачи.
III вариант
Особенностью следующего варианта является дополнительная обертка для второго и третьего столбца. Эта обертка (wrapper) позволяет проще решать задачу распределения пространства страницы между центральным и правым стобцами без использования возможностей Flexbox Layout.
<div class="blocks">
<div class="blocks--block__fixed-width red"></div>
<div class="blocks--wrapper">
<div class="blocks--block green"></div>
<div class="blocks--block blue"></div>
</div>
</div>
html, body {
margin: 0;
}
.blocks, .blocks--wrapper {
display: flex;
height: 100vh;
}
.blocks--wrapper {
width: calc(100% - 100px);
}
.blocks--block {
width: 50%;
}
.blocks--block__fixed-width {
width: 100px;
}
CODEPEN
IV вариант
В следующем примере, во-первых, используются псевдоклассы
:first-child
и :not(:first-child)
для выделения соответственно первого и всех_кроме_первого элементов, имеющих class="block"
. Во-вторых, используется больше возможостей Flexbox Layout, в частности свойство дочерних элементов flex
, которое объединяет свойства flex-grow, flex-shrink и flex-basis
и, таким образом, определяет относительные размеры элементов.<div class="blocks">
<div class="block red"></div>
<div class="block green"></div>
<div class="block blue"></div>
</div>
html, body {
margin: 0;
}
.blocks {
display: flex;
height: 100vh;
}
.block:first-child {
flex: 0 0 100px;
}
.block:not(:first-child) {
flex: 1 1 50%;
}
CODEPEN
Большим преимуществом предыдущего варианта считаю однократное вхождение зависимости от ширины левой колонки. Это в значительной мере облегчает поддержку и дальнейшую разработку проекта.
Следующий момент, который хочется подчеркнуть отдельно: если количество отднотипных колонок будет увеличиваться и будет принято решение воспользоваться, например, функцией
map
для их создания, то подобный метод стилизации позволит это осуществить гораздо проще. Рассмотрим этот случай на примере следующего кода:<div id="root"></div>
const colours = ['red','blue','green'];
const blocks = colours.map(colour =>
<div className={"block " + colour}></div>
);
ReactDOM.render(
<div className="blocks">{blocks}</div>,
document.getElementById('root')
);
#root, html, body {
margin: 0;
}
.blocks {
display: flex;
height: 100vh;
width: 100%;
}
.block:first-child {
flex: 0 0 100px;
}
.block:not(:first-child) {
flex: 1 1 50%;
}
CODEPEN
Не углубляясь в работу библиотеки React, с помощью которой происходит рендеринг компонентов в данном случае, лишь поясню, что создание большого количества однотипных элементов это частовстречающаяся задача и возможность ее автоматизации это большой плюс. Об этом надо помнить на всех стадиях веб-разработки: дизайн, верстка, программирование.
V вариант
И последний вариант решения поставленной задачи, только с оговоркой, что она состоит именно в раскрашивании страницы.
<div class="container"></div>
html, body {
margin: 0;
}
.container {
width: 100%;
height: 100vh;
background: linear-gradient(
to right,
red 100px,
green 100px,
green calc(50% + 50px),
blue calc(50% + 50px)
);
}
CODEPEN
Здесь используется свойство
background
, которое задает градиентное изменение цветов слева на право. Для чёткой границы каждый последующий цвет начинается с точки остановки предыдущего цвета.VI вариант
В комментарии SmithZx предложил отличное решение на Grid Layout — это очень удобная и современная альтернатива существующим раскладкам. На его основе получилось, наверное, самое лаконичное решение:
<div class="blocks">
<div class="red"></div>
<div class="green"></div>
<div class="blue"></div>
</div>
html, body {
margin: 0;
}
.blocks {
display: grid;
height: 100vh;
grid-template-columns: 100px 1fr 1fr;
}
CODEPEN
VII вариант
Следующий пример предложил Ukrop1975 в комментарии. В нем используется значение свойства
display:table
, которое позволяет задать табличное поведение любым элементам.<div class="blocks">
<div class="block fixed-width red"></div>
<div class="block green"></div>
<div class="block blue"></div>
</div>
html, body {
margin: 0;
}
.blocks {
display: table;
width: 100%;
height: 100vh;
table-layout: fixed;
}
.block {
display: table-cell;
width: 50%;
}
.block.fixed-width {
width: 100px;
}
CODEPEN
Такой подход позволяет использовать все преимущества таблиц над блоками, не нарушая правил семантики. Одним из таких преимуществ можно считать то, что по умолчанию ширина столбцов таблицы зависит от содержания, а высота — одинаковая.
VIII вариант
Также очень интересной показалась идея не использовать только
display:block
, предложенная nebsehemvi в комментарии. Такая необходимость может возникнуть, например, для обеспечения обратной совместимости, так как до сих пор широко используются браузеры не поддерживающие Flexbox и Grid.<div>
<div class="block fixed-width red"></div>
<div class="block green"></div>
<div class="block blue"></div>
</div>
html, body {
margin: 0;
}
.block {
display: block;
float: left;
height: 100vh;
width: calc((100% - 100px)/2);
}
.fixed-width {
max-width: 100px;
}
CODEPEN
IX вариант
И совсем экстремальный вариант, предложенный dimpa91 в комментарии. Здесь идея состоит в том, чтобы отказаться от функции
calc
, чтобы летало даже на CCS2. <div class="blocks">
<div class="block fixed-width red"></div>
<div class="block green"></div>
<div class="block blue"></div>
</div>
body, html {
margin: 0;
}
.blocks {
margin-left: 100px;
}
.block.fixed-width {
width: 100px;
margin-left: -100px;
}
.block {
width: 50%;
height: 100vh;
float: left;
}
CODEPEN
Спасибо всем, кто выразил свое профессиональное мнение и отдельное большое спасибо тем, кто проиллюстрировал его кодом! Всем лучи добра .)
Комментарии (27)
GreatRash
30.10.2017 16:01codepen.io/anon/pen/BmywMx
Задача оторвана от жизни, поэтому вот.ErisNuts Автор
01.11.2017 00:32я просто не совсем поняла, что хорошего в данном случае нам дает абсолютное позиционирование.
расскажите, пожалуйста, чем вы руководствовались…YNile
01.11.2017 12:46Предположу, что задачей.
Какая задача, такое и решение — простое и понятное. Ничего лишнего.
Про наполнение и переполнение блоков в задаче ни слова.ErisNuts Автор
01.11.2017 14:03Обычно абсолютное позиционирование используется, если никакие другие методы не работают. Например, при размещении иконок внутри контейнера.
Элемент с таким типом позиционирования не зависит от других элементов и не влияет на другие элементы. Это следует учитывать каждый раз, когда используется абсолютное позиционирование. Его чрезмерное или неправильное использование могут ограничить гибкость сайта.
А самое главное, цель обучения — не научиться решать «оторванные от жизни» задачи, а постепенно погружаться в мир предмета, впитывая знания о правилах, требованиях, ограничениях. Иначе, мне пришлось бы принимать абсолютно любые рабочие варианты.GreatRash
01.11.2017 17:46Мало ли, что там обычно используется. Мне дали абсолютно оторванную от реальности задачу, я привёл абсолютно оторванное от реальности решение, которое ещё и работает везде, начиная аж с ИЕ7.
nebsehemvi
30.10.2017 22:46Иногда самое хорошее решение — простое.
Выкинуть из первого варианта флексы, правильно оформить код и всё будет хорошо. Еще бы от calc избавиться, так вообще прелесть будет — на CSS2 взлетит.
codepen.io/NebSehemvi/pen/pdvwQg
И почему автор использует vw? Если не ошибаюсь, то при использовании vw может появиться горизонтальная полоса прокрутки.ErisNuts Автор
31.10.2017 21:01задачка учебная, поэтому цель не просто получить решение, а чему-то научиться…
да, может появиться скроллбар, потому что размер вьюпорта больше, чем ширина элемента html.
если дополнить код таким образом:
html, body, .blocks { width: 100%; }
можно использовать50%
вместо50vw
.
ErisNuts Автор
01.11.2017 00:21спасибо за интересный вариант решения задачки, добавила его в статью с сылкой на ваше авторство, но с небольшими правками.)
SmithZx
30.10.2017 22:46Ещё до прочтения вариантов пришло в голову очевидное решение, не представленное в статье:
codepen.io/anon/pen/pdvWNZ
Скрытый текст<div class="root"> <div class="red"></div> <div class="green"></div> <div class="blue"></div> </div>
html, body { width: 100%; height: 100%; margin: 0; } .root { display: grid; width: 100%; height: 100%; grid-template-columns: 100px 1fr 1fr; } .red { grid-column: 1; background-color: red; } .green { grid-column: 2; background-color: green; } .blue { grid-column: 3; background-color: blue; }
ErisNuts Автор
01.11.2017 00:24спасибо за отличный вариант решения задачки!
добавила его в статью с ссылкой на ваше авторство, но с небольшими правками .)
посмотрите, пожалуйста, получилось очень лаконично.)
Ukrop1975
30.10.2017 22:46Кто мешает для DIV-ов использовать table-cell?
codepen.io/Ukrop1975/pen/dZPmvVErisNuts Автор
01.11.2017 00:26Ничего.) хороший вариант, имеет свои плюсы. добавила его с ссылкой на ваше авторство с небольшими правками.)
alix_ginger
31.10.2017 12:27Итак, здесь мы имеем несколько антипаттернов: незначащие имена классов
И предлагаете red, green & blue вместо one, two & three? Эти имена точно так же описывают представление, а не семантику, что не совсем правильно.ErisNuts Автор
31.10.2017 19:53В таких вопросах я всегда аппелирую к стоимости поддержки кода. Допустим поставили задачу добавить еще одну полосу. И вот нужно переименовать все блоки, исправить стили, короче, куча работы.
А идея с классами — названиями цветов используется в CSS-фреймворках: в Semantic UI, например:
офомление кнопок
И еще, что мне кажется важным, по коду же и так видно, что блок первый, или второй, дублирование информации получается.alix_ginger
01.11.2017 14:53А если понадобиться изменить цвета блоков, придется править HTML, чтобы изменить представление.
Конечно, я прекрасно понимаю, что идеал — когда в HTML только семантика, а в CSS только представление — недостижим, и просто в данный момент придираюсь :) Ради простоты и стоимости поддержки можно (и нужно) отклониться от «идеологической чистоты» кода, как, например, и сделали в Semantic UI. В Bootstrap, кстати, сделали правильнее: классы, от которых зависит цвет кнопки, называются btn-primary, btn-secondary, btn-danger и т. д. — такие названия говорят о значении кнопки, а не о ее внешнем виде.
А вообще, в контексте исходного задания нет никакой семантики, к которой можно привязать классы.
Aingis
Извратненько.
1. Зачем тащить
когда уже давно завезли
vh
? Достаточно указатьmin-height: 100vh
наbody
или обёртке:Это лучше тем, что для наследования высоты вы вынуждены указывать
height: 100%
для каждого элемента, начиная отhtml
. Это приводит к тому, что когда у вас содержимого по высоте больше чем на одну страницу, то требуемые эффекты могут окончится на границе первого экрана.min-height: 100vh
даёт требуемую высоту элемента на весь экран изначально и позволяет расти по необходимости.2. Вы правы в том, что на
body
не стоит использовать оформление кроме самого базового. Стоит помнить, что в нём расположены все элементы страницы. Вы можете захотеть добавить попап. Какое-нибудь расширение может добавить свои элементы. Если делать его флекс-контейнером, то эффекты могут быть самими непредсказуемыми. Для основного содержимого страницы лучше использовать обёртку.3. Псевдокласс
:nth-child
предназначен в первую очередь для простого оформления вроде черезполосицы или, например, задания отступа всем элементам кроме первого. Но не стоит использовать его для раскладки. Вы тем самым привязываетесь к структуре документа и при малейших изменениях вёрстка может «поехать». Кроме того, вы неизбежно увеличиваете специфичность селектора для стилей со всеми вытекающими проблемами переопределения.4. Внезапно, запись
flex: 1 1 auto
не подразумевает, что колонки будут одинаковой ширины: пример на Codepen. Она говорит, что будет одинаково распределяться свободное доступное пространство. Либо же одинаково уменьшаться в размерах (если позволяют правила переполнения) относительно основного размера, который приflex-basis: auto
равен ширине содержимого.5. Стоит предусмотреть возможность переноса блоков на следующую строку на узких экранах (от телефонов и планшетов до банального окна браузера в полэкрана) с помощью
flex-wrap
, иначе будет горизонтальная прокрутка, которой не должно быть по условиям задачи.Mikola-BLR
4. Поэтому правильнее записать так:
Чтобы и на мелких разрешениях длинное изображение не ломало ширину блоков:
Aingis
Тогда будет очень узко на мобилках.
ErisNuts Автор
height:100%
наheight:100vh
, вы правы, так аккуратнее.flex: 1 1 auto
— да, я не учла вариант с вложенными элементами, правильнее будетflex: 1 1 50%
, либо указыватьmax-width: 50%
.