Чтобы текст обтекал картинку - как в газетной вёрстке, - изображению нужно свойство float: left или float: right. Картинка "прилипает" к нужному краю, а весь текст рядом с ней сам обтекает её без единой обёртки или flex/grid-конструкции. У этого древнего свойства до сих пор есть одна задача, с которой не справляются ни Flexbox, ни Grid - именно она разбирается в этой статье.
В статье:
Как работает float: картинка слева или справа от текста
float выводит элемент из обычного потока документа и прижимает его к левому или правому краю родителя. Блочные элементы после него встают на своё обычное место, как будто float-элемента нет вообще, а вот строчное содержимое - текст - огибает его, занимая свободное место сбоку. Именно поэтому эффект обтекания работает только с текстом (и другим инлайн-содержимым), а не с соседними блоками.
Отступ между картинкой и текстом задаётся margin'ом на самой float-картинке - margin на абзаце текста картинку не отодвинет, потому что текст технически "подтекает" под неё, а не стоит с ней в одном ряду. Ещё один нюанс: применение float автоматически превращает элемент в блочный по вычисляемому display, даже если это был изначально строчный img.
float: left; у картинки - текст абзаца обтекает её справа.
margin-left: 20px; на самом абзаце вместо margin на картинке - отступ не появится, картинка и текст всё равно будут слипаться.
Пример: как float обтекает текст
Это обычный абзац текста рядом с картинкой. Как только у картинки стоит float: left, текст сам обтекает её справа и снизу - не нужно ни оборачивать оба элемента во flex-контейнер, ни считать проценты ширины. Попробуйте прочитать этот абзац дальше - текст продолжает огибать картинку до тех пор, пока не закончится место рядом с ней по высоте, а затем снова занимает всю ширину строки.
.article-image {
float: left;
margin: 0 16px 8px 0;
}
/* для правого края: float: right; margin: 0 0 8px 16px; */
Проблема "схлопывания" контейнера и три способа её решить
Если внутри контейнера ВСЕ дочерние элементы float-нутые, у родителя схлопывается высота почти до нуля - ведь float выводит элементы из потока, и родитель их как бы не "видит" при расчёте своей высоты. На практике это выглядит так: фон или рамка контейнера обрывается там, где должны быть картинки, а сам контейнер выглядит пустым.
Есть три способа заставить родителя учитывать float-детей:
Пример: как flow-root решает схлопывание
Без исправления - пунктирная рамка обрывается сразу, "не замечая" картинку:
С display: flow-root - рамка честно обхватывает картинку по высоте:
/* Старый способ - clearfix-хак через псевдоэлемент */
.wrapper::after {
content: "";
display: block;
clear: both;
}
/* Способ через overflow - тоже работает, но может обрезать
тень или добавить нежелательный скролл */
.wrapper {
overflow: auto;
}
/* Современный способ - без побочных эффектов */
.wrapper {
display: flow-root;
}
clearfix-хак (первый вариант) держится на том, что генерируемый ::after-элемент получает clear: both и как бы "продавливает" реальную высоту контейнера ниже float-детей - долгое время это был единственный способ, отсюда и название. overflow: auto тоже "исправляет" схлопывание, потому что создаёт новый блочный форматирующий контекст (BFC), но как побочный эффект может обрезать тени или показать лишний скроллбар. display: flow-root создан специально для этой задачи - никаких побочных эффектов, поддержка около 96% браузеров по данным caniuse, и это рекомендуемый способ на сегодняшний день.
float внутри Flexbox и Grid: почему свойство перестаёт действовать
float игнорируется у элементов, которые сами являются flex- или grid-элементами - то есть у прямых детей контейнера с display: flex или display: grid. Если добавить float ребёнку flex-контейнера, ничего не произойдёт: расположением там управляет сам flex/grid-алгоритм, а float для этих детей просто не применяется.
Пример: float молча игнорируется во flex-контейнере
У блока ниже стоит float: left, но родитель - display: flex, поэтому float ни на что не влияет, элемент встаёт как обычный flex-элемент:
Текст рядом не обтекает картинку - он просто соседний flex-элемент.
.card {
display: flex; /* родитель - flex-контейнер */
}
.card img {
float: left; /* не сработает: это flex-элемент, float игнорируется */
}
Если внутри карточки на Flexbox/Grid всё-таки нужно обтекание текстом вокруг картинки, float стоит применять не к самому flex/grid-элементу, а к вложенному тексту-блоку внутри одной из ячеек - то есть создавать обычный поток текста уже внутри конкретной flex/grid-области, а не пытаться float-ить сам flex-элемент.
Обтекание по контуру: shape-outside для не-прямоугольных картинок
По умолчанию текст обтекает прямоугольную область картинки, даже если на самой картинке круглый или неровный силуэт. Свойство shape-outside меняет форму этой невидимой границы обтекания - но работает оно только вместе с float, применяется исключительно к float-элементам.
Поддерживаются готовые фигуры circle(), ellipse(), polygon(), inset(), а также реальное изображение через url() - в этом случае граница вычисляется по альфа-каналу картинки (какие пиксели прозрачные), и порог прозрачности можно настроить свойством shape-image-threshold. Поддержка широкая, Baseline-стабильная с января 2020 года.
Пример: как работает shape-outside
Текст здесь обтекает не прямоугольник картинки, а её реальную круглую форму - буквы подходят ближе к кругу сверху и снизу, а не останавливаются на невидимом квадратном углу. Это и есть эффект shape-outside: circle(50%).
.round-photo {
float: left;
shape-outside: circle(50%);
border-radius: 50%;
width: 110px;
height: 110px;
}
Сравнение способов
| Приём | Когда использовать | Поддержка браузерами |
|---|---|---|
float: left / float: right |
Обтекание текста вокруг картинки, как в газетной вёрстке | Все браузеры |
Clearfix (::after + clear: both) |
Исправить схлопывание контейнера старым способом, для очень старых браузеров | Все браузеры |
overflow: auto |
Быстро исправить схлопывание, если не важны побочные эффекты (обрезка тени, скролл) | Все браузеры |
display: flow-root |
Исправить схлопывание контейнера без побочных эффектов (рекомендуемый способ) | Около 96% браузеров (Baseline) |
shape-outside |
Обтекание по контуру не-прямоугольной картинки, только вместе с float | Baseline с января 2020 года |
Частые ошибки
| Ошибка | Что происходит | Как исправить |
|---|---|---|
| margin задан абзацу текста, а не картинке | Отступ не появляется, текст всё равно "слипается" с картинкой | Задать margin на самой float-картинке |
| Все дочерние элементы контейнера - float, а у самого контейнера нет фикса | Высота родителя схлопывается почти до нуля, фон или рамка обрываются | Добавить контейнеру display: flow-root (или clearfix/overflow: auto) |
| float задан прямому flex- или grid-элементу | Свойство молча игнорируется, обтекания нет | Применять float к вложенному тексту внутри ячейки, а не к самому flex/grid-элементу |
shape-outside указан без float | Форма обтекания не применяется, картинка остаётся прямоугольной | Добавить float: left или float: right тому же элементу |
Подробнее о том, что такое блочные и строчные элементы и как свойство display определяет их поведение на странице - в уроке курса Блочные и строчные элементы: display block, inline, inline-block. float, как показано выше, сам меняет вычисляемый display элемента на блочный - это прямое продолжение темы того урока.