Плавающие элементы и обтекание текстом в CSS
Чтение — около 9 минут· без учёта практики
В предыдущем уроке мы
закончили с позиционированием. Плавающие элементы — ещё один способ сдвинуть элемент с его места в потоке. Когда-то
на них строили целые страницы: колонки, меню и сетки. Для раскладки страниц сейчас есть Flexbox и Grid, а у
float осталась задача, ради которой его и придумали, — обтекание фото текстом, как в журнале. Этот урок
дополнительный: в проект сайта Тони он не входит, но без него не прочитать старый CSS, а такой код встречается часто.
Свойство float
Пусть нужно компактно разместить на странице заголовок, два фото и текст. В примере ниже за каждым фото идёт отдельный абзац. Без float
каждое фото займёт отдельную строку, а рядом с ним останется много пустого места. Чтобы текст встал рядом с фото,
используют свойство
float. Оно прижимает элемент к левому или правому краю, и элемент становится плавающим: содержимое, которое идёт
после него, поднимается вверх и обтекает его с другой стороны.
float: попробуйте сами
Первое фото прижато к левому краю, и абзац обтекает его справа; второе — к правому, и текст обтекает его слева. Удалите float у класса .float-left — фото встанет на отдельную строку над абзацем.
index.html
<html lang="ru">
<head>
<meta charset="utf-8">
<title>Обтекание фото текстом: float</title>
<style>
body { font-family: system-ui, sans-serif; background-color: linen; color: #2b211b; line-height: 1.6; }
h2 {
text-align: center;
}
img {
width: 30%; /* 30% ширины родителя */
height: auto;
}
.float-left {
float: left; /* фото прижато к левому краю, текст обтекает его справа */
margin: 0 1rem 0.5rem 0; /* отступ между фото и текстом */
}
.float-right {
float: right; /* фото прижато к правому краю, текст обтекает его слева */
margin: 0 0 0.5rem 1rem;
}
</style>
</head>
<body>
<h2>Тони</h2>
<img src="assets/tony-toy.png" alt="Тони с канатом в зубах" class="float-left" width="300" height="400">
<p>
Тони — карликовый пинчер чёрно-подпалого окраса. Он весит чуть больше четырёх килограммов, но уверен, что охраняет
весь дом. Больше всего на свете Тони любит канат с мячиком «Удав»: его можно бросать, тянуть и приносить обратно
сколько угодно раз.
</p>
<img src="assets/album-hoodie.png" alt="Тони в бежевой толстовке с капюшоном" class="float-right" width="300" height="400">
<p>
Короткая шерсть пинчера плохо греет, поэтому в холодную погоду Тони гуляет в толстовке. На прогулке он внимательно
изучает каждый куст у пруда, здоровается с соседскими собаками и не упускает случая погонять голубей.
</p>
</body>
</html>
Что мы сделали в этом примере:
-
Задали изображениям ширину
30%от родителя, а заголовок выровняли по центру. -
В классе
.float-leftуказалиfloat: left: фото прижато к левому краю, а текст обтекает его справа. Чтобы текст не прилипал к фото, задали внешний отступ справа и снизу:margin: 0 1rem 0.5rem 0. -
В классе
.float-right—float: right: фото у правого края, текст слева, поэтому внешний отступ задан слева.
Расстояние до текста раньше часто задавали внутренним отступом изображения (padding). Это тоже работает,
но внешний отступ удобнее: он не увеличивает само изображение, а у фото с рамкой или фоном не появляется лишнего поля.
У свойства float такие значения:
| Значение | Описание |
|---|---|
none |
Элемент не плавающий и стоит на своём месте в потоке. Значение по умолчанию. |
left |
Элемент прижат к левому краю, содержимое обтекает его справа. |
right |
Элемент прижат к правому краю, содержимое обтекает его слева. |
inline-start, inline-end |
Логические значения: элемент прижат к началу или концу строки. В русском тексте это левый и правый край, а в языках с письмом справа налево, например в арабском, — наоборот. |
Плавающий элемент внутри контейнера
Плавающий элемент прижимается к краю не окна браузера, а своего родителя. Если у родителя ограничена ширина, элемент встанет у края этого родителя.
Плавающий элемент в контейнере: попробуйте сами
Контейнер шириной 60% стоит по центру, а плавающий блок прижат к его правому краю. Обратите внимание: блок шириной по содержимому, хотя это div.
index.html
<html lang="ru">
<head>
<meta charset="utf-8">
<title>Плавающий элемент внутри контейнера</title>
<style>
body { font-family: system-ui, sans-serif; background-color: linen; color: #2b211b; line-height: 1.6; }
.container {
width: 60%; /* 60% ширины родителя */
height: 200px;
margin: 0 auto; /* контейнер по центру */
background-color: tan;
}
.float-right {
float: right; /* прижат к правому краю контейнера, а не окна */
border: 1px solid saddlebrown;
background-color: #fffaf3;
}
</style>
</head>
<body>
<div class="container">
<div class="float-right">
<p>Что Тони любит больше?</p>
<ul>
<li>Мячик</li>
<li>Канат</li>
<li>Косточку</li>
</ul>
</div>
</div>
</body>
</html>
В этом примере контейнер шириной 60% от родителя (<body>) выровнен по центру и залит
цветом tan. Внутри — блок <div> с float: right. Заметьте ещё одну
особенность: внешнее поведение плавающего элемента становится блочным. При автоматической ширине обычный
незамещаемый блок подбирает её по содержимому с учётом доступного места; длинный текст может переноситься.
Внутренняя раскладка, например display: flex, при этом сохраняется.
Контейнер и плавающее фото
Браузер окружает плавающий элемент только текстом и строчным содержимым. Фон и рамки соседних блоков он не обтекает: они проходят под плавающим элементом. Кроме того, плавающий элемент исключается из нормального потока, и высота контейнера рассчитывается без него. Если фото выше текста, контейнер заканчивается раньше фото, и оно «вываливается» вниз — поверх рамки и того, что идёт следом.
overflow и flow-root: попробуйте сами
В первой заметке рамка и фон заканчиваются после строки текста, а фото выходит за них. Во второй и третьей контейнер растянулся до низа фото: у одной overflow: auto, у другой display: flow-root.
index.html
<html lang="ru">
<head>
<meta charset="utf-8">
<title>Контейнер с плавающим фото: overflow и flow-root</title>
<style>
body { font-family: system-ui, sans-serif; background-color: linen; color: #2b211b; line-height: 1.6; }
.note {
border: 3px solid seagreen;
background-color: rgb(46 139 87 / 0.2);
margin-bottom: 5rem;
}
img {
float: right;
width: 30%;
height: auto;
}
.fix-overflow {
overflow: auto; /* старый способ: контейнер включает плавающее фото */
}
.fix-flow-root {
display: flow-root; /* современный способ без побочных эффектов */
}
</style>
</head>
<body>
<div class="note">
<img src="assets/album-squirrel.png" alt="Тони с плюшевой белкой" width="300" height="400">
Без исправления: рамка и фон заканчиваются после текста, а фото выходит за контейнер.
</div>
<div class="note fix-overflow">
<img src="assets/album-squirrel.png" alt="Тони с плюшевой белкой" width="300" height="400">
overflow: auto — контейнер растянулся до низа фото.
</div>
<div class="note fix-flow-root">
<img src="assets/album-squirrel.png" alt="Тони с плюшевой белкой" width="300" height="400">
display: flow-root — тот же результат.
</div>
</body>
</html>
Чтобы контейнер включил плавающий элемент, нужно создать для него новый блочный контекст форматирования (англ. block formatting context, BFC) — независимую область, плавающие элементы которой не выходят наружу. Способов несколько:
-
display: flow-root— современный способ. Значение только и делает, что создаёт новый контекст: контейнер остаётся обычным блоком, без обрезки и полос прокрутки. При этом отступы его дочерних блоков больше не схлопываются с его отступами. -
overflow: autoилиoverflow: hidden— способ, который вы встретите в старом коде. Свойствоoverflowмы рассматривали в уроке «Размеры элементов». Любое значение, кромеvisibleиclip, заставляет браузер пересчитать высоту блока так, чтобы она включала плавающий элемент. Но побочные эффекты остаются:hiddenобрежет всё, что выходит за блок (например, тень или выпадающее меню), аautoможет добавить полосу прокрутки. - Приём clearfix — тоже из старого кода. Контейнеру добавляют псевдоэлемент, который встаёт после всех плавающих элементов и отменяет обтекание:
/* clearfix: так контейнеры включали плавающие элементы до появления flow-root */
.clearfix::after {
content: "";
display: table;
clear: both; /* о свойстве clear — в следующем разделе */
}
В новом коде используйте display: flow-root. А встретив overflow: hidden или класс
clearfix рядом с плавающими элементами, вы теперь знаете, зачем они там.
Отмена обтекания: clear
Рано или поздно понадобится, чтобы определённый элемент не обтекал плавающие элементы, а начинался под ними. Например, мы составляем список подписанных фото, где каждое фото прижато вправо. Если текст первой подписи короче фото, второй блок поднимается и тоже обтекает первое фото: фото выстраиваются лесенкой, а подписи оказываются не рядом со своими снимками.
Чтобы это исправить, используют свойство
clear. Оно определяет, с какой стороны элемента не допускаются предшествующие плавающие элементы:
если они там есть, элемент опускается ниже них. Учитываются float в том же блочном контексте форматирования;
например, плавающее фото внутри отдельного flow-root не влияет на clear снаружи.
| Значение | Описание |
|---|---|
none |
Плавающие элементы разрешены с обеих сторон. Значение по умолчанию. |
left |
Плавающие элементы не разрешены с левой стороны. |
right |
Плавающие элементы не разрешены с правой стороны. |
both |
Плавающие элементы не разрешены ни с левой, ни с правой стороны. |
inline-start, inline-end |
Логические значения: не разрешены со стороны начала или конца строки. |
clear: попробуйте сами
У блоков clear: right, поэтому второй блок начинается под первым фото. Удалите clear — второй блок поднимется и начнёт обтекать первое фото, а фото выстроятся лесенкой.
index.html
<html lang="ru">
<head>
<meta charset="utf-8">
<title>Отмена обтекания: clear</title>
<style>
body { font-family: system-ui, sans-serif; background-color: linen; color: #2b211b; line-height: 1.6; }
div {
border-top: 1px solid saddlebrown;
background-color: rgb(139 69 19 / 0.15);
clear: right; /* справа от блока не должно быть плавающих элементов */
}
img {
width: 25%;
height: auto;
}
.float-right {
float: right;
margin-bottom: 15px;
}
</style>
</head>
<body>
<div>
<img src="assets/album-puppy.png" alt="Тони-щенок" class="float-right" width="300" height="400">
Я в детстве: смотрю в камеру и ещё не знаю, что такое канат.
</div>
<div>
<img src="assets/album-laugh.png" alt="Тони смеётся, запрокинув голову" class="float-right" width="300" height="400">
Смеюсь над шуткой хозяина.
</div>
</body>
</html>
В этом примере мы запретили блокам <div> плавающие элементы с правой стороны — отменили правила
обтекания, и блоки выстроились один за другим. А чтобы отделить фото от следующего блока, задали изображениям
внешний отступ снизу 15px.
Обтекание по форме: shape-outside
Плавающий элемент — прямоугольник, и текст обтекает именно его, даже если фото скруглено до круга. Свойство
shape-outside задаёт плавающему элементу другую форму для обтекания: круг circle(), эллипс
ellipse(), многоугольник polygon() или форму непрозрачной части изображения
url(). Расстояние от формы до текста задаёт shape-margin.
shape-outside: попробуйте сами
Фото скруглено через border-radius: 50%, а shape-outside: circle(50%) заставляет текст обтекать его по кругу. Замените shape-margin: 1rem на 2rem — текст отодвинется от круга.
index.html
<html lang="ru">
<head>
<meta charset="utf-8">
<title>Обтекание по форме: shape-outside</title>
<style>
body { font-family: system-ui, sans-serif; background-color: linen; color: #2b211b; line-height: 1.6; }
img {
float: left;
width: 160px;
height: 160px;
object-fit: cover;
border-radius: 50%; /* фото выглядит кругом */
shape-outside: circle(50%); /* и текст обтекает его по кругу */
shape-margin: 1rem; /* расстояние от круга до текста */
}
</style>
</head>
<body>
<img src="assets/walk.jpg" alt="Тони на дорожке у пруда" width="240" height="426">
<p>
Шлейка надета — значит, идём гулять! Тони знает это лучше всех: стоит хозяину взять в руки оранжевую шлейку, как он
уже сидит у двери. На прогулке он проверяет любимые кусты у пруда, здоровается с соседями и обязательно находит
палку, которую нужно принести домой. Удалите свойство shape-outside — строки снова будут обтекать квадрат, в который
вписан круг, и у краёв фото появятся пустые углы.
</p>
</body>
</html>
Свойство действует только на плавающие элементы. border-radius меняет только то, как фото выглядит,
а shape-outside — только то, как его обтекает текст. Поэтому их задают вместе.
Когда float не нужен
Для раскладки страниц — колонок, меню в строку, сеток карточек — используйте Flexbox и Grid. Они не требуют отменять обтекание, выравнивают элементы по высоте и не заставляют контейнер «терять» содержимое. Плавающие элементы оставьте для обтекания текстом.
-
У флекс- и грид-элементов
floatне действует: если родитель — флекс- или грид-контейнер, свойство игнорируется. -
Элемент с абсолютным или фиксированным позиционированием не может быть плавающим:
floatу него вычисляется вnone. Об этом шла речь в первой части урока о позиционировании. -
На узком экране фото с обтеканием может оставить для текста слишком узкую колонку. Задавайте фото ширину в
процентах или отменяйте
floatмедиазапросом.
Проверьте себя
Выберите ответ — объяснение появится сразу. Ответ можно менять.
Задание
Ожидаемый результат: фото Тони у правого края карточки, первый абзац обтекает его слева. Карточка со светлым фоном и рамкой заканчивается ниже фото, а строка «Следующая прогулка — завтра утром» стоит под карточкой. Уже 30rem (480 пикселей) фото стоит по центру над текстом, и текст занимает всю ширину карточки.
Выполните задание
Условие задания
index.html
<html lang="ru">
<head>
<meta charset="utf-8">
<title>Заметка о прогулке</title>
<style>
body { font-family: system-ui, sans-serif; background-color: linen; color: #2b211b; line-height: 1.6; margin: 1rem; }
.note {
padding: 1rem;
border: 1px solid tan;
border-radius: 16px;
background-color: #fffaf3;
}
.note img {
width: 40%;
height: auto;
border-radius: 8px;
}
</style>
</head>
<body>
<article class="note">
<h2>Прогулка у пруда</h2>
<img src="assets/walk.jpg" alt="Тони на дорожке у пруда" width="240" height="426">
<p>Сегодня мы с хозяином гуляли у пруда целый час. Я проверил все кусты, поздоровался с таксой Бусей и нашёл
отличную палку.</p>
<h3>Что взять с собой</h3>
<ul>
<li>шлейку и поводок;</li>
<li>канат с мячиком;</li>
<li>воду и миску.</li>
</ul>
</article>
<p>Следующая прогулка — завтра утром.</p>
</body>
</html>
Дополнительная задача
Эта задача — тренировка чтения старого кода: в новых проектах такую страницу собирают на Grid. Сверстайте страницу на плавающих элементах:
- Шапка с меню из четырёх пунктов, прижатых к правому краю. Обратите внимание на порядок пунктов в результате: первым у правого края оказывается «Домой».
- Тёмная боковая панель слева: ширина
15%от родителя, высота400px. - Справа от панели — серое меню с пунктами в строку, под ним белая область с заголовком по центру.
- Под всем — серый подвал на всю ширину окна.
Подсказка: в документе использованы цвета #333, #999 и #fff; остальные
значения выберите сами. Меню и основному содержимому задайте внешний отступ слева, равный ширине панели.
Ожидаемый результат: справа в белой шапке пункты «Раздел 3», «Раздел 2», «Раздел 1», «Домой» — в обратном
порядке, потому что каждый следующий пункт с float: right встаёт левее предыдущего. Слева тёмная панель
высотой 400 пикселей, справа от неё серое меню и белая область с заголовком, внизу серый подвал на всю ширину.
Выполните дополнительную задачу
Условие задания
index.html
<html lang="ru">
<head>
<meta charset="utf-8">
<title>Дополнительное задание</title>
<style>
body { margin: 0; }
</style>
</head>
<body>
<header>
<ul>
<li><a href="#">Домой</a></li>
<li><a href="#">Раздел 1</a></li>
<li><a href="#">Раздел 2</a></li>
<li><a href="#">Раздел 3</a></li>
</ul>
</header>
<aside></aside>
<nav>
<ul>
<li><a href="#">Домой</a></li>
<li><a href="#">Раздел 1</a></li>
<li><a href="#">Раздел 2</a></li>
<li><a href="#">Раздел 3</a></li>
</ul>
</nav>
<article>
<h1>Вёрстка сайта плавающими элементами</h1>
<p>Основное содержимое страницы.</p>
</article>
<footer>2017, basicweb.ru, дополнительное задание.</footer>
</body>
</html>
История: вёрстка страниц на float
Как раньше верстали главную страницу плавающими элементами
До появления Flexbox и Grid колонки, меню и ряды фото строили на float. В старом коде вы встретите
такую разметку главной страницы:
Старая вёрстка: попробуйте сами
Меню шапки, боковое меню и ряд фото построены на float. Сузьте окно: боковое меню шириной 15% становится тесным, а разделы с отступом 16% не перестраиваются — так выглядели неадаптивные страницы.
index.html
<html lang="ru">
<head>
<meta charset="utf-8">
<title>Вёрстка страницы плавающими элементами</title>
<style>
body { font-family: system-ui, sans-serif; background-color: linen; color: #2b211b; }
header {
background-color: #777;
}
header ul {
list-style-type: none; /* убираем маркеры */
margin: 0;
padding: 0;
overflow: auto; /* шапка включает плавающие пункты */
}
header li {
float: left; /* пункты меню в строку */
color: white;
}
header li a {
display: inline-block; /* строчной ссылке не задать вертикальные отступы */
color: orange;
padding: 10px;
text-decoration: none;
}
header li a:hover {
background-color: #888;
}
footer {
background-color: #888;
clear: both; /* подвал не поднимается к плавающим элементам */
min-height: 30px;
}
nav {
margin-top: 2%;
float: left; /* боковое меню слева */
width: 15%;
border: 1px solid #888;
}
nav ul {
list-style-type: none;
padding: 0;
}
nav li a {
color: orange;
padding: 10px;
text-decoration: none;
}
nav li a:hover {
color: #777;
}
section {
margin: 2% 0 0 16%; /* отступ слева больше ширины меню — ровная колонка */
border: 1px solid #888;
}
img {
width: 13%;
height: auto;
margin: 2% 10%;
float: left; /* фотографии в ряд */
border: 1px solid orange;
}
.clear-photos {
clear: left; /* ряд фото начинается под боковым меню */
}
</style>
</head>
<body>
<!-- шапка сайта -->
<header>
<ul>
<li><a href="#">Домой</a>|</li>
<li><a href="#">Раздел 1</a>|</li>
<li><a href="#">Раздел 2</a>|</li>
<li><a href="#">Раздел 3</a></li>
</ul>
</header>
<!-- дополнительная навигация -->
<nav>
<ul>
<li><a href="#">Домой</a></li>
<li><a href="#">Раздел 1</a></li>
<li><a href="#">Раздел 2</a></li>
<li><a href="#">Раздел 3</a></li>
</ul>
</nav>
<!-- основное содержимое -->
<section>Канат с мячиком «Удав» — моя любимая игрушка.</section>
<section>Резиновая косточка — для вечеров, когда гулять уже поздно.</section>
<div class="clear-photos">
<img src="assets/album-puppy.png" alt="Тони-щенок" width="300" height="400">
<img src="assets/album-sleep.png" alt="Тони спит" width="300" height="319">
<img src="assets/album-laugh.png" alt="Тони смеётся" width="300" height="400">
</div>
<footer>2016, все права принадлежат Тони и сайту basicweb.ru.</footer>
</body>
</html>
По порядку, что сделано в этом примере:
-
Шапка (
<header>) — фон#777. У списка меню убраны маркеры (list-style-type: none) и отступы, аoverflow: autoзаставляет шапку включить плавающие пункты, — та же особенность, что мы разбирали выше. Пункты (<li>) плавающие и прижаты к левому краю. Ссылки — строчно-блочные (display: inline-block), чтобы задать им внутренние отступы10px; цвет оранжевый, без подчёркивания, при наведении фон меняется на#888(псевдокласс:hover). -
Подвал (
<footer>) — фон, минимальная высота30pxиclear: both: плавающие элементы запрещены с обеих сторон, поэтому подвал не поднимается к ним (здесь достаточно и значенияleft). -
Боковое меню (
<nav>) — внешний отступ сверху2%, плавающее, прижато к левому краю, ширина15%от<body>и рамка1px solid #888. У списка убраны маркеры и внутренние отступы; ссылки оранжевые, без подчёркивания, при наведении серые. -
Разделы с содержимым (
<section>) — внешний отступ слева16%при ширине меню15%. Разделы обтекают меню, а отступ выравнивает их ровной колонкой; сверху отступ2%(margin: 2% 0 0 16%). -
Фото лежат в блоке с классом
.clear-photosиclear: left, поэтому ряд начинается под боковым меню, а не обтекает его. Сами изображения — шириной13%, с отступами2% 10%, плавающие, прижаты влево и обведены оранжевой рамкой.
Такая вёрстка требовала подбирать проценты, следить за отменой обтекания и плохо приспосабливалась к узким экранам. Сегодня ту же раскладку собирают сеткой: каркас из шапки, бокового меню, содержимого и подвала — на Grid, меню в строку и ряд фото — на Flexbox.