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

Например, следует ли учитывать полосу прокрутки для vw-единиц? Как насчет навигации по сайту или элементов управления страницами? Еще есть физические атрибуты самих устройств (привет, notch!), которые нельзя упускать из виду.

Немного контекста

Спецификация довольно расплывчато объясняет, как должны рассчитываться единицы вьюпорта. На мобильных устройствах мы в основном имеем дело с высотой, поэтому давайте разберемся с проблемой на примере высоты области просмотра (viewport height, vh):

vh unit
Равна 1% от высоты исходного блока контента.

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

vh изначально рассчитывается по текущей высоте вьюпорта вашего браузера. Когда вы открываете браузер и начинаете загружать на сайт, 1vh = 1% от высоты экрана, минус интерфейс.

Но если вы начинаете прокрутку, все меняется. Как только скроется адресная строка, значение vh обновится, и контент страницы некрасиво «прыгнет».

Safari для iOS был одним из первых мобильных браузеров, который обновил свою реализацию, установив фиксированное значение для vh на основе максимальной высоты экрана. При этом не будет испытывать прыжков при скрытии адресной строки. Мобильный Chrome последовал этому примеру около года назад.

На момент написания этой статьи, есть тикет для решения этой проблемы в Firefox Android.

Хотя фиксированное значение удобно, у него есть и минусы. Если адресная строка находится в поле зрения, нижняя часть блока со значением высоты 100vh будет обрезана.

Элемент обрезается внизу (слева), но нам бы хотелось получить полный вид (справа)

Трюк с пользовательскими свойствами CSS

Пользовательские свойства CSS и несколько строк JavaScript могут стать идеальным способом получить согласованный и правильный размер вьюпорта.

В JavaScript всегда можно получить значение текущего окна просмотра с помощью window.innerHeight, которое учитывает интерфейс браузера и обновляется при изменении области видимости. Хитрость заключается в том, чтобы сохранить это значение в CSS-переменной и применить его к элементу вместо единицы vh.

Назовем переменную —vh. Применить ее можно следующим образом:

.my-element {
  height: 100vh; /* фолбэк для браузеров, которые не поддерживают пользовательские свойства */
  height: calc(var(--vh, 1vh) * 100);
}

Теперь нужно внутреннюю высоту окна просмотра в JavaScript:

// Сначала получаем высоту окна просмотра 
// и умножаем ее на 1%
let vh = window.innerHeight * 0.01;

// Затем устанавливаем значение свойства --vh
// для корневого элемента
document.documentElement.style.setProperty('--vh', `${vh}px`);

Итак, с помощью JS мы получили высоту вьюпорта, вычислили 1% от нее и присвоили в пользовательскую переменную.

Теперь можно использовать --vh вместо vh:

See the Pen Viewport Height on Mobile (no resize on update) by CSS-Tricks (@css-tricks) on CodePen.

Еще одна маленькая деталь

Кажется, что мы все сделали, но это не так. Возможно вы обратили внимание, что скрипт срабатывает один раз, но не обновляет значение переменной при изменении высоты окна просмотра.

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

// слушаем событие resize
window.addEventListener('resize', () => {
  // получаем текущее значение высоты
  let vh = window.innerHeight * 0.01;
  document.documentElement.style.setProperty('--vh', `${vh}px`);
});

Важно!

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

Кроме того, вы можете реализовать метод debounce для события resize, чтобы избежать запуска многих событий при изменении размера окна. Узнать больше можно в этой статье.

See the Pen Correct Viewport Height on Mobile by CSS-Tricks (@css-tricks) on CodePen.

Теперь вы можете изменить размер демо, CSS-переменная обновляется соответствующим образом.

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

2 комментария

  • Николай
    Николай
    16/06/2019, 17:58
    Правда я заменил setProperty('--vh', `${vh}px`) на setProperty('--vh', vh + 'px') иначе сборщик ругался. Вроде работает. Большое спасибо.
  • Михаил
    Михаил
    4/12/2018, 14:48
    Всё чётко работает, спасибо за статью!

Оставить комментарий

*Доступные HTML-теги: a, abbr, blockquote, code, pre, del, i, em, strong, b, strike
*Не будет опубликован