УРОК Nº 9.
Самое понятное руководство по статическому, относительному, абсолютному и фиксированному позиционированию
Под "статическим позиционированием" подразумевается нормальный поток страницы, с которым мы работали до сих пор. Блоковая модель CSS, float элементы и схемы верстки flexbox работают в этом "статическом" потоке, но это не единственная схема позиционирования, доступная в CSS.
Три других типа позиционирования - "относительное", "абсолютное" и "фиксированное". Каждый из них позволяет вручную позиционировать элементы по определенным координатам, в отличие от более семантических вариантов flexbox и floats. Вместо того чтобы сказать: "Этот блок поместить в центр его контейнера", продвинутое позиционирование позволяет сказать нечто-то вроде: "Этот блок поместить на 20 пикселей выше и на 50 пикселей правее от начала координат его родителя".
Подавляющее большинство элементов на веб-странице должно быть расположено в соответствии со статическим потоком страницы. Другие схемы позиционирования появляются, когда вы хотите сделать более сложные вещи, например, изменить положение определенного элемента или анимировать компонент пользовательского интерфейса, не испортив окружающие элементы.
Эта глава состоит из двух частей. Мы начнем с изучения относительного, абсолютного и фиксированного позиционирования по отдельности, а затем применим все, что узнали, к созданию шикарного выпадающего меню.
Начните с создания нового проекта Atom под названием advanced-positioning и нового файла schemes.html со следующей разметкой:
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8'/>
<title>Positioning Is Easy!</title>
<link rel='stylesheet' href='styles.css'/>
</head>
<body>
<div class='container'>
<div class='example relative'>
<div class='item'><img src='images/static.svg' /></div>
<div class='item item-relative'><img src='images/static.svg' /></div>
<div class='item'><img src='images/static.svg' /></div>
</div>
</div>
<div class='container'>
<div class='example absolute'>
<div class='item'><img src='images/static.svg' /></div>
<div class='item item-absolute'><img src='images/absolute.svg' /></div>
<div class='item'><img src='images/static.svg' /></div>
</div>
</div>
<div class='container'>
<div class='example fixed'>
<div class='item'><img src='images/static.svg' /></div>
<div class='item item-fixed'><img src='images/fixed.svg' /></div>
<div class='item'><img src='images/static.svg' /></div>
</div>
</div>
</body>
</html>
У нас есть три примера для работы, все с одинаковой HTML-структурой. Изменение поведения позиционирования в каждом из них будет иметь кардинально разные эффекты.
На этой странице используются изображения, чтобы сделать наш пример немного нагляднее. При распаковке файлов в проект сохраняйте родительскую папку images, как показано выше. Не забудьте также создать файл styles.css и заполнить его необходимыми базовыми стилями:
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
height: 1200px;
}
.container {
display: flex;
justify-content: center;
}
.example {
display: flex;
justify-content: space-around;
}
width: 800px;
margin: 50px 0;
background-color: #D6E9FE;
}
.item img {
display: block;
}
Здесь нет ничего нового, просто знакомые приемы flexbox для создания сетки элементов. Единственная особенность - четко указанная высота элемента <body>, которая позволит нам прокручивать страницу вверх и вниз, чтобы продемонстрировать различные варианты позиционирования.
Свойство CSS position позволяет изменять схему позиционирования конкретного элемента. Его значение по умолчанию, как можно догадаться, статично. Если свойство position элемента не имеет значения static, он называется "позиционированным элементом". Именно позиционированным элементам посвящен весь этот урок.
Можно смешивать и сочетать различные схемы позиционирования. Опять же, большая часть вашей веб-страницы должна быть статически позиционирована, но часто можно встретить относительно и абсолютно позиционированные элементы внутри других элементов, которые являются частью нормального потока страницы.
"Относительное позиционирование" перемещает элементы относительно того места, где они обычно появляются в статическом потоке страницы. Это полезно для перемещения блоков, когда стандартный поток немного смещен.
Давайте превратим элемент .item-relative в файле schemes.html в относительно позиционированный элемент. Добавьте следующее правило в styles.css:
.item-relative {
position: relative;
top: 30px;
left: 30px;
}
Благодаря position: relative; он становится позиционированным элементом, а свойства top и left позволяют определить, насколько он смещен от своего статического положения. Это похоже на задание координат (x, y) для элемента.
Относительное позиционирование работает аналогично отступам margin, с одним очень важным отличием: значения top и left не влияют ни на окружающие элементы, ни на родительский элемент. Все остальное отображается так, как если бы .item-relative находился в своем исходном положении. Смещение можно рассматривать как действие, применяемое после того, как браузер завершит компоновку страницы.
Свойства top и left отмеряются от верхнего и левого краев исходного блока. С помощью свойств bottom и right мы можем задать смещение относительно других краев.
Например, следующий код сдвинет блок в противоположном направлении:
.item-relative {
position: relative;
bottom: 30px;
right: 30px;
}
Обратите внимание, что эти свойства принимают отрицательные значения, что означает два способа задать одно и то же смещение. Мы могли бы с тем же успехом использовать top: -30px; вместо объявленного ранее bottom: 30px;.
"Абсолютное позиционирование" - это то же самое, что и относительное позиционирование, но смещение происходит относительно всего окна браузера, а не исходной позиции элемента. Поскольку в этом случае нет никакой связи со статическим потоком страницы. Считайте, что это самый ручной способ размещения элемента.
Давайте рассмотрим это, добавив следующее правило в нашу таблицу стилей:
.item-absolute {
position: absolute;
top: 10px;
left: 10px;
}
Наша HTML-структура точно такая же, как и в предыдущем примере, но в этом случае фиолетовое изображение будет помещено в левый верхний угол окна браузера. Вы также можете попробовать задать значение bottom или right, чтобы получить более четкое представление о происходящем.
Другой интересный эффект абсолютного позиционирования заключается в том, что оно полностью удаляет элемент из нормального потока страницы. Это легче увидеть на примере элементов, выровненных по левому краю, поэтому давайте временно изменим свойство justify-content в нашем правиле .example:
.example {
display: flex;
justify-content: flex-start;
/* Обновите это */
/* ... */
}
В нашем примере с относительным позиционированием (первый ряд) на месте позиционируемого элемента все еще остается пространство, а при абсолютном позиционировании это пространство исчезает. Как будто .item-absolute даже не существует для своего родителя и окружающих элементов. Перед тем как двигаться дальше, обязательно измените justify-content обратно на space-around.
В большинстве случаев такое поведение не слишком полезно, поскольку в этом случае все на вашей странице должно быть абсолютно позиционировано - иначе мы получим непредсказуемые наложения статических элементов на абсолютные. Итак, зачем вообще существует absolute?
Абсолютное позиционирование становится гораздо более практичным, когда оно осуществляется относительно какого-либо другого элемента, находящегося в статическом потоке страницы. К счастью, существует способ изменить систему координат абсолютно позиционированного элемента.
Координаты абсолютных элементов всегда относительны к ближайшему контейнеру, который является позиционированным элементом. Он становится относительным по отношению к браузеру только тогда, когда ни один из его родителей не позиционирован. Так, если мы изменим родительский элемент .item-absolute на относительно позиционированный, он должен появиться в левом верхнем углу этого элемента, а не в окне браузера.
.absolute {
position: relative;
}
div-контейнер .absolute располагается в соответствии с нормальным потоком страницы, и мы можем вручную перемещать наш .item-absolute, куда нам нужно. Это великолепно, потому что если мы захотим изменить нормальный поток контейнера, скажем, для мобильного макета, любые абсолютно позиционированные элементы автоматически передвинутся вместе с ним.
Обратите внимание, что мы не указали никаких координат смещения для .absolute. Мы используем относительное позиционирование только для того, чтобы позволить нашему абсолютному элементу вернуться в нормальный поток страницы. Так мы безопасно сочетаем абсолютное позиционирование со статическим позиционированием.
У "фиксированного позиционирования" много общего с абсолютным: оно очень "ручное", элемент удален от нормального течения страницы, а система координат относительна ко всему окну браузера. Ключевое отличие заключается в том, что фиксированные элементы не прокручиваются вместе с остальной частью страницы.
Перейдем к обновлению нашего третьего примера, чтобы использовать фиксированное позиционирование:
.item-fixed {
position: fixed;
bottom: 0;
right: 0;
}
В результате красная картинка окажется в правом нижнем углу экрана. Попробуйте прокрутить страницу, и вы обнаружите, что она не перемещается вместе с остальными элементами на странице, в то время как абсолютно позиционированная фиолетовая картинка перемещается.
Так создаются навигационные панели, которые всегда остаются на экране, а также назойливые всплывающие и упорно не исчезающие pop-up баннеры.
Это немного выходит за рамки данного руководства, поскольку речь идет о HTML и CSS, а не о JavaScript. Однако анимация - это один из основных вариантов использования относительного и абсолютного позиционирования, поэтому давайте заглянем в будущее, анимировав один из наших элементов.
Эти усовершенствованные схемы позиционирования позволяют JavaScript перемещать элементы, избегая при этом какого-либо взаимодействия с окружающими элементами. Например, попробуйте скопировать и вставить следующее в файл schemes.html после третьего элемента .container. Элемент <script> должен быть последним внутри <body>.
<script>
var left = 0;
function frame() {
var element = document.querySelector('.item-relative');
left += 2;
element.style.left = left + 'px';
if (left >= 300) {
clearInterval(id)
}
}
var id = setInterval(frame, 10)
</script>
Этот код JavaScript создает простую анимацию, которая постоянно обновляет свойство left в .item-relative. При перезагрузке страницы вы должны увидеть, что синее изображение плавает по правому краю своего контейнера.
Это довольно примитивный пример, но вы, надеюсь, видите, как он применим к причудливым анимациям пользовательского интерфейса. Если бы вы попытались добиться того же эффекта, манипулируя свойствами margin или padding, вы бы невольно переместили статически расположенные блоки и/или содержащий их элемент .example.
Итак, это основные приемы. Давайте сделаем с их помощью что-нибудь продвинутое! В оставшейся части урока мы применим полученные навыки для создания замечательного навигационного меню с интерактивным выпадающим подменю для одной из ссылок. Мы создадим эту страницу с самого нуля.
Фиксированное позиционирование позволит сделать меню прикрепленным к верхней части страницы, а относительное позиционирование создаст якорь для абсолютно позиционированного выпадающего меню. Мы также поговорим о методиках создания навигационных меню и рассмотрим практическое применение псевдоклассов, о которых мы говорили на уроке Селекторы CSS.
Для начала нам нужна новая веб-страница под названием menu.html, которая имеет заголовок и простое меню верхнего уровня:
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8'/>
<title>Awesome!</title>
<link href='menu.css' rel='stylesheet'/>
</head>
<body>
<div class='header'>
<div class='logo'><img src='images/awesome-logo.svg'/></div>
<ul class='menu'>
<li class='dropdown'><span>Features ▾</span></li>
<li><a href='#'>Blog</a></li>
<li><a href='#'>Subscribe</a></li>
<li><a href='#'>About</a></li>
</ul>
</div>
</body>
</html>
Навигационные меню почти всегда должны быть оформлены в виде списка <ul>, а не кучи элементов <div>. Такая семантика делает навигацию вашего сайта гораздо более доступной для поисковых систем. Также обратите внимание, как мы готовимся к созданию нашего выпадающего меню, добавляя атрибут class к первому <li> в списке. Этот <span> позволит нам отличить ярлык от подменю, которое он открывает.
Далее нам понадобится новая таблица стилей menu.css, которая, помимо прочего, сделает наш .header более похожим на заголовок:
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
height: 1200px;
font-size: 18px;
font-family: sans-serif;
color: #5D6063;
}
a:link,
a:visited {
color: #5D6063;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
.header {
position: fixed;
display: flex;
justify-content: space-between;
width: 100%;
padding: 50px;
background: #D6E9FE;
}
Все это должно быть вам знакомо. Однако обратите внимание на фиксированное положение .header, благодаря которому наше меню навигации находится поверх любого контента, размещенного на странице.
Хотя навигационные меню на большинстве сайтов размечены как неупорядоченные списки, на самом деле они не выглядят как списки. Мы можем исправить это. С помощью свойства display, мы сделаем элементы списка строчными блоками вместо блоковых блоков. Добавьте в menu.css следующее:
.menu {
margin-top: 15px;
}
.menu > li {
display: inline;
margin-right: 50px;
}
.menu > li: last-of-type {
margin-right: 0;
}
Мы используем селекторы дочерних элементов, вместо селекторов потомков. Все потому, что мы хотим выбрать только элементы <li>, находящиеся как раз внутри .menu. Это станет особенно важным, когда мы добавим подменю, имеющее свои собственные элементы <li>, которые мы не хотим стилизовать с помощью этого правила. Также мы добавили отступы margins ко всем элементам списка, но удалили их из последнего <li> псевдоклассом :last-of-type. Отступы margins между элементами - довольно распространенная практика.
Наше подменю будет выглядеть так же, как и меню верхнего уровня, за исключением того, что все оно будет вложено в cписок [list]. Измените элемент .menu следующим образом, чтобы весь список .features-menu был завернут в первый <li> элемента .menu.
<ul class='menu'>
<li class='dropdown'><span>Features ▾</span>
<ul class='features-menu'> <!-- Start of submenu -->
<li><a href='#'<Harder</a></li>
<li><a href='#'<Better</a></li>
<li><a href='#'<Faster</a></li>
<li><a href='#'<Stronger</a></li>
</ul> <!-- End of submenu -->
</li>
<li><a href='#'<Blog</a></li> <!-- These are the same -->
<li><a href='#'<Subscribe</a></li>
<li><a href='#'<About</a></li>
</ul>
Это дает много важной информации для поисковых систем. Google увидит, что все эти новые элементы связаны с меткой Features и что они образуют отдельную секцию нашего сайта. Рекомендуем размечать сложные навигационные меню именно таким образом.
Что касается CSS, то с интерактивным выпадающим меню мы разберемся позже. Сейчас же давайте просто приведем наше подменю в нужный вид. Добавьте несколько простых стилей, чтобы было видно блок, который мы пытаемся позиционировать:
.features-menu {
display: flex;
flex-direction: column;
background: #B2D6FF;
border-radius: 5px;
padding-top: 60px;
}
.features-menu li {
list-style: none;
border-bottom: 1px solid #FFF;
padding: flex;
margin: flex;
}
.features-menu li:last-of-type {
border-bottom: none;
}
Само подменю оформлено правильно, но оно отображается в неправильном месте и сильно портит остальные элементы меню верхнего уровня. Этого следовало ожидать, поскольку оно все еще статически позиционировано, а значит, взаимодействует со своим родителем и окружающими элементами.
Для создания желаемой верстки используйте ваши новые навыки позиционирования CSS.
Мы хотим, чтобы остальные элементы меню верхнего уровня отображались так же, как и до добавления подменю, как будто подменю вообще не было. Да это как раз то, как себя ведут абсолютно позиционированные элементы. Давайте попробуем? Добавьте несколько строк в правило .features-menu:
.features-menu {
display: flex;
flex-direction: column;
background: #B2D6FF;
border-radius: 5px;
padding-top: 60px;
position: absolute; /* Добавьте эти строки */
top: -25px;
left: -30px;
}
Отлично! Подменю больше не является частью статического потока страницы, поэтому наши элементы меню верхнего уровня вернулись в нормальное состояние. Однако подменю должно появиться ниже ярлыка Features, а не в углу окна браузера. Какое совпадение... мы только что узнали, как это делается!
Подменю находится в <li class='dropdown'>. Превращение его в позиционированный элемент должно изменить систему координат, используемую нашим абсолютно позиционированным .features-menu:
.dropdown {
position: relative;
}
Однако есть одна проблема. Хотя подменю находится в правильном месте, но теперь оно закрывает ярлык Features.
Ранее мы не сталкивались с проблемой "глубины". До сих пор все наши HTML-элементы отображались друг над другом или друг под другом интуитивно понятным образом. Но поскольку мы занимаемся продвинутыми вещами, полагаться на то, что браузер сам определит, какие элементы отображаются поверх других, не стоит.
Свойство z-index позволяет управлять глубиной расположения элементов на странице. Если представить экран как трехмерное пространство, то отрицательные значения z-index уходят дальше вглубь страницы, а положительные - выходят за ее пределы.
Другими словами, элемент .features-menu должен иметь меньший z-index, чем ярлык Features. По умолчанию значение z-index равно 0, поэтому давайте сделаем их оба выше этого значения. Мы удобно обернули ярлык Features в <span>, что позволяет нам стилизовать его с помощью дочернего селектора, как показано ниже:
.dropdown > span {
z-index: 2;
position: relative; /* Это важно! */
cursor: pointer;
}
.features-menu {
/* ... */
z-index: 1;
}
Теперь ярлык Features должен появиться в верхней части подменю. Обратите внимание на строку position: relative;. Она необходима, потому что только позиционированные элементы обращают внимание на свойство z-index. Об этом легко забыть, поэтому сделайте заметку на следующий раз, на случай, если у вас возникнут проблемы с глубиной, а ваши правила CSS не будут иметь никакого результата.
Мы добавили пример использования свойства cursor, чтобы при наведении курсора на ярлык он выглядел как ссылка. Подробнее об этом можно прочитать на сайте Mozilla Developer Network.
Отлично! Подменю готово! Наша последняя задача - скрыть его, пока пользователь не наведет на него курсор. Помните псевдокласс :hover из главы "Селекторы CSS"? Мы используем его, чтобы превратить наше подменю в интерактивное выпадающее меню.
Сначала нам нужно изменить существующее правило .features-menu, добавив селектор-потомок :hover. Тогда подменю будет видно только когда пользователь наведет на него курсор. Обновите селектор .features-menu следующим образом:
.dropdown:hover
.features-menu { /* Раньше это было `.features-menu` */
display: flex; /* Оставьте все остальное в прежнем виде */
flex-direction: column;
background: #B2D6FF;
/* ... */
}
Затем нам нужно будет скрыть подменю с помощью свойства display. Добавьте новое правило в menu.css:
.features-menu {
/* Это новое правило. Добавьте его. */
display: none;
}
Установка display в none заставляет элемент полностью исчезнуть. Переопределив это значение с помощью flex в правиле :hover, мы фактически говорим браузеру снова показать .features-menu. Эта хитрая комбинация селекторов-потомков и псевдоклассов позволяет нам по условию скрывать или показывать элемент.
В этой главе мы рассмотрели четыре новые схемы верстки CSS:
Относительное позиционирование изменяло положение элемента, не затрагивая окружающие его блоки. Абсолютное позиционирование выводило элементы из статического потока страницы и размещало их относительно окна браузера. Относительно абсолютное позиционирование позволяло вернуться в статический поток страницы. Наконец, фиксированное позиционирование позволяло нам создавать элементы, которые не прокручивались вместе с остальной частью страницы.
Мы использовали эти новые методы позиционирования для создания довольно сложного навигационного меню. Если оно показалось вам сложным, значит, так оно и было. Но не волнуйтесь, вам не нужно заучивать HTML и CSS, на которых основано наше меню. Ваша цель должна заключаться в том, чтобы месяца через три вы могли вернуться к этому примеру и понять, что делают все эти декларации position: relative; и position: absolute;.
Это меню также является хорошим примером того, как начало работы с HTML-разметкой значительно упрощает жизнь. Сначала мы создали нужную нам смысловую структуру [semantic structure]. Затем мы написали несколько замысловатых CSS, чтобы расположить блоки именно там, где нам нужно. Если вы смотрите на сложный эскиз и не уверены, с чего начать, то это отличный способ начать решать поставленную задачу.
У нашего меню все еще есть одна большая недоработка: оно не создано для мобильных устройств. Во-первых, в смартфонах и планшетах отсутствует курсор. Во-вторых, наш макет не отображается хорошо, если размер браузера менее 960 пикселей. Первое требует немного магии JavaScript (или очень продвинутого CSS), поэтому мы оставим это для другого курса. Но второе мы сможем решить с помощью адаптивного дизайна на следующем уроке.