🔍 Начните печатать, чтобы искать по книге или перейти к нужной странице по номеру
Удобно листать не только прокруткой, но и клавишами‑стрелками:
Эта книга — пошаговая инструкция по вёрстке сайтов на языках ХТМЛ и ЦСС. Вы узнаете не только как сверстать сайт, но и как опубликовать его в интернете, настроить красивый шаринг в соцсети и подключить системы аналитики.
Отдельный раздел книги посвящён работе с верстальщиком: как ставить задачи, получать предсказуемый результат и правильно принимать и внедрять вёрстку.
Знакомство с ХТМЛ и ЦСС
Модули
Расстановка
Страницы
Спецэффекты
Контроль качества
Публикация
Как работать с верстальщиком
Эта книга — пошаговая инструкция по вёрстке сайтов на языках ХТМЛ и ЦСС. Вы узнаете не только как сверстать сайт, но и как опубликовать его в интернете, настроить красивый шаринг в соцсети и подключить системы аналитики.
Отдельный раздел книги посвящён работе с верстальщиком: как ставить задачи, получать предсказуемый результат и правильно принимать и внедрять вёрстку.
Знакомство с ХТМЛ и ЦСС
Модули
Расстановка
Страницы
Спецэффекты
Контроль качества
Публикация
Как работать с верстальщиком
Ваш дизайн может работать не на всех размерах экрана. На мобильном телефоне может не поместиться меню или некрасиво переноситься текст. Чтобы адаптировать страницу к мобильному телефону и другим устройствам, введём понятие «точек излома».
Точка излома, breakpoint, — ширина экрана, при которой одна версия вёрстки сменяется на другую. Например, при ширине экрана менее 991 пикселя меню могло бы занять всю ширину экрана и стать прокручиваемым. Используем для этого медиавыражения.
Чтобы решить, при каких условиях медиавыражения должны срабатывать, используют два подхода: по устройствам и по содержимому. В первом случае мы решаем, что у страницы есть отдельный дизайн для разных устройств, экранов: например, мобильных, лаптопов и десктопов. Затем для каждого из устройств определяем точки излома.
В большинстве популярных ЦСС‑фреймворков эти точки выглядят так:
| Узкие мобильные | < 576 пк |
| Мобильные | < 768 пк |
| Лаптопы | ≥ 768 пк |
| Десктопы | ≥ 992 пк |
| Широкие десктопы | ≥ 1400 пк |
Вы можете использовать любые другие значения, опираясь на статистику экранов посетителей сайта или на значения, заданные в вашем ЦСС‑фреймворке.
Например, на сайте бюро мы остановились на трёх типах устройств:
| Мобильные | ≤ 960 пк |
| Лаптопы | > 960 пк |
| Десктопы | > 1400 пк |
Советуем использовать как можно меньше устройств и точек излома. Чем их больше, тем больше вариантов раскладки нужно придумать, сверстать и проверить. Если шапка и подвал адаптируются к двум устройствам, это даёт
Медиавыражения
Ваш дизайн может работать не на всех размерах экрана. На мобильном телефоне может не поместиться меню или некрасиво переноситься текст. Чтобы адаптировать страницу к мобильному телефону и другим устройствам, введём понятие «точек излома».
Точка излома, breakpoint, — ширина экрана, при которой одна версия вёрстки сменяется на другую. Например, при ширине экрана менее 991 пикселя меню могло бы занять всю ширину экрана и стать прокручиваемым. Используем для этого медиавыражения.
Чтобы решить, при каких условиях медиавыражения должны срабатывать, используют два подхода: по устройствам и по содержимому. В первом случае мы решаем, что у страницы есть отдельный дизайн для разных устройств, экранов: например, мобильных, лаптопов и десктопов. Затем для каждого из устройств определяем точки излома.
В большинстве популярных ЦСС‑фреймворков эти точки выглядят так:
| Узкие мобильные | < 576 пк |
| Мобильные | < 768 пк |
| Лаптопы | ≥ 768 пк |
| Десктопы | ≥ 992 пк |
| Широкие десктопы | ≥ 1400 пк |
Вы можете использовать любые другие значения, опираясь на статистику экранов посетителей сайта или на значения, заданные в вашем ЦСС‑фреймворке.
Например, на сайте бюро мы остановились на трёх типах устройств:
| Мобильные | ≤ 960 пк |
| Лаптопы | > 960 пк |
| Десктопы | > 1400 пк |
Советуем использовать как можно меньше устройств и точек излома. Чем их больше, тем больше вариантов раскладки нужно придумать, сверстать и проверить. Если шапка и подвал адаптируются к двум устройствам, это даёт
Медиавыражения
Адаптация может понадобиться не только на уровне всего сайта, но и на уровне отдельных элементов. Например, адрес компании в подвале может постепенно сокращать свой текст, когда перестаёт помещаться:
Большая Новодмитровская улица, 36, строение 2
Большая Новодмитровская улица, 36, стр. 2
Б. Новодмитровская ул., 36, стр. 2
В таких случаях точки излома лучше выбирать исходя из содержимого и использовать Container Queries — медиавыражения, отталкивающиеся от размеров контейнера, а не от размеров окна:
<footer>
<div class="address">
Б<span class="sacrifice--second">ольшая</span><span>.</span> Новодмитровская ул<span class="sacrifice--second">ица</span><span>.</span>,
36, стр<span class="sacrifice--first">оение</span><span>.</span> 2
</div>
</footer>Для логичных переносов склеили неразрывными пробелами части адреса, сильно связанные друг с другом по смыслу: «Большая Новодмитровская улица», «36, строение 2»
.address {
container-type: inline-size; /* Объявляем .address контейнером, с шириной которого можно делать медиавыражения */
}
.address .sacrifice--first + span,
.address .sacrifice--first + span {
display: none; /* Скрываем спаны с точками, пока они не нужны */
}
@container (width <= 21em) { /* При ширине подвала менее 21em, используем первую волну сокращений */
.address .sacrifice--first {
display: none;
}
.address .sacrifice--first + span {
display: inline;
}
}
@container (width <= 19em) { /* При ширине подвала менее 19em, используем вторую волну сокращений */
.address .sacrifice--second {
display: none;
}
.address .sacrifice--second + span {
display: inline;
}
}
Конечно, можно было бы использовать и обычные медиавыражения с шириной экрана. Но это сделало бы вёрстку адреса более хрупкой и менее переносимой. Если в подвале у адреса появится сосед, адрес станет занимать меньше места. Текст придётся сокращать раньше, значит, придётся пересчитывать точки в медиавыражениях.
Container Queries. МДН
Адаптация может понадобиться не только на уровне всего сайта, но и на уровне отдельных элементов. Например, адрес компании в подвале может постепенно сокращать свой текст, когда перестаёт помещаться:
Большая Новодмитровская улица, 36, строение 2
Большая Новодмитровская улица, 36, стр. 2
Б. Новодмитровская ул., 36, стр. 2
В таких случаях точки излома лучше выбирать исходя из содержимого и использовать Container Queries — медиавыражения, отталкивающиеся от размеров контейнера, а не от размеров окна:
<footer>
<div class="address">
Б<span class="sacrifice--second">ольшая</span><span>.</span> Новодмитровская ул<span class="sacrifice--second">ица</span><span>.</span>,
36, стр<span class="sacrifice--first">оение</span><span>.</span> 2
</div>
</footer>Для логичных переносов склеили неразрывными пробелами части адреса, сильно связанные друг с другом по смыслу: «Большая Новодмитровская улица», «36, строение 2»
.address {
container-type: inline-size; /* Объявляем .address контейнером, с шириной которого можно делать медиавыражения */
}
.address .sacrifice--first + span,
.address .sacrifice--first + span {
display: none; /* Скрываем спаны с точками, пока они не нужны */
}
@container (width <= 21em) { /* При ширине подвала менее 21em, используем первую волну сокращений */
.address .sacrifice--first {
display: none;
}
.address .sacrifice--first + span {
display: inline;
}
}
@container (width <= 19em) { /* При ширине подвала менее 19em, используем вторую волну сокращений */
.address .sacrifice--second {
display: none;
}
.address .sacrifice--second + span {
display: inline;
}
}
Конечно, можно было бы использовать и обычные медиавыражения с шириной экрана. Но это сделало бы вёрстку адреса более хрупкой и менее переносимой. Если в подвале у адреса появится сосед, адрес станет занимать меньше места. Текст придётся сокращать раньше, значит, придётся пересчитывать точки в медиавыражениях.
Container Queries. МДН
Приведём примеры вёрстки типичных шапок, меню и подвалов сайтов. Сосредоточимся только на раскладке элементов, оставив типографику на усмотрение читателю.
Одни и те же элементы можно сверстать, используя разные способы: гриды, флексбоксы или строчные элементы. Поэтому вёрстка в этой и последующих главах — это не эталоны, высеченные в камне, а лишь примеры решения типовых задач в вёрстке.
Приведём примеры вёрстки типичных шапок, меню и подвалов сайтов. Сосредоточимся только на раскладке элементов, оставив типографику на усмотрение читателю.
Одни и те же элементы можно сверстать, используя разные способы: гриды, флексбоксы или строчные элементы. Поэтому вёрстка в этой и последующих главах — это не эталоны, высеченные в камне, а лишь примеры решения типовых задач в вёрстке.
Типичная шапка из трёх элементов: логотипа, меню и телефона
В контейнере <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 для разной ширины экрана.