🔍 Начните печатать, чтобы искать по книге или перейти к нужной странице по номеру
Удобно листать не только прокруткой, но и клавишами‑стрелками:
Эта книга — пошаговая инструкция по вёрстке сайтов на языках ХТМЛ и ЦСС. Вы узнаете не только как сверстать сайт, но и как опубликовать его в интернете, настроить красивый шаринг в соцсети и подключить системы аналитики.
Отдельный раздел книги посвящён работе с верстальщиком: как ставить задачи, получать предсказуемый результат и правильно принимать и внедрять вёрстку.
Знакомство с ХТМЛ и ЦСС
Модули
Расстановка
Страницы
Спецэффекты
Контроль качества
Публикация
Как работать с верстальщиком
Эта книга — пошаговая инструкция по вёрстке сайтов на языках ХТМЛ и ЦСС. Вы узнаете не только как сверстать сайт, но и как опубликовать его в интернете, настроить красивый шаринг в соцсети и подключить системы аналитики.
Отдельный раздел книги посвящён работе с верстальщиком: как ставить задачи, получать предсказуемый результат и правильно принимать и внедрять вёрстку.
Знакомство с ХТМЛ и ЦСС
Модули
Расстановка
Страницы
Спецэффекты
Контроль качества
Публикация
Как работать с верстальщиком
Типичная шапка из трёх элементов: логотипа, меню и телефона
В контейнере <nav> — navigation — собирают ссылки для навигации по сайту
Если не свешивать круглый логотип за линию сетки, он будет казаться сдвинутым
Шапка и подвал сайта чаще всего имеют форму строк, прижатых к верху и низу страницы
Мы используем max‑content, а не min‑content, чтобы пункты меню из нескольких слов не переносились
Аналогично можно было бы выделить текущий пункт меню начертанием или цветом
По умолчанию содержимое грид‑ячеек выравнивается по верхнему краю
В гридах align‑items управляет выравниванием по вертикали, а justify‑items — по горизонтали
В гридах justify‑self управляет выравниванием внутри ячейки
Иногда подчёркивание делают с помощью border‑bottom, background‑image или box‑shadow
Сверстаем простую шапку «в линию», встречающуюся на большинстве сайтов. Сосредоточимся только на раскладке, опустив типографику и стили для сброса.
Вёрстка с чистого листа
Начнём с разметки. Соберём шапку в контейнере <header>. Для логотипа возьмём див с картинкой, завёрнутой в ссылку на главную страницу. Меню соберём в ненумерованный список <ul>. Телефон сделаем кликабельной ссылкой с хитрым протоколом tel:
<header class="header">
<div class="logo">
<a href="/">
<img src="logo.svg">
</a>
</div>
<nav class="nav">
<ul>
<li class="nav__item">
<a href="/products/">Продукция</a>
</li>
<li class="nav__item">
<a href="/services/">Услуги</a>
</li>
<li class="nav__item is__active">
<a href="/blog/">Блог</a>
</li>
<li class="nav__item">
<a href="/contacts/">Контакты</a>
</li>
</ul>
</nav>
<div class="tel">
<a href="tel:+74954005050">
+7 495 400‑50‑50
</a>
</div>
</header>
Стилизуем логотип. Зафиксируем его размеры и свесим кружок логотипа за линии сетки:
.logo {
width: 40px;
height: 40px;
margin: 0 -5px;
}
Стилизуем меню. Включим гриды и разложим пункты меню по колонкам:
.nav ul {
display: grid;
grid-auto-flow: column;
}
Определим автоматические колонки по максимальному содержимому и зададим отступ между пунктами меню:
.nav ul {
display: grid;
grid-auto-flow: column;
grid-auto-columns: max-content;
column-gap: 20px;
}
Стилизуем пункты меню. Увеличим кликабельную область ссылок и стилизуем текущий пункт меню:
.nav ul {
display: grid;
grid-auto-flow: column;
grid-auto-columns: max-content;
column-gap: 20px;
align-items: center;
}
.nav__item a {
position: relative;
z-index: 1;
}
.nav__item a::before {
content: '';
position: absolute;
inset: -3px -7px;
z-index: -1;
}
.nav__item.is__active a {
color: #fff;
text-decoration: none;
}
.nav__item.is__active a::before {
background-color: #ff5722;
border-radius: 8px;
}
Настроим раскладку шапки в целом. Включим гриды и разложим логотип, меню и телефон по колонкам. Заодно добавим шапке верхний падинг и сделаем её контейнером‑обёрткой, чтобы она не прилипала к краям и правильно управляла своей шириной:
.header {
display: grid;
grid-template-columns: min-content max-content 1fr;
column-gap: 20px;
padding-top: 10px;
}<header class="header layoutContainer">
<!-- … -->
</header>Обёртка для страницы
Отцентрируем всё по вертикали:
.header {
display: grid;
grid-template-columns: min-content max-content 1fr;
column-gap: 20px;
padding-top: 10px;
align-items: center;
line-height: 1;
}
Отожмём телефон к правому краю:
.header {
display: grid;
grid-template-columns: min-content max-content 1fr;
column-gap: 20px;
padding: 10px;
align-items: center;
}
.header .tel {
justify-self: end;
}Уберём подчеркивания у ссылок в шапке:
.header {
display: grid;
grid-template-columns: min-content max-content 1fr;
column-gap: 20px;
padding: 10px;
align-items: center;
}
.header .tel {
justify-self: end;
}
.header a {
text-decoration: none;
}Типичная шапка из трёх элементов: логотипа, меню и телефона
В контейнере <nav> — navigation — собирают ссылки для навигации по сайту
Если не свешивать круглый логотип за линию сетки, он будет казаться сдвинутым
Шапка и подвал сайта чаще всего имеют форму строк, прижатых к верху и низу страницы
Мы используем max‑content, а не min‑content, чтобы пункты меню из нескольких слов не переносились
Аналогично можно было бы выделить текущий пункт меню начертанием или цветом
По умолчанию содержимое грид‑ячеек выравнивается по верхнему краю
В гридах align‑items управляет выравниванием по вертикали, а justify‑items — по горизонтали
В гридах justify‑self управляет выравниванием внутри ячейки
Иногда подчёркивание делают с помощью border‑bottom, background‑image или box‑shadow
Сверстаем простую шапку «в линию», встречающуюся на большинстве сайтов. Сосредоточимся только на раскладке, опустив типографику и стили для сброса.
Вёрстка с чистого листа
Начнём с разметки. Соберём шапку в контейнере <header>. Для логотипа возьмём див с картинкой, завёрнутой в ссылку на главную страницу. Меню соберём в ненумерованный список <ul>. Телефон сделаем кликабельной ссылкой с хитрым протоколом tel:
<header class="header">
<div class="logo">
<a href="/">
<img src="logo.svg">
</a>
</div>
<nav class="nav">
<ul>
<li class="nav__item">
<a href="/products/">Продукция</a>
</li>
<li class="nav__item">
<a href="/services/">Услуги</a>
</li>
<li class="nav__item is__active">
<a href="/blog/">Блог</a>
</li>
<li class="nav__item">
<a href="/contacts/">Контакты</a>
</li>
</ul>
</nav>
<div class="tel">
<a href="tel:+74954005050">
+7 495 400‑50‑50
</a>
</div>
</header>
Стилизуем логотип. Зафиксируем его размеры и свесим кружок логотипа за линии сетки:
.logo {
width: 40px;
height: 40px;
margin: 0 -5px;
}
Стилизуем меню. Включим гриды и разложим пункты меню по колонкам:
.nav ul {
display: grid;
grid-auto-flow: column;
}
Определим автоматические колонки по максимальному содержимому и зададим отступ между пунктами меню:
.nav ul {
display: grid;
grid-auto-flow: column;
grid-auto-columns: max-content;
column-gap: 20px;
}
Стилизуем пункты меню. Увеличим кликабельную область ссылок и стилизуем текущий пункт меню:
.nav ul {
display: grid;
grid-auto-flow: column;
grid-auto-columns: max-content;
column-gap: 20px;
align-items: center;
}
.nav__item a {
position: relative;
z-index: 1;
}
.nav__item a::before {
content: '';
position: absolute;
inset: -3px -7px;
z-index: -1;
}
.nav__item.is__active a {
color: #fff;
text-decoration: none;
}
.nav__item.is__active a::before {
background-color: #ff5722;
border-radius: 8px;
}
Настроим раскладку шапки в целом. Включим гриды и разложим логотип, меню и телефон по колонкам. Заодно добавим шапке верхний падинг и сделаем её контейнером‑обёрткой, чтобы она не прилипала к краям и правильно управляла своей шириной:
.header {
display: grid;
grid-template-columns: min-content max-content 1fr;
column-gap: 20px;
padding-top: 10px;
}<header class="header layoutContainer">
<!-- … -->
</header>Обёртка для страницы
Отцентрируем всё по вертикали:
.header {
display: grid;
grid-template-columns: min-content max-content 1fr;
column-gap: 20px;
padding-top: 10px;
align-items: center;
line-height: 1;
}
Отожмём телефон к правому краю:
.header {
display: grid;
grid-template-columns: min-content max-content 1fr;
column-gap: 20px;
padding: 10px;
align-items: center;
}
.header .tel {
justify-self: end;
}Уберём подчеркивания у ссылок в шапке:
.header {
display: grid;
grid-template-columns: min-content max-content 1fr;
column-gap: 20px;
padding: 10px;
align-items: center;
}
.header .tel {
justify-self: end;
}
.header a {
text-decoration: none;
}Адаптируем получившуюся шапку для мобильных устройств. Будем считать, что в нашем дизайне мобильные устройства — это устройства с экраном шириной менее 992 пикселей.
Обернём <nav> в див с классом navHolder — он пригодится нам для прокрутки и обрезания меню:
<header class="header">
<div class="logo">…</div>
<div class="navHolder">
<nav class="nav">
<!-- … -->
</nav>
</div>
<div class="tel">…</div>
</header>Поменяем раскладку шапки. Вместо трёх колонок сделаем сетку 2×2:
@media (width < 992px) {
.header {
grid-template-columns: min-content 1fr;
grid-template-rows: min-content min-content;
row-gap: 15px;
}
}Переставим телефон в правый верхний угол, меню поставим вниз и растянем на две колонки:
@media (width < 992px) {
.header {
grid-template-columns: min-content 1fr;
grid-template-rows: min-content min-content;
row-gap: 15px;
}
.header .tel {
grid-column: 2;
grid-row: 1;
}
.header .navHolder {
grid-column: 1 / span 2;
grid-row: 2;
}
}
Растянем меню на всю ширину экрана:
@media (width < 992px) {
.header {
grid-template-columns: min-content 1fr;
grid-template-rows: min-content min-content;
row-gap: 15px;
}
.header .tel {
grid-column: 2;
grid-row: 1;
}
.header .navHolder {
grid-column: 1 / span 2;
grid-row: 2;
margin-left: -10px;
margin-right: -10px;
}
}
Включим горизонтальную прокрутку в меню:
@media (width < 992px) {
.header {
grid-template-columns: min-content 1fr;
grid-template-rows: min-content min-content;
row-gap: 15px;
}
.header .tel {
grid-column: 2;
grid-row: 1;
}
.header .navHolder {
grid-column: 1 / span 2;
grid-row: 2;
margin-left: -10px;
margin-right: -10px;
}
.nav {
overflow-x: scroll; /* Зададим возможность горизонтальной прокрутки */
-webkit-overflow-scrolling: touch; /* Для Сафари дополнительно зададим инерцию при прокрутке. С инерцией меню будет «оттягиваться» при попытке прокрутить меню за его пределы. */
}
}
Из‑за overflow обрезались поля у текущего пункта в меню. Плюс при прокрутке появляется горизональный скролбар. Устраним эти проблемы:
@media (width < 992px) {
.header {
grid-template-columns: min-content 1fr;
grid-template-rows: min-content min-content;
row-gap: 15px;
}
.header .tel {
grid-column: 2;
grid-row: 1;
}
.header .navHolder {
grid-column: 1 / span 2;
grid-row: 2;
margin-left: -10px;
margin-right: -10px;
}
.nav {
padding: 10px; /* Добавим дополнительные поля */
margin: -10px 0; /* С помощью отрицательных отступов сверху-снизу компенсируем «нарощенные» поля */
scrollbar-width: none; /* Скроем скролбар в Хроме и Файрфоксе */
overflow-x: scroll;
-webkit-overflow-scrolling: touch;
}
.nav::-webkit-scrollbar {
display: none; /* Скроем скролбар в Сафари */
}
}
Добавим немного косметики в виде «забеления» по краям с помощью маски и градиента:
@media (width < 992px) {
.header {
grid-template-columns: min-content 1fr;
grid-template-rows: min-content min-content;
row-gap: 15px;
}
.header .tel {
grid-column: 2;
grid-row: 1;
}
.header .navHolder {
grid-column: 1 / span 2;
grid-row: 2;
margin-left: -10px;
margin-right: -10px;
}
.nav {
padding: 10px;
margin: -10px 0;
scrollbar-width: none;
overflow-x: scroll;
-webkit-overflow-scrolling: touch;
mask-image: linear-gradient(
90deg,
transparent 0,
rgba(0, 0, 0, .25) 8px,
#000 16px,
#000 calc(100% - 16px),
rgba(0, 0, 0, .25) calc(100% - 8px),
transparent
);
}
.nav::-webkit-scrollbar {
display: none;
}
}
Окружим меню линейками, чтобы показать, что оно прокручивается независимо от других элементов страницы:
@media (width < 992px) {
.navHolder {
position: relative; /* Мы используем обертку для линеек, потому что линейки у .nav попадали бы под маску и забеление */
}
.navHolder::before,
.navHolder::after {
content: ''; /* Используем псевдоэлементы, а не рамку, потому что их позицией можно управлять */
position: absolute;
left: 0;
right: 0;
height: 1px;
background: rgba(0, 0, 0, .07);
}
.navHolder::before {
top: -9px;
}
.navHolder::after {
bottom: -8px;
}
}
Осталась одна проблема. При некоторых ширинах экрана меню обрезается так, что кажется, что всё поместилось и прокручивать нечего. Чтобы поправить это, можно написать небольшой скрипт, который будет анализировать ширину элементов и менять значение column-gap в меню так, чтобы последний видимый элемент всегда обрезался.
Если меню статичное и вряд ли будет меняться, проще задать подходящие значения column-gap для разной ширины экрана.
Адаптируем получившуюся шапку для мобильных устройств. Будем считать, что в нашем дизайне мобильные устройства — это устройства с экраном шириной менее 992 пикселей.
Обернём <nav> в див с классом navHolder — он пригодится нам для прокрутки и обрезания меню:
<header class="header">
<div class="logo">…</div>
<div class="navHolder">
<nav class="nav">
<!-- … -->
</nav>
</div>
<div class="tel">…</div>
</header>Поменяем раскладку шапки. Вместо трёх колонок сделаем сетку 2×2:
@media (width < 992px) {
.header {
grid-template-columns: min-content 1fr;
grid-template-rows: min-content min-content;
row-gap: 15px;
}
}Переставим телефон в правый верхний угол, меню поставим вниз и растянем на две колонки:
@media (width < 992px) {
.header {
grid-template-columns: min-content 1fr;
grid-template-rows: min-content min-content;
row-gap: 15px;
}
.header .tel {
grid-column: 2;
grid-row: 1;
}
.header .navHolder {
grid-column: 1 / span 2;
grid-row: 2;
}
}
Растянем меню на всю ширину экрана:
@media (width < 992px) {
.header {
grid-template-columns: min-content 1fr;
grid-template-rows: min-content min-content;
row-gap: 15px;
}
.header .tel {
grid-column: 2;
grid-row: 1;
}
.header .navHolder {
grid-column: 1 / span 2;
grid-row: 2;
margin-left: -10px;
margin-right: -10px;
}
}
Включим горизонтальную прокрутку в меню:
@media (width < 992px) {
.header {
grid-template-columns: min-content 1fr;
grid-template-rows: min-content min-content;
row-gap: 15px;
}
.header .tel {
grid-column: 2;
grid-row: 1;
}
.header .navHolder {
grid-column: 1 / span 2;
grid-row: 2;
margin-left: -10px;
margin-right: -10px;
}
.nav {
overflow-x: scroll; /* Зададим возможность горизонтальной прокрутки */
-webkit-overflow-scrolling: touch; /* Для Сафари дополнительно зададим инерцию при прокрутке. С инерцией меню будет «оттягиваться» при попытке прокрутить меню за его пределы. */
}
}
Из‑за overflow обрезались поля у текущего пункта в меню. Плюс при прокрутке появляется горизональный скролбар. Устраним эти проблемы:
@media (width < 992px) {
.header {
grid-template-columns: min-content 1fr;
grid-template-rows: min-content min-content;
row-gap: 15px;
}
.header .tel {
grid-column: 2;
grid-row: 1;
}
.header .navHolder {
grid-column: 1 / span 2;
grid-row: 2;
margin-left: -10px;
margin-right: -10px;
}
.nav {
padding: 10px; /* Добавим дополнительные поля */
margin: -10px 0; /* С помощью отрицательных отступов сверху-снизу компенсируем «нарощенные» поля */
scrollbar-width: none; /* Скроем скролбар в Хроме и Файрфоксе */
overflow-x: scroll;
-webkit-overflow-scrolling: touch;
}
.nav::-webkit-scrollbar {
display: none; /* Скроем скролбар в Сафари */
}
}
Добавим немного косметики в виде «забеления» по краям с помощью маски и градиента:
@media (width < 992px) {
.header {
grid-template-columns: min-content 1fr;
grid-template-rows: min-content min-content;
row-gap: 15px;
}
.header .tel {
grid-column: 2;
grid-row: 1;
}
.header .navHolder {
grid-column: 1 / span 2;
grid-row: 2;
margin-left: -10px;
margin-right: -10px;
}
.nav {
padding: 10px;
margin: -10px 0;
scrollbar-width: none;
overflow-x: scroll;
-webkit-overflow-scrolling: touch;
mask-image: linear-gradient(
90deg,
transparent 0,
rgba(0, 0, 0, .25) 8px,
#000 16px,
#000 calc(100% - 16px),
rgba(0, 0, 0, .25) calc(100% - 8px),
transparent
);
}
.nav::-webkit-scrollbar {
display: none;
}
}
Окружим меню линейками, чтобы показать, что оно прокручивается независимо от других элементов страницы:
@media (width < 992px) {
.navHolder {
position: relative; /* Мы используем обертку для линеек, потому что линейки у .nav попадали бы под маску и забеление */
}
.navHolder::before,
.navHolder::after {
content: ''; /* Используем псевдоэлементы, а не рамку, потому что их позицией можно управлять */
position: absolute;
left: 0;
right: 0;
height: 1px;
background: rgba(0, 0, 0, .07);
}
.navHolder::before {
top: -9px;
}
.navHolder::after {
bottom: -8px;
}
}
Осталась одна проблема. При некоторых ширинах экрана меню обрезается так, что кажется, что всё поместилось и прокручивать нечего. Чтобы поправить это, можно написать небольшой скрипт, который будет анализировать ширину элементов и менять значение column-gap в меню так, чтобы последний видимый элемент всегда обрезался.
Если меню статичное и вряд ли будет меняться, проще задать подходящие значения column-gap для разной ширины экрана.
Адаптируем шапку к мобильной версии с помощью гамбургерного меню. Будет скрывать меню и показывать «гамбургер» при той ширине экрана, при которой меню не помещается в него целиком.
Возьмём шапку без мобильной версии и добавим кнопку гамбургер:
<header class="header">
<div class="logo">
<a href="/">
<img src="logo.svg" width="40" height="40">
</a>
</div>
<nav class="nav">
<ul>
<li class="nav__item">
<a href="/products/">Продукция</a>
</li>
<li class="nav__item">
<a href="/services/">Услуги</a>
</li>
<li class="nav__item is__active">
<a href="/blog/">Блог</a>
</li>
<li class="nav__item">
<a href="/contacts/">Контакты</a>
</li>
<li class="nav__item">
<a href="tel:+74954005050">+7 495 400‑50‑50</a>
</li>
</ul>
</nav>
<div class="tel">
<a href="tel:+74954005050">
+7 495 400‑50‑50
</a>
</div>
<!-- aria-label подскажет смысл кнопки программам чтения с экрана -->
<button class="hamburger" type="button" aria-label="Открыть меню">
<span class="hamburger-box">
<span class="hamburger-inner"></span>
</span>
</button>
</header>
Вёрстка простой шапки «в линию»
Поменяем раскладку на мобильных устройствах. Переставим телефон и кнопку к логотипу, а меню вниз. Заодно скроем кнопку на достаточно широких экранах:
@media (width > 690px) {
.header .hamburger {
display: none;
}
}
@media (width <= 690px) {
.header {
position: relative; /* Задаём контекст для позиционирования меню */
grid-template-columns: min-content 1fr 1fr min-content; /* Определяем четыре колонки: первую и последнюю — по ширине логотипа и кнопки, вторую и третью — 50% от оставшегося места */
}
.header .hamburger {
grid-column: 4; /* Кнопку ставим в последнюю колонку */
}
.header .tel {
grid-column: 2 / span 2; /* Телефону отдаём две центральные колонки */
justify-self: stretch;
}
.nav {
position: absolute; /* Меню позиционируем абсолютно, сразу после шапки поверх содержимого. Из-за этого кнопка перестанет нажиматься, исправим это на следующем шаге */
top: 0;
left: 0;
width: 100%; /* Растягиваем меню на всю ширину экрана */
padding: 65px 10px 20px; /* Добавляем настолько большой верхний падинг, чтобы меню встало точно под шапкой */
}
.nav ul {
grid-auto-flow: row; /* Раскладываем меню рядами */
grid-auto-rows: max-content;
row-gap: 10px;
}
}
Стилизуем кнопку. Для простоты возьмём готовые стили и анимацию из библиотеки «гамбургерных» иконок на ЦСС:
<link rel="stylesheet" src="styles/hamburgers.css">
<header class="header">
<div class="logo">
<a href="/">
<img src="logo.svg" width="40" height="40">
</a>
</div>
<nav class="nav">
<ul>
<li class="nav__item">
<a href="/products/">Продукция</a>
</li>
<li class="nav__item">
<a href="/services/">Услуги</a>
</li>
<li class="nav__item is__active">
<a href="/blog/">Блог</a>
</li>
<li class="nav__item">
<a href="/contacts/">Контакты</a>
</li>
<li class="nav__item">
<a href="tel:+74954005050">+7 495 400‑50‑50</a>
</li>
</ul>
</nav>
<div class="tel">
<a href="tel:+74954005050">
+7 495 400‑50‑50
</a>
</div>
<button class="hamburger hamburger--squeeze" type="button" aria-label="Открыть меню">
<span class="hamburger-box">
<span class="hamburger-inner"></span>
</span>
</button>
</header> Где хранить стили
.header .hamburger {
grid-column: 4;
grid-row: 1;
color: inherit; /* Чтобы иконка унаследовала цвет текста, а не была белой */
line-height: 0; /* Чтобы иконка не увеличивала высоту меню */
padding: 0;
}
.header .hamburger-inner,
.header .hamburger-inner::before,
.header .hamburger-inner::after {
border-radius: 0; /* Убираем скругления в гамбургере */
}«Гамбургер» и его анимацию можно было бы сверстать вручную или заменить на иконки
Научим кнопку «кликаться». Пускай пока она меняет своё состояние и добавляет или убирает класс у меню:
<script>
document
.querySelectorAll('.hamburger') // Находим кнопку-гамбургер
.forEach(button => {
const nav = button
.closest('.header')
.querySelector('.nav') // Находим ближайшее к кнопке меню
button.addEventListener('click', () => { // По клику на кнопку добавляем или удаляем класс is-active у кнопки и меню
button
.classList
.toggle('is-active')
nav
.classList
.toggle('is-active')
})
})
</script>Внутри <script> пишут текст на Яваскрипте — языке программирования, встроенном в браузеры. Яваскрипт используют для создания интерактивных страниц и веб‑приложений
Подключим меню к кнопке и добавим анимацию. Пусть меню приезжает сверху:
@media (width > 690px) {
.header .hamburger {
display: none;
}
}
@media (width <= 690px) {
.header {
position: relative;
grid-template-columns: min-content 1fr 1fr min-content;
}
.header .hamburger {
grid-column: 4;
grid-row: 1;
}
.header .tel {
grid-column: 2 / span 2;
grid-row: 1;
justify-self: stretch;
}
.nav {
position: absolute;
top: 0;
left: 0;
width: 100%;
padding: 65px 10px 20px;
transform: translateY(-100%); /* Прячем меню за верхнюю границу экрана */
transition: transform 75ms ease; /* Анимируем изменение позиции меню, синхронизировав с анимацией кнопки */
transition-delay: 120ms;
}
.nav.is-active {
transform: translateY(0); /* Когда меню активно, ставим его под шапку */
transition-timing-function: cubic-bezier(.215, .61, .355, 1);
}
.nav ul {
grid-auto-flow: row;
grid-auto-rows: max-content;
row-gap: 10px;
}
}Тайминги и скорость анимации скопировали из кнопки, чтобы не было рассинхрона
Появилась проблема с наползанием. Выезжающее меню перекрывает логотип. Поправим это, добавив слой, разделяющий выезжающее меню и логотип с кнопкой:
@media (width > 690px) {
.header .hamburger {
display: none;
}
}
@media (width <= 690px) {
.header {
position: relative;
grid-template-columns: min-content 1fr 1fr min-content;
}
.header .hamburger {
grid-column: 4;
grid-row: 1;
}
.header .tel {
grid-column: 2 / span 2;
grid-row: 1;
justify-self: stretch;
}
.nav {
position: absolute;
top: 0;
left: 0;
width: 100%;
padding: 65px 10px 20px;
transform: translateY(calc(60px - 100%)); /* Спрячем меню под слой-разделитель */
z-index: 1; /* Меню поедет на самом нижнем уровне */
transition: transform 75ms ease;
transition-delay: 120ms;
}
.nav.is-active {
transform: translateY(0);
transition-timing-function: cubic-bezier(.215, .61, .355, 1);
}
.nav ul {
grid-auto-flow: row;
grid-auto-rows: max-content;
row-gap: 10px;
}
.header::before {
content: '';
background: inherit;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 2; /* Слой-разделитель скроет меню, пока оно проезжает под шапкой */
}
.header .logo,
.header .tel,
.header .hamburger {
position: relative;
z-index: 3; /* Логотип, телефон и кнопка встанут на самом верху */
}
}Адаптируем шапку к мобильной версии с помощью гамбургерного меню. Будет скрывать меню и показывать «гамбургер» при той ширине экрана, при которой меню не помещается в него целиком.
Возьмём шапку без мобильной версии и добавим кнопку гамбургер:
<header class="header">
<div class="logo">
<a href="/">
<img src="logo.svg" width="40" height="40">
</a>
</div>
<nav class="nav">
<ul>
<li class="nav__item">
<a href="/products/">Продукция</a>
</li>
<li class="nav__item">
<a href="/services/">Услуги</a>
</li>
<li class="nav__item is__active">
<a href="/blog/">Блог</a>
</li>
<li class="nav__item">
<a href="/contacts/">Контакты</a>
</li>
<li class="nav__item">
<a href="tel:+74954005050">+7 495 400‑50‑50</a>
</li>
</ul>
</nav>
<div class="tel">
<a href="tel:+74954005050">
+7 495 400‑50‑50
</a>
</div>
<!-- aria-label подскажет смысл кнопки программам чтения с экрана -->
<button class="hamburger" type="button" aria-label="Открыть меню">
<span class="hamburger-box">
<span class="hamburger-inner"></span>
</span>
</button>
</header>
Вёрстка простой шапки «в линию»
Поменяем раскладку на мобильных устройствах. Переставим телефон и кнопку к логотипу, а меню вниз. Заодно скроем кнопку на достаточно широких экранах:
@media (width > 690px) {
.header .hamburger {
display: none;
}
}
@media (width <= 690px) {
.header {
position: relative; /* Задаём контекст для позиционирования меню */
grid-template-columns: min-content 1fr 1fr min-content; /* Определяем четыре колонки: первую и последнюю — по ширине логотипа и кнопки, вторую и третью — 50% от оставшегося места */
}
.header .hamburger {
grid-column: 4; /* Кнопку ставим в последнюю колонку */
}
.header .tel {
grid-column: 2 / span 2; /* Телефону отдаём две центральные колонки */
justify-self: stretch;
}
.nav {
position: absolute; /* Меню позиционируем абсолютно, сразу после шапки поверх содержимого. Из-за этого кнопка перестанет нажиматься, исправим это на следующем шаге */
top: 0;
left: 0;
width: 100%; /* Растягиваем меню на всю ширину экрана */
padding: 65px 10px 20px; /* Добавляем настолько большой верхний падинг, чтобы меню встало точно под шапкой */
}
.nav ul {
grid-auto-flow: row; /* Раскладываем меню рядами */
grid-auto-rows: max-content;
row-gap: 10px;
}
}
Стилизуем кнопку. Для простоты возьмём готовые стили и анимацию из библиотеки «гамбургерных» иконок на ЦСС:
<link rel="stylesheet" src="styles/hamburgers.css">
<header class="header">
<div class="logo">
<a href="/">
<img src="logo.svg" width="40" height="40">
</a>
</div>
<nav class="nav">
<ul>
<li class="nav__item">
<a href="/products/">Продукция</a>
</li>
<li class="nav__item">
<a href="/services/">Услуги</a>
</li>
<li class="nav__item is__active">
<a href="/blog/">Блог</a>
</li>
<li class="nav__item">
<a href="/contacts/">Контакты</a>
</li>
<li class="nav__item">
<a href="tel:+74954005050">+7 495 400‑50‑50</a>
</li>
</ul>
</nav>
<div class="tel">
<a href="tel:+74954005050">
+7 495 400‑50‑50
</a>
</div>
<button class="hamburger hamburger--squeeze" type="button" aria-label="Открыть меню">
<span class="hamburger-box">
<span class="hamburger-inner"></span>
</span>
</button>
</header> Где хранить стили
.header .hamburger {
grid-column: 4;
grid-row: 1;
color: inherit; /* Чтобы иконка унаследовала цвет текста, а не была белой */
line-height: 0; /* Чтобы иконка не увеличивала высоту меню */
padding: 0;
}
.header .hamburger-inner,
.header .hamburger-inner::before,
.header .hamburger-inner::after {
border-radius: 0; /* Убираем скругления в гамбургере */
}«Гамбургер» и его анимацию можно было бы сверстать вручную или заменить на иконки
Научим кнопку «кликаться». Пускай пока она меняет своё состояние и добавляет или убирает класс у меню:
<script>
document
.querySelectorAll('.hamburger') // Находим кнопку-гамбургер
.forEach(button => {
const nav = button
.closest('.header')
.querySelector('.nav') // Находим ближайшее к кнопке меню
button.addEventListener('click', () => { // По клику на кнопку добавляем или удаляем класс is-active у кнопки и меню
button
.classList
.toggle('is-active')
nav
.classList
.toggle('is-active')
})
})
</script>Внутри <script> пишут текст на Яваскрипте — языке программирования, встроенном в браузеры. Яваскрипт используют для создания интерактивных страниц и веб‑приложений
Подключим меню к кнопке и добавим анимацию. Пусть меню приезжает сверху:
@media (width > 690px) {
.header .hamburger {
display: none;
}
}
@media (width <= 690px) {
.header {
position: relative;
grid-template-columns: min-content 1fr 1fr min-content;
}
.header .hamburger {
grid-column: 4;
grid-row: 1;
}
.header .tel {
grid-column: 2 / span 2;
grid-row: 1;
justify-self: stretch;
}
.nav {
position: absolute;
top: 0;
left: 0;
width: 100%;
padding: 65px 10px 20px;
transform: translateY(-100%); /* Прячем меню за верхнюю границу экрана */
transition: transform 75ms ease; /* Анимируем изменение позиции меню, синхронизировав с анимацией кнопки */
transition-delay: 120ms;
}
.nav.is-active {
transform: translateY(0); /* Когда меню активно, ставим его под шапку */
transition-timing-function: cubic-bezier(.215, .61, .355, 1);
}
.nav ul {
grid-auto-flow: row;
grid-auto-rows: max-content;
row-gap: 10px;
}
}Тайминги и скорость анимации скопировали из кнопки, чтобы не было рассинхрона
Появилась проблема с наползанием. Выезжающее меню перекрывает логотип. Поправим это, добавив слой, разделяющий выезжающее меню и логотип с кнопкой:
@media (width > 690px) {
.header .hamburger {
display: none;
}
}
@media (width <= 690px) {
.header {
position: relative;
grid-template-columns: min-content 1fr 1fr min-content;
}
.header .hamburger {
grid-column: 4;
grid-row: 1;
}
.header .tel {
grid-column: 2 / span 2;
grid-row: 1;
justify-self: stretch;
}
.nav {
position: absolute;
top: 0;
left: 0;
width: 100%;
padding: 65px 10px 20px;
transform: translateY(calc(60px - 100%)); /* Спрячем меню под слой-разделитель */
z-index: 1; /* Меню поедет на самом нижнем уровне */
transition: transform 75ms ease;
transition-delay: 120ms;
}
.nav.is-active {
transform: translateY(0);
transition-timing-function: cubic-bezier(.215, .61, .355, 1);
}
.nav ul {
grid-auto-flow: row;
grid-auto-rows: max-content;
row-gap: 10px;
}
.header::before {
content: '';
background: inherit;
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
z-index: 2; /* Слой-разделитель скроет меню, пока оно проезжает под шапкой */
}
.header .logo,
.header .tel,
.header .hamburger {
position: relative;
z-index: 3; /* Логотип, телефон и кнопка встанут на самом верху */
}
}Визитка — это страница с текстом о себе. Часто визитку совмещают с резюме, добавляя опыт работы, навыки, доклады и выступления на конференциях. Большинство визиток состоит из текста, списка ссылок и фотографии автора, задающей настроение. Они не очень объёмные, но с типографическими изысками.
Визитка — это страница с текстом о себе. Часто визитку совмещают с резюме, добавляя опыт работы, навыки, доклады и выступления на конференциях. Большинство визиток состоит из текста, списка ссылок и фотографии автора, задающей настроение. Они не очень объёмные, но с типографическими изысками.