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

Разработать страницу, состоящую из трех разноцветных столбцов. Левый столбец шириной 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)


  1. Aingis
    30.10.2017 15:19
    +1

    Извратненько.

    1. Зачем тащить

    html, body {
        height: 100%;
    }

    когда уже давно завезли vh? Достаточно указать min-height: 100vh на body или обёртке:

    #root {
        min-height: 100vh;
    }

    Это лучше тем, что для наследования высоты вы вынуждены указывать height: 100% для каждого элемента, начиная от html. Это приводит к тому, что когда у вас содержимого по высоте больше чем на одну страницу, то требуемые эффекты могут окончится на границе первого экрана. min-height: 100vh даёт требуемую высоту элемента на весь экран изначально и позволяет расти по необходимости.

    2. Вы правы в том, что на body не стоит использовать оформление кроме самого базового. Стоит помнить, что в нём расположены все элементы страницы. Вы можете захотеть добавить попап. Какое-нибудь расширение может добавить свои элементы. Если делать его флекс-контейнером, то эффекты могут быть самими непредсказуемыми. Для основного содержимого страницы лучше использовать обёртку.

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

    4. Внезапно, запись flex: 1 1 auto не подразумевает, что колонки будут одинаковой ширины: пример на Codepen. Она говорит, что будет одинаково распределяться свободное доступное пространство. Либо же одинаково уменьшаться в размерах (если позволяют правила переполнения) относительно основного размера, который при flex-basis: auto равен ширине содержимого.

    5. Стоит предусмотреть возможность переноса блоков на следующую строку на узких экранах (от телефонов и планшетов до банального окна браузера в полэкрана) с помощью flex-wrap, иначе будет горизонтальная прокрутка, которой не должно быть по условиям задачи.


    1. Mikola-BLR
      30.10.2017 22:46

      4. Поэтому правильнее записать так:

      .block:not(:first-child) {
        flex: 1 1 50%;
      }

      Чтобы и на мелких разрешениях длинное изображение не ломало ширину блоков:
      .block:not(:first-child) {
        flex: 1 1 50%;
        overflow: hidden;
      }


      1. Aingis
        31.10.2017 11:49

        Тогда будет очень узко на мобилках.


    1. ErisNuts Автор
      31.10.2017 20:37

      • исправила height:100% на height:100vh, вы правы, так аккуратнее.
      • не спорю, привязываться к структуре документа была не самая лучшая идея, но она обусловлена, постановкой задачи — разбиение столбцов на первый и все остальные, а главное, появилась возможность воспользоваться маппингом.
      • на счет flex: 1 1 auto — да, я не учла вариант с вложенными элементами, правильнее будет flex: 1 1 50%, либо указывать max-width: 50%.
      • адаптивность и вообще работа с мобильниками идет как дополнительное задание на защите лабораторной, эту часть я намеренно пока опустила, позже планирую дополнить.


  1. GreatRash
    30.10.2017 16:01

    codepen.io/anon/pen/BmywMx
    Задача оторвана от жизни, поэтому вот.


    1. ErisNuts Автор
      01.11.2017 00:30

      image


    1. ErisNuts Автор
      01.11.2017 00:32

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


      1. YNile
        01.11.2017 12:46

        Предположу, что задачей.
        Какая задача, такое и решение — простое и понятное. Ничего лишнего.
        Про наполнение и переполнение блоков в задаче ни слова.


        1. ErisNuts Автор
          01.11.2017 14:03

          Обычно абсолютное позиционирование используется, если никакие другие методы не работают. Например, при размещении иконок внутри контейнера.
          Элемент с таким типом позиционирования не зависит от других элементов и не влияет на другие элементы. Это следует учитывать каждый раз, когда используется абсолютное позиционирование. Его чрезмерное или неправильное использование могут ограничить гибкость сайта.
          А самое главное, цель обучения — не научиться решать «оторванные от жизни» задачи, а постепенно погружаться в мир предмета, впитывая знания о правилах, требованиях, ограничениях. Иначе, мне пришлось бы принимать абсолютно любые рабочие варианты.


          1. GreatRash
            01.11.2017 17:46

            Мало ли, что там обычно используется. Мне дали абсолютно оторванную от реальности задачу, я привёл абсолютно оторванное от реальности решение, которое ещё и работает везде, начиная аж с ИЕ7.


  1. vdv73rus
    30.10.2017 17:38

    А где CSS-grids? 2017-й на дворе


    1. ErisNuts Автор
      01.11.2017 00:20

      добавила вариант с грид раскладкой .)


  1. nebsehemvi
    30.10.2017 22:46

    Иногда самое хорошее решение — простое.

    Выкинуть из первого варианта флексы, правильно оформить код и всё будет хорошо. Еще бы от calc избавиться, так вообще прелесть будет — на CSS2 взлетит.
    codepen.io/NebSehemvi/pen/pdvwQg

    И почему автор использует vw? Если не ошибаюсь, то при использовании vw может появиться горизонтальная полоса прокрутки.


    1. dimpa91
      31.10.2017 19:55

      Можно и от calc избавиться: jsfiddle.net/8y2mu14w/1


      1. ErisNuts Автор
        01.11.2017 00:35

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


        1. dimpa91
          01.11.2017 10:57

          Да, чаще всего с отрицательным margin это решается


    1. ErisNuts Автор
      31.10.2017 21:01

      задачка учебная, поэтому цель не просто получить решение, а чему-то научиться…
      да, может появиться скроллбар, потому что размер вьюпорта больше, чем ширина элемента html.
      если дополнить код таким образом:

      html, body, .blocks {
        width: 100%;
      }
      

      можно использовать 50% вместо 50vw.


    1. ErisNuts Автор
      01.11.2017 00:21

      спасибо за интересный вариант решения задачки, добавила его в статью с сылкой на ваше авторство, но с небольшими правками.)


  1. 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;
    }


    1. MeylisDay
      31.10.2017 19:55

      Я тоже не очень понимаю, почему гриды не упомянуты в статье.


      1. ErisNuts Автор
        01.11.2017 00:22

        исправилась .)


    1. ErisNuts Автор
      01.11.2017 00:24

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


  1. Ukrop1975
    30.10.2017 22:46

    Кто мешает для DIV-ов использовать table-cell?
    codepen.io/Ukrop1975/pen/dZPmvV


    1. ErisNuts Автор
      01.11.2017 00:26

      Ничего.) хороший вариант, имеет свои плюсы. добавила его с ссылкой на ваше авторство с небольшими правками.)


  1. alix_ginger
    31.10.2017 12:27

    Итак, здесь мы имеем несколько антипаттернов: незначащие имена классов

    И предлагаете red, green & blue вместо one, two & three? Эти имена точно так же описывают представление, а не семантику, что не совсем правильно.


    1. ErisNuts Автор
      31.10.2017 19:53

      В таких вопросах я всегда аппелирую к стоимости поддержки кода. Допустим поставили задачу добавить еще одну полосу. И вот нужно переименовать все блоки, исправить стили, короче, куча работы.
      А идея с классами — названиями цветов используется в CSS-фреймворках: в Semantic UI, например:
      офомление кнопок
      И еще, что мне кажется важным, по коду же и так видно, что блок первый, или второй, дублирование информации получается.


      1. alix_ginger
        01.11.2017 14:53

        А если понадобиться изменить цвета блоков, придется править HTML, чтобы изменить представление.

        Конечно, я прекрасно понимаю, что идеал — когда в HTML только семантика, а в CSS только представление — недостижим, и просто в данный момент придираюсь :) Ради простоты и стоимости поддержки можно (и нужно) отклониться от «идеологической чистоты» кода, как, например, и сделали в Semantic UI. В Bootstrap, кстати, сделали правильнее: классы, от которых зависит цвет кнопки, называются btn-primary, btn-secondary, btn-danger и т. д. — такие названия говорят о значении кнопки, а не о ее внешнем виде.

        А вообще, в контексте исходного задания нет никакой семантики, к которой можно привязать классы.