7 июня 2022 г.

Свойство float

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

Далее мы рассмотрим, как работает float, разберём решения сопутствующих проблем, а также ряд полезных рецептов.

Как работает float

Синтаксис:

float: left | right | none | inherit;

При применении этого свойства происходит следующее:

  1. Элемент позиционируется как обычно, а затем вынимается из документа потока и сдвигается влево (для left) или вправо (для right) до того как коснётся либо границы родителя, либо другого элемента с float.
  2. Если пространства по горизонтали не хватает для того, чтобы вместить элемент, то он сдвигается вниз до тех пор, пока не начнёт помещаться.
  3. Другие непозиционированные блочные элементы без float ведут себя так, как будто элемента с float нет, так как он убран из потока.
  4. Строки (inline-элементы), напротив, «знают» о float и обтекают элемент по сторонам.

Ещё детали:

  1. Элемент при наличии float получает display:block.

    То есть, указав элементу, у которого display:inline свойство float: left/right, мы автоматически сделаем его блочным. В частности, для него будут работать width/height.

    Исключением являются некоторые редкие display наподобие inline-table и run-in (см. Relationships between „display“, „position“, and „float“)

  2. Ширина float-блока определяется по содержимому. («CSS 2.1, 10.3.5»).

  3. Вертикальные отступы margin элементов с float не сливаются с отступами соседей, в отличие от обычных блочных элементов.

Это пока только теория. Далее мы рассмотрим происходящее на примере.

Текст с картинками

Одно из первых применений float, для которого это свойство когда-то было придумано – это вёрстка текста с картинками, отжатыми влево или вправо.

Например, вот страница о Винни-Пухе с картинками, которым поставлено свойство float:

Её HTML-код выглядит примерно так:

<img src="1.jpg" style="float:right">
<p>Текст...</p>
<p>Текст...</p>

<img src="2.jpg" style="float:left">
<p>Текст...</p>

<img src="3.jpg" style="float:right">
<p>Текст...</p>

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

Посмотрим, например, как выглядело бы начало текста без float:

  1. Элемент IMG вынимается из документа потока. Иначе говоря, последующие блоки начинают вести себя так, как будто его нет, и заполняют освободившееся место (изображение для наглядности полупрозрачно):
  1. Элемент IMG сдвигается максимально вправо(при float:right):
  1. Строки, в отличие от блочных элементов, «чувствуют» float и уступают ему место, обтекая картинку слева:

При float:left – всё то же самое, только IMG смещается влево (или не смещается, если он и так у левого края), а строки – обтекают справа

Строки и инлайн-элементы смещаются, чтобы уступить место float. Обычные блоки – ведут себя так, как будто элемента нет.

Чтобы это увидеть, добавим параграфам фон и рамку, а также сделаем изображение немного прозрачным:

Как видно из рисунка, параграфы проходят «за» float. При этом строки в них о float'ах знают и обтекают их, поэтому соответствующая часть параграфа пуста.

Блок с float

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

Посмотрим, как это работает, на конкретной задаче – сделать рамку с названием вокруг картинки с Винни.

HTML будет такой:

<h2>Винни-Пух</h2>

<div class="left-picture">
  <img src="winnie-mult.jpg" width="200" height="150">
  <div>Кадр из советского мультфильма</div>
</div>

<p>Текст...</p>

…то есть, div.left-picture включает в себя картинку и заголовок к ней. Добавим стиль с float:

.left-picture {
  float: left;

  /* рамочка и отступ для красоты (не обязательно) */
  margin: 0 10px 5px 0;
  text-align: center;
  border: 1px solid black;
}

Результат:

Заметим, что блок div.left-picture «обернул» картинку и текст под ней, а не растянулся на всю ширину. Это следствие того, что ширина блока с float определяется по содержимому.

Очистка под float

Разберём ещё одну особенность использования свойства float.

Для этого выведем персонажей из мультфильма «Винни-Пух». Цель:

Реализуем её, шаг за шагом.

Шаг 1. Добавляем информацию

Попробуем просто добавить Сову после Винни-Пуха:

<h2>Винни-Пух</h2>
<div class="left">Картинка</div>
<p>..Текст о Винни..</p>

<h2>Сова</h2>
<div class="left">Картинка</div>
<p>..Текст о Сове..</p>

Результат такого кода будет странным, но предсказуемым:

Произошло следующее:

  • Заголовок <h2>Сова</h2> не заметил float (он же блочный элемент) и расположился сразу после предыдущего параграфа <p>..Текст о Винни..</p>.
  • После него идёт float-элемент – картинка «Сова». Он был сдвинут влево. Согласно алгоритму, он двигается до левой границы или до касания с другим float-элементом, что и произошло (картинка «Винни-Пух»).
  • Так как у совы float:left, то последующий текст обтекает её справа.

Шаг 2. Свойство clear

Мы, конечно же, хотели бы расположить заголовок «Сова» и остальную информацию ниже Винни-Пуха.

Для решения возникшей проблемы придумано свойство clear.

Синтаксис:

clear: left | right | both;

Применение этого свойства сдвигает элемент вниз до тех пор, пока не закончатся float'ы слева/справа/с обеих сторон.

Применим его к заголовку H2:

h2 {
  clear: left;
}

Результат получившегося кода будет ближе к цели, но всё ещё не идеален:

Элементы теперь в нужном порядке. Но куда пропал отступ margin-top у заголовка «Сова»?

Теперь заголовок «Сова» прилегает снизу почти вплотную к картинке, с учётом её margin-bottom, но без своего большого отступа margin-top.

Таково поведение свойства clear. Оно сдвинуло элемент h2 вниз ровно настолько, чтобы элементов float не было сбоку от его верхней границы.

Если посмотреть на элемент заголовка внимательно в инструментах разработчика, то можно заметить отступ margin-top у заголовка по-прежнему есть, но он располагается «за» элементом float и не учитывается при работе в clear.

Чтобы исправить ситуацию, можно добавить перед заголовком пустой промежуточный элемент без отступов, с единственным свойством clear:both. Тогда уже под ним отступ заголовка будет работать нормально:

<h2>Винни-Пух</h2>
<div class="left">Картинка</div>
<p>Текст</p>

<div style="clear:both"></div>

<h2>Сова</h2>
<div class="left">Картинка</div>
<p>Текст</p>

Результат получившегося кода:

  • Свойство clear гарантировало, что <div style="clear:both"> будет под картинкой с float.
  • Заголовок <h2>Сова</h2> идёт после этого <div>. Так что его отступ учитывается.

Заполнение блока-родителя

Итак, мы научились располагать другие элементы под float. Теперь рассмотрим следующую особенность.

Из-за того, что блок с float удалён из потока, родитель не выделяет под него места.

Например, выделим для информации о Винни-Пухе красивый элемент-контейнер <div class="hero">:

<div class="hero">

  <h2>Винни-Пух</h2>

  <div class="left">Картинка</div>

  <p>Текст.</p>
</div>

Стиль контейнера:

.hero {
  background: #D2B48C;
  border: 1px solid red;
}

Результат получившегося кода:

Элемент с float оказался выпавшим за границу родителя .hero.

Чтобы этого не происходило, используют одну из следующих техник.

Поставить родителю float

Элемент с float обязан расшириться, чтобы вместить вложенные float.

Поэтому, если это допустимо, то установка float контейнеру всё исправит:

.hero {
  background: #D2B48C;
  border: 1px solid red;
  float: left;
}

Разумеется, не всегда можно поставить родителю float, так что смотрим дальше.

Добавить в родителя элемент с clear

Добавим элемент div style="clear:both" в самый конец контейнера .hero.

Он с одной стороны будет «нормальным» элементом, в потоке, и контейнер будет обязан выделить под него пространство, с другой – он знает о float и сместится вниз.

Соответственно, и контейнер вырастет в размере:

<div class="hero">

  <h2>Винни-Пух</h2>

  <div class="left">Картинка</div>

  <p>Текст.</p>

  <div style="clear:both"></div>
</div>

Результат – правильное отображение, как и в примере выше. Открыть код.

Единственный недостаток этого метода – лишний HTML-элемент в разметке.

Универсальный класс clearfix

Чтобы не добавлять в HTML-код лишний элемент, можно задать его через :after.

.clearfix:after {
  content: "."; /* добавить содержимое: "." */
  display: block;  /* сделать блоком, т.к. inline не может иметь clear */
  clear: both;  /* с обеих сторон clear */
  visibility: hidden; /* сделать невидимым, зачем нам точка внизу? */
  height: 0;  /* сделать высоту 0, чтобы не занимал место */
}

Добавив этот класс к родителю, получим тот же результат, что и выше. Открыть код.

overflow:auto/hidden

Если добавить родителю overflow: hidden или overflow: auto, то всё станет хорошо.

.hero {
  overflow: auto;
}

Этот метод работает во всех браузерах, полный код в песочнице.

Несмотря на внешнюю странность, этот способ не является «хаком». Такое поведение прописано в спецификации CSS.

Однако, установка overflow может привести к появлению полосы прокрутки, способ с псевдоэлементом :after более безопасен.

float вместо display:inline-block

При помощи float можно размещать блочные элементы в строке, похоже на display: inline-block:

Результат
index.html
<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <style>
    .gallery li {
      float: left;
      width: 130px;
      list-style: none;
      /* красивости */

      border: 1px solid black;
      text-align: center;
      margin: 5px;
    }
  </style>
</head>

<body>

  <ul class="gallery">
    <li>
      <img src="https://js.cx/carousel/1.png" width="130" height="130">
      <div>Картинка 1</div>
    </li>

    <li>
      <img src="https://js.cx/carousel/2.png" width="130" height="130">
      <div>Картинка 2</div>
    </li>

    <li>
      <img src="https://js.cx/carousel/3.png" width="130" height="130">
      <div>Картинка 3</div>
    </li>

    <li>
      <img src="https://js.cx/carousel/4.png" width="130" height="130">
      <div>Картинка 4</div>
    </li>

    <li>
      <img src="https://js.cx/carousel/5.png" width="130" height="130">
      <div>Картинка 5</div>
    </li>

    <li>
      <img src="https://js.cx/carousel/6.png" width="130" height="130">
      <div>Картинка 6</div>
    </li>

    <li>
      <img src="https://js.cx/carousel/7.png" width="130" height="130">
      <div>Картинка 7</div>
    </li>

    <li>
      <img src="https://js.cx/carousel/8.png" width="130" height="130">
      <div>Картинка 8</div>
    </li>

    <li>
      <img src="https://js.cx/carousel/9.png" width="130" height="130">
      <div>Картинка 9</div>
    </li>

  </ul>
</body>

</html>

Стиль здесь:

.gallery li {
  float: left;
  width: 130px;
  list-style: none;
}

Элементы float:left двигаются влево, а если это невозможно, то вниз, автоматически адаптируясь под ширину контейнера, получается эффект, аналогичный display: inline-block, но с особенностями float.

Вёрстка в несколько колонок

Свойство float позволяет делать несколько вертикальных колонок.

float:left + float:right

Например, для вёрстки в две колонки можно сделать два <div>. Первому указать float:left (левая колонка), а второму – float:right (правая колонка).

Чтобы они не ссорились, каждой колонке нужно дополнительно указать ширину:

<div>Шапка</div>
<div class="column-left">Левая колонка</div>
<div class="column-right">Правая колонка</div>
<div class="footer">Низ</div>

Стили:

.column-left {
  float: left;
  width: 30%;
}

.column-right {
  float: left;
  width: 70%;
}

.footer {
  clear: both;
}

Результат (добавлены краски):

Результат
index.html
<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <style>
    body,
    html {
      margin: 0;
      padding: 0;
    }

    .column-left {
      float: left;
      width: 30%;
    }

    .column-right {
      float: left;
      width: 70%;
      overflow: auto;
      /* расшириться вниз захватить float'ы */
    }

    .footer {
      clear: both;
    }

    .inner {
      margin: 1em;
    }
  </style>
</head>

<body>

  <div style="background:yellow">Шапка</div>

  <div class="column-left" style="background:#aef">
    <div class="inner">
      <!-- див для отступа внутри ширины родителя -->
      <h3>Персонажи:</h3>
      <ul>
        <li>Винни-Пух</li>
        <li>Ослик Иа</li>
        <li>Сова</li>
        <li>Кролик</li>
      </ul>
    </div>
  </div>

  <div class="column-right" style="background:tan">
    <div class="inner">
      <h3>Винни-Пух</h3>

      <img src="https://js.cx/clipart/winnie-mult.jpg" style="float:left; margin: 0 1em .5em 0">

      <p>Ви́нни-Пу́х (англ. Winnie-the-Pooh) — плюшевый мишка, персонаж повестей и стихов Алана Александра Милна (цикл не имеет общего названия и обычно тоже называется «Винни-Пух», по первой книге). Один из самых известных героев детской литературы XX века.</p>

      <p>В 1960-е—1970-е годы, благодаря пересказу Бориса Заходера «Винни-Пух и все-все-все», а затем и фильмам студии «Союзмультфильм», где мишку озвучивал Евгений Леонов, Винни-Пух стал очень популярен и в Советском Союзе.</p>

    </div>
  </div>

  <div class="footer" style="background:yellow">Низ</div>



</body>

</html>

В эту структуру легко добавить больше колонок с разной шириной. Правой колонке можно было бы указать и float:right.

float + margin

Ещё вариант – сделать float для левой колонки, а правую оставить в потоке, но с отбивкой через margin:

.column-left {
  float: left;
  width: 30%;
}

.column-right {
  margin-left: 30%;
}

.footer {
  clear: both;
}

Результат (добавлены краски):

Результат
index.html
<!DOCTYPE html>
<html>

<head>
  <meta charset="utf-8">
  <style>
    body,
    html {
      margin: 0;
      padding: 0;
    }

    .column-left {
      float: left;
      width: 30%;
    }

    .column-right {
      margin-left: 30%;
      width: 70%;
      overflow: auto;
      /* расшириться вниз захватить float'ы */
    }

    .footer {
      clear: both;
    }

    .inner {
      margin: 1em;
    }
  </style>
</head>

<body>

  <div style="background:yellow">Шапка</div>

  <div class="column-left" style="background:#aef">
    <div class="inner">
      <!-- див для отступа внутри ширины родителя -->
      <h3>Персонажи:</h3>
      <ul>
        <li>Винни-Пух</li>
        <li>Ослик Иа</li>
        <li>Сова</li>
        <li>Кролик</li>
      </ul>
    </div>
  </div>
  <div class="column-right" style="background:tan">
    <div class="inner">

      <h3>Винни-Пух</h3>

      <img src="https://js.cx/clipart/winnie-mult.jpg" style="float:left; margin: 0 1em .5em 0">

      <p>Ви́нни-Пу́х (англ. Winnie-the-Pooh) — плюшевый мишка, персонаж повестей и стихов Алана Александра Милна (цикл не имеет общего названия и обычно тоже называется «Винни-Пух», по первой книге). Один из самых известных героев детской литературы XX века.</p>

      <p>В 1960-е—1970-е годы, благодаря пересказу Бориса Заходера «Винни-Пух и все-все-все», а затем и фильмам студии «Союзмультфильм», где мишку озвучивал Евгений Леонов, Винни-Пух стал очень популярен и в Советском Союзе.</p>


    </div>
  </div>

  <div class="footer" style="background:yellow">Низ</div>



</body>

</html>

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

В современных браузерах (кроме IE10-) эту же задачу лучше решает flexbox.

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

Задачи

важность: 5

Галерея изображений состоит из картинок в рамках с подписями (возможно, с другой дополнительной информацией).

Пример галереи:

Технически вывод такой галереи можно реализовать при помощи списка UL/LI, где:

  1. каждый LI имеет display:inline-block
  2. каждый LI имеет float:left

Какие различия между этими подходами? Какой вариант выбрали бы вы?

Разница колоссальная.

В первую очередь она в том, что inline-block продолжают участвовать в потоке, а float – нет.

Чтобы её ощутить, достаточно задать себе следующие вопросы:

  1. Что произойдёт, если контейнеру UL поставить рамку border – в первом и во втором случае?
  2. Что будет, если элементы LI различаются по размеру? Будут ли они корректно перенесены на новую строку в обоих случаях?
  3. Как будут вести себя блоки, находящиеся под галереей?

Попробуйте сами на них ответить.

Затем читайте дальше.

Что будет, если контейнеру UL поставить рамку border?

Контейнер не выделяет пространство под float. А больше там ничего нет. В результате он просто сожмётся в одну линию сверху.

Попробуйте сами, добавьте рамку в песочнице.

А в случае с inline-block всё будет хорошо, т.к. элементы остаются в потоке.

Что будет, если элементы LI различаются по размеру? Будут ли они корректно перенесены на новую строку в обоих случаях?

При float:left элементы двигаются направо до тех пор, пока не наткнутся на границу внешнего блока (с учётом padding) или на другой float-элемент.

Может получиться вот так:

Вы можете увидеть это, открыв демо-галерею в отдельном окне и изменяя его размер:

При использовании inline-block таких странностей не будет, блоки перенесутся корректно на новую строку. И, кроме того, можно выровнять элементы по высоте при помощи li { vertical-align:middle }:

Как будут вести себя блоки, находящиеся под галереей?

В случае с float нужно добавить дополнительную очистку с clear, чтобы поведение было идентично обычному блоку.

Иначе блоки, находящиеся под галереей, вполне могут «заехать» по вертикали на территорию галереи.

Открыть решение в песочнице.

важность: 3

Сделайте дерево при помощи семантической вёрстки и CSS-спрайта с иконками (есть готовый).

Выглядеть должно так (не кликабельно):

  • Поддержка многострочных названий узлов
  • Над иконкой курсор становится указателем.

Исходный документ содержит список UL/LI и ссылку на картинку.

P.S. Достаточно сделать HTML/CSS-структуру, действия добавим позже.

Открыть песочницу для задачи.

Для решения можно применить принцип двухколоночной вёрстки float + margin. Иконка будет левой колонкой, а содержимое – правой.

Открыть решение в песочнице.

важность: 5

Оформите навигацию, центрированную внутри DIV'а:

Требования:

  • Левая стрелка – слева, правая – справа, список страниц – по центру.
  • Список страниц центрирован вертикально.
  • Текст сверху и снизу ни на что не наползает.
  • Курсор при наведении на стрелку или элемент списка становится стрелкой pointer.

P.S. Без использования таблиц.

Открыть песочницу для задачи.

HTML-структура:

<div class="nav">
  <img src="arrow-left.jpg" class="left" width="40" height="40">
  <img src="arrow-right.jpg" class="right" width="40" height="40">
  <ul class="pages">
    <li>...</li>
  </ul>
</div>

Стили:

.nav {
  height: 40px;
  width: 80%;
  margin: auto;
}

.nav .left {
  float: left;
  cursor: pointer;
}

.nav .right {
  float: right;
  cursor: pointer;
}

.nav .pages {
  list-style: none;
  text-align: center;
  margin: 0;
  padding: 0;
}

.nav .pages li {
  display: inline;
  margin: 0 3px;
  line-height: 40px;
  cursor: pointer;
}

Основные моменты:

  • Сначала идёт левая кнопка, затем правая, а лишь затем – текст. Почему так, а не лево – центр – право?

    Дело в том, что float смещает элемент вправо относительно обычного места. А какое обычное место будет у правого IMG без float?

    Оно будет под списком, так как список – блочный элемент, а IMG – инлайн-элемент. При добавлении float:right элемент IMG сдвинется вправо, оставшись под списком.

    Код в порядке лево-центр-право (неправильный):

    <div...>
      <img src="arrow-left.jpg" class="left" width="40" height="40">
      <ul class="pages"> (li) 1 2 3 4 5 6 7 8 9</ul>
      <img src="arrow-right.jpg" class="right" width="40" height="40">
    </div>

    Его демо:

    Правильный порядок: лево-право-центр, тогда float останется на верхней строке.

    Код, который даёт правильное отображение:

    <div ...>
      <img src="arrow-left.jpg" class="left" width="40" height="40">
      <img src="arrow-right.jpg" class="right" width="40" height="40">
      <ul class="pages"> .. список .. </ul>
    </div>

    Также можно расположить стрелки при помощи position: absolute. Тогда, чтобы текст при уменьшении размеров окна не налез на стрелки – нужно добавить в контейнер левый и правый padding:

    Выглядеть будет примерно так:

    <div style="position:relative; padding: 0 40px;">
      <img style="position:absolute;left:0" src="..left.." width="40" height="40">
      <ul> (li) 1 2 3 4 5 6 7 8 9 </ul>
      <img style="position:absolute;right:0" srr="..right.." width="40" height="40">
    </div>
  • Центрирование одной строки по вертикали осуществляется указанием line-height, равной высоте.

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

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

Открыть решение в песочнице.

важность: 4

Есть две колонки 30%/70%:

<style>
  .left {
    float:left;
    width:30%;
    background: #aef;
  }

  .right {
    float:right;
    width:70%;
    background: tan;
  }
</style>

<div class="left">
 Левая<br>Колонка
</div>
<div class="right">
 Правая<br>Колонка<br>...
</div>

Добавьте к правой колонке рамку border-left и отступ padding-left.

Двухколоночная вёрстка при этом не должна сломаться!

Желательно не трогать свойство width ни слева ни справа и не создавать дополнительных элементов.

Подсказка

Используйте свойство box-sizing.

Решение

Да, можно – указываем box-sizing: border-box и добавляем свойства:

<style>
  .left {
    float:left;
    width:30%;
    background: #aef;
  }

  .right {
    float:right;
    width:70%;

    box-sizing: border-box;
    -moz-box-sizing: border-box;

    border-left: 2px solid green;
    padding-left: 10px;

    background: tan;
  }
</style>

<div class="left">
 Левая<br>Колонка
</div>
<div class="right">
 Правая<br>Колонка<br>...
</div>
Карта учебника