Подписывайтесь на мой твиттер, там всегда что-нибудь интересное!

Viewport единицы в CSS

В этой статье вы узнаете про единицы вьюпорта в CSS и варианты их применения.

Перевод CSS Viewport Units

Viewport единицы уже существуют несколько лет и со временем я вижу рост их применения другими разработчиками. Их полезность заключается в том, что они дают нам возможность выставлять размеры гибким и динамическим образом, без прямого использования JavaScript. Также, для них легко сделать фоллбэк в случае отсутствия поддержки браузером.

Интро

В соответствии со спецификацией CSS, процентные вьюпорт единицы относительны размеру изначального блок контейнера, который сам по себе является корневым элементом страницы.

Viewport единицы это vwvhvmin и vmax.

Viewport ширина

Единица vw представляет собой процентное измерение ширины корневого элемента. Один vw равен 1% ширины вьюпорта.

Тут у нас есть элемент с таким CSS:

.element {
    width: 50vw;
}

Когда ширина вьюпорта равна 500px, то 50vw будут высчитаны таким образом:

width = 500*50% = 250px

Viewport высота

Единица vh представляет собой процентное измерение высоты корневого элемента. Один vh равен 1% высоты вьюпорта.

У нас есть элемент с таким CSS:

.element {
    height: 50vh;
}

Если высота вьюпорта равна 290px, то 70vh будет высчитано следующим образом:

height = 290*70% = 202px

Вот и всё! Теперь давайте взглянем на другие единицы.

Vmin

Эта единица является минимумом ширины или высоты вьюпорта. Если ширина вьюпорта больше его высоты, то значение единицы будет высчитано, основываясь на высоте.

Давайте посмотрим на пример ниже.

У нас есть мобильный телефон в лэндскейп режиме и у элемента на странице есть такая единица как vmin. В этом случае, значение будет высчитано по высоте вьюпорта, так как оно меньше ширины.

.element {
    padding-top: 10vmin;
    padding-bottom: 10vmin;
}

Вот как она будет считаться:

Как вы уже могли предположить, результат будет считаться таким образом:

padding-top = (10% of height) = 10% * 164 = 16.4px 
padding-bottom = (10% of height) = 10% * 164 = 16.4px

Vmax

Это прямая противоположность vmin. Значение считается основываясь на максимальной ширине и высоте.

Давайте посмотрим на пример:

.element {
    padding-top: 10vmax;
    padding-bottom: 10vmax;
}

Результат бы был высчитан таким образом:

padding-top = (10% of width) = 10% * 350 = 35px
padding-bottom = (10% of width) = 10% * 350 = px

Как viewport единицы отличаются от процентов?

Они берут за основу корневой элемент страницы, в то время как проценты основаны на размере своего контейнера. Поэтому они и отличаются друг от друга и каждый имеет свойственные себе примеры использования.

Примеры использования viewport единиц

В следующих секциях мы посмотрим на некоторые примеры использования единиц viewport и на то, как их применять в ваших рабочих проектах.

Размер шрифта

Единицы вьюпорта идеальны для адаптивной типографики. Для примера, мы можем использовать следующий код для заголовка статьи:

.title {
    font-size: 5vw;
}

Размер заголовка будет увеличиваться или уменьшаться в зависимости от ширины вьюпорта. Это как будто бы мы выдали размеру шрифта 5% ширины страницы. Однако, как бы не хотелось, а надо протестировать и смотрим, что получается.

Обратите внимание, что шрифт стал очень мелким при мобильных размерах, это очень плохо в плане доступности и UX. Насколько я знаю, минимальный размер шрифта на мобильных устройствах не должен быть ниже 14px. А там у нас выходит уже ниже 10px.

Чтобы решить эту проблему, нам надо дать заголовку минимальный размер шрифта, который не может быть меньше положенной нормы. И тут CSS calc() спешит на помощь!

.title {
    font-size: calc(14px + 2vw);
}

У функции calc() будет основное значение и оно добавит к нему 2vw. Учитывая это, размер шрифта точно не будет слишком маленьким.

Ещё стоит рассмотреть то, как себя будет вести размер шрифта на больших экранах, к примеру на 27” аймаках. Что будет? Ну вы уже наверное предположили. Размер шрифта бахнет аж в 95px, что само по себе уже кошмар. Чтобы предохраниться от этой ситуации мы можем использовать медиа запросы на определённых брейкпоинтах и менять размеры шрифтов.

@media (min-width: 1800px) {
    .title {
        font-size: 40px;
    }
}

Сбрасывая font-size мы можем быть уверены в том, что размер шрифта не будет слишком большим. Тут возможно вам понадобится несколько медиа запросов, но это сугубо ваше личное дело когда и как их использовать в контексте проекта.

Полноэкранные секции

Иногда нам надо, чтобы секция забирала 100% высоты виюпорта. Это так называемые полноэкранные секции. Для их создания мы можем использовать вьюпорт единицу высоты.

.section {
    height: 100vh;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
}

Добавив height: 100vh, мы можем точно убедиться в том, высота секции будет в 100% высоту вьюпорта. Также, тут я добавил немного флексбокса, чтобы отцентровать контент вертикально и горизонтально.

Прилипающий футер

На больших экранах вы могли уже обратить внимание на то, что футер не прилипает к концу страницы. Ну и это нормально. Это даже не рассматривается как плохая практика. Однако, тут у нас есть пространство для улучшений. Давайте рассмотрим следующий кейс, в котором и происходит эта ошибка.

Чтобы её решить, нам нужно отдать основному контенту высоту, равную разнице между высотой вьюпорта и суммой хедера и футера. Динамически это довольно хитрая операция, но с помощью единиц вьюпорта всё становится довольно быстро и понятно.

Первое решение: calc и единицы вьюпорта

Если высота хедера и футера фиксированны, то их можно совместить с помощью функции calc():

header,
footer {
    height: 70px;
}

main {
    height: calc(100vh - 70px - 70px);
}

Это решение не гарантирует того, что оно будет всегда работать, особенно для футера. За всю свою карьеру я никогда не использовал футер с фиксированной высотой, потому что это просто непрактично, особенно на разных размерах экранов.

Второе решение: Flexbox и вьюпорт единицы (рекомендуемое)

Добавляя 100vh как высоту для body элемента, мы можем использовать флексы для того, чтобы основной контент занимал всё оставшееся место.

body {
    min-height: 100vh;
    display: flex
    flex-direction: column;
{

main {
    flex-grow: 1;
}

С учетом этого, наша проблема решена и у нас есть прилипающий футер вне зависимости от длины контента.

Адаптивные элементы

Занимаясь подготовкой материала я наткнулся на эту статью и она мне реально понравилась. Так что я возьму пример использования оттуда и объясню его своим способом.

Предположим, что у нас есть портфолио для того, чтобы показать свои адаптивные работы и у нас имеется три типа устройства (мобильные, планшеты и ноутбук). В каждом устройстве есть по изображению. Суть в том, чтобы сделать этот контент 100% отзывчивым на CSS.

Используя гриды и единицы вьюпорта, мы можем сделать это адаптивным и динамическим.

<div class="wrapper">
  <div class="device laptop"></div>
  <div class="device mobile"></div>
  <div class="device tablet"></div>
</div>

Обратите внимание, что вьюпорт единицы используются в grid-* свойствах. Они также используются для borderborder-radius и других свойств. Всё это приведет к флюидному дизайну.

.wrapper {
  display: grid;
  grid-template-columns: repeat(20, 5vw); 
  grid-auto-rows: 6vw;
}

.mobile { 
  position: relative;
  z-index: 1;
  grid-column: 2 / span 3;
  grid-row: 3 / span 5;
}
 
.tablet {
  position: relative;
  z-index: 1;
  grid-column: 13 / span 7; 
  grid-row: 4 / span 4;
  border-bottom: 1vw solid #a9B9dd;
  border-right: solid 3vw #a9B9dd;
}
 
.laptop {
  position: relative;
  grid-column: 3/span 13;
  grid-row: 2 / span 8;
}

.laptop:after {
    content:"";
    position:absolute;
    bottom: -3vw;
    left: -5.5vw;
    right: -5.5vw;
    height: 3vw;
    background-color: #a9B9dd;
    border-radius: 0 0 1vw 1vw;
}

Видео пример

Выходим за пределы контейнера

Я обратил внимание на случай, который больше всего подходит для редакторских шаблонов. А именно когда дочерний элемент забирает 100% ширины вьюпорта, хотя его родитель ограничен в ней. Давайте рассмотрим пример ниже:

Чтобы достигнуть такого же эффекта, мы можем использовать вьюпорт единицы и свойства позиционирования. Вот наш CSS:

.break-out {
    width: 100vw;
    position: relative;
    left: 50%;
    right: 50%;
    margin-left: -50vw;
    margin-right: -50vw;
}

Давайте разберем всё по полочкам и поймём как это работает.

Добавляем width: 100vw

Самый важный шаг, который даст изображению ширину равную 100% вьюпорта.

Добавляем margin-left: -50vw

Чтобы отцентровать изображение, нам понадобится выдать отрицательный маргин с половиной ширины вьюпорта.

Добавляем left: 50%

И наконец, мы отодвинем изображение в правую сторону со значением 50% от ширины его родителя.

Вертикальный и горизонтальный спейсинг

Ещё один интересный пример, а именно использование вьюпорт единиц для спейсинга между элементами. Его можно использовать с таким значениями как margintopbottom и grid-gap. Применяя его, спейсинг будет основываться на ширине вьюпорта и его высоте, что может быть крайне полезным для создания динамических шаблонов.

Модалка

В случае с модалками нам надо отталкивать их с самого верха вьюпорта. Зачастую я делал это с помощью свойства top и процентов с пикселями. Однако, с единицами вьюпорта, мы можем добавить спейсинг, который может меняться, основываясь на высоте самого вьюпорта.

.modal-body {
    top: 20vh;
}

Посмотрите это видео, чтобы посмотреть на результат.

Шапка

Шапка это секция, которая показывает нам общую картину на странице или сайте. Часто там есть заголовок и описание, и среди этого всего есть фиксированная высота и падинги для верха и подвала страницы. Тут нам интересны падинги.

Для примера, вот как выглядит CSS:

.page-header {
    padding-top: 1rem;
    padding-bottom: 1rem;
}

@media (min-width: 800px) {
    .page-header {
        padding-top: 4rem;
        padding-bottom: 4rem;
    }
}

Вертикальный паддинг довольно мал на мобильных устройствах и он становится больше на больших вьюпортах. Как насчет использования единиц вьюпорта? Давайте посмотрим на это.

.page-header {
    padding-top: 10vh;
    padding-bottom: 10vh;
}

.page-header h2 {
    margin-bottom: 1.5vh;
}

Я использовал vh для паддинга в шапке и маргина под заголовком. Обратите внимание как меняется спейсинг!

Сетка из нескольких элементов

Ещё один пример динамического спейсинга можно применить в сетке из нескольких элементов. Тут может быть всё: статьи, секции контента, услуги и тп. В нашем примере мы посмотрим на то, как можно использовать вьюпорт единицы для сетки из статей.

Используя их в grid-gap, мы можем получить желаемый результат. Обратите внимание, что я применял функцию calc(). Суть использования calc() в том, чтобы у нас уже был базис для вертикального и горизонтального промежутков.

.wrapper {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
    grid-gap: calc(14px + 1vh) calc(14px + 0.5vw);
}

Vmin и Vmax — примеры использования

Изучая примеры использования для этих двух единиц, я не нашёл ничего, только этот пример из CSS-tricks.

Этот пример показывает возможность работы с верхним и нижним паддингом в шапке элемента. Паддинг зачастую слишком сокращен, когда вьюпорт довольно мал. Используя vmin, мы можем иметь флюидные верхние и нижние паддинги, которые основываются на вьюпорте с маленькими размерами.

.page-header {
    padding: 10vmin 1rem;
}

Пример

Вертикальные медиа запросы и единицы вьюпорта

Несколько лет назад я написал статью о вертикальных медиа запросах.

Сейчас я хочу пролить свет на эту тему, так как она имеет прямое отношение к статье, которую вы сейчас читаете.

Секции на всю высоту экрана в Landscape режиме

Используя вертикальные медиа запросы мы можем проверять находится ли девайс пользователя в лэндскэйп режиме. Если так, то тогда не будет смысла иметь секцию, занимающую всю высоту вьюпорта, применяя height: 100vh.

Чтобы решить эту проблему, мы можем сделать следующее.

@media (min-height: 400px) {
    .section {
        height: 100vh;
    }
}

Или мы можем использовать orientation в медиа запросах:

@media (orientation: landscape) {
    .section {
        height: 100vh;
    }
}

Соотношение сторон

Мы можем использовать vh для создания адаптивных элементов, которые поддерживают актуальное состояние соотношения сторон вне зависимости от размера вьюпорта. Давайте на это посмотрим.

Нам нужно решить, какое соотношение сторон нам нужно. Для примера, 9/16.

section {
    /* 9/16 * 100 */
    height: 56.25vw;
}

Пример

Используем единицы вьюпорта для графических элементов

Я не уверен правильно ли я назвал это, но надеюсь, что вы поймете суть следующих графических примеров.

Довольно популярный подход с верхним бордером

Вы знали, что верхний бордер один из самых используемых элементов на веб сайтах к сегодняшнему дню? В основном его цвет, это такой же как и основной цвет страницы.

Давайте сделаем изначальным значением бордера 3px. Как конвертировать это фиксированное значение в вьюпорт единицу? Вот как можно это посчитать:

vw = (Pixel Value / Viewport width) * 100

Ширина вьюпорта используется для расчета отношения между пикселями и vw единицей.

Для примера, вот как добавлен верхний бордер:

.header {
    border-top: 4px solid #8f7ebc;  
}

В моём случае ширина вьюпорта равна 1440.

vw = (4 / 1440) * 100 = 0.277

И вот такой CSS мы получим:

.header {
    border-top: 0.277vw solid #8f7ebc;  
}

Даже лучше, мы можем использовать основное значение в пикселях и единицу вьюпорта.

.header {
    border-top: calc(2px + 0.138vw) solid $color-main;
}

Нумерация секций

Для элементов секций с каунтером или иконкой, мы можем тоже применить единицы вьюпорта. Посмотрите на макет ниже:

/**
1. Используем гибкое значение для flex, width и height свойств.
2. line-height используется для центровки текста вертикально.
3. Размер шрифта.
4. Расстояние между кругом и текстом
**/
.point:before {
    flex: 0 0 calc(24px + 4vw); /* [1] */
    width: calc(24px + 4vw); /* [1] */
    height: calc(24px + 4vw); /* [1] */
    line-height: calc(24px + 4vw); /* [2] */
    font-size: calc(14px + 1vw); /* [3] */
    margin-right: calc(10px + 0.5vw); /* [4] */
}

Пример

Viewport Units Watcher

Я сделал инструмент, которые помогает мне в проверке высчитанного значения элементов у единиц вьюпорта. Посмотрите ниже:

Я добавил имя класса и свойство для каждого элемента и при смене размеров вьюпорта, значение будут динамически обновляться. Это куда лучше, чем использование DevTools и высчитывание каждого отдельного элемента.

Пример

Проблема со скроллом на мобильных устройствах

Есть распространенная проблема на мобильных устройствах, которая касается скролла, даже если используется 100vh. Проблема тут в том, что адресная строка находится во вьюхе. Луис Хобрегс написал статью об этом и показал простое и легкое её решение.

.my-element {
  height: 100vh;
  height: calc(var(--vh, 1vh) * 100);
}

А теперь js:

let vh = window.innerHeight * 0.01;
document.documentElement.style.setProperty('--vh', `${vh}px`);

Использование 100vw как антипаттерн

Применяя 100vw для того, чтобы выдать элементу полную ширину вьюпорта, всё у вас будет отлично работать на Mac OS, так как вертикальный скроллбар там спрятан по-дефолту. Как результат, ширина скроллбара добавлена полной ширине.

Однако, пользователи Windows обратят внимание на то, что появился горизонтальный скроллинг. Почему? Потому что ширина скроллбара была добавлена ширине экрана. Скажем, что вы добавили такой CSS:

.element {
    width: 100vw;
}

На Windows, высчитанная ширина .element будет равна 100vw + 8.5px, где 8.5px это ширина вертикального скроллбара.

Насколько я знаю, то пока что нет конкретного решения этой проблемы без использования JavaScript, применение которого тут я не советую. Лучше избегать 100vw и использовать альтернативу. Для примера, мы можем применить CSS Grid, чтобы элемент вышел за пределы контейнера, посмотрите на решение в этой статье.

Спасибо Крису Моргану и Килиану Валкхофу за то, что предупредили меня об этой проблеме.

Доступность это важно

Используя единицы вьюпорта довольно часто на вашем проекте, например для создание всего макета, доступность становится очень важной. Когда шаблон зумится ближе или отдаляется, элементы с единицами вьюпорат не будут увеличиваться или уменьшаться как вы ожидали. Это станет проблемой для пользователей, которые полагались на зум в браузере.

По словам Сары Суейдан, использование единиц вьюпорта для размеров шрифтов это плохая практика в плане доступности. Лучше комбинировать их с фиксированными значениями, как описано ниже:

.title {
    font-size: calc(16px + .3vw);
}

Даже лучше, мы можем использовать CSS единицы em и rem, вместо px. Таким образом у нас будет больше контроля при изменении размера шрифта.

.title {
    font-size: calc(1rem + .3vw);
}

И это будет хорошо. Пожалуйста, убедитесь в том, что использование единиц вьюпорта подходит для того, что вы делаете и не забывайте хорошо тестировать.

Полезные инструменты

Я нашёл этот полезный конвертер из px в vw, который может помочь вам в вашем проекте.

У вас есть какие-нибудь полезные инструменты? Дайте мне знать.

Ресурсы и связанные статьи

Заключение

Это всё. У вас есть какие-нибудь комментарии и предложения? Пишите автору на shadeed9.