JavaScript и viewport: инструменты адаптивного дизайна

Раздел: Веб-разработка -> Адаптивная верстка

Определение viewport в JavaScript

Как получить точный размер видимой области с учётом мобильных браузеров, когда адресная строка или панель инструментов меняют высоту?

Основное решение: использовать window.visualViewport – современный API, предоставляющий актуальные размеры видимой области (включая динамические изменения, вызванные появлением клавиатуры или скроллом браузера).


// Получение размеров через visualViewport
if (window.visualViewport) {
  const width = window.visualViewport.width;
  const height = window.visualViewport.height;
  console.log(`Ширина: ${width}px, Высота: ${height}px`);
}
  

Viewport js (viewport в javascript)

API поддерживается во всех современных браузерах, включая Safari на iOS. Событие resize на объекте visualViewport позволяет отслеживать изменения в реальном времени.

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

Типичная ошибка: полагаться на window.innerHeight в мобильных браузерах, так как оно может включать высоту адресной строки. Решение: всегда использовать window.visualViewport для чувствительных к размерам элементов.

Проблема: метод не работает в старых браузерах (Internet Explorer). Для них применять window.innerHeight как запасной вариант.

Варианты получения размеров viewport

Как получить размер области просмотра без учёта панелей инструментов браузера (только видимое содержимое)?

Использовать window.innerWidth и window.innerHeight. Эти значения возвращают ширину и высоту внутреннего окна, включая полосу прокрутки (если она есть).


console.log(`inner: ${window.innerWidth}x${window.innerHeight}`);
  

Цель: когда требуется получить размер всего окна, включая возможные скрытые панели (работает в большинстве настольных браузеров).

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

Как получить размер видимого содержимого страницы (без полос прокрутки и панелей браузера)?

Использовать document.documentElement.clientWidth и document.documentElement.clientHeight. Эти значения показывают размер области, где размещается контент (за вычетом полосы прокрутки).


console.log(`client: ${document.documentElement.clientWidth}x${document.documentElement.clientHeight}`);
  

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

В некоторых версиях браузеров высота может отличаться от window.innerHeight из-за панели закладок или инструментов разработчика.

Как реагировать на изменения viewport с помощью CSS-медиа-запросов в JavaScript?

Использовать window.matchMedia. Создаётся объект, представляющий медиа-запрос, и добавляется слушатель изменения.


const mq = window.matchMedia('(min-width: 768px)');
function handleMQ(e) {
  if (e.matches) console.log('Ширина больше 768px');
  else console.log('Ширина меньше 768px');
}
mq.addListener(handleMQ);
handleMQ(mq);
  

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

Не все старые браузеры поддерживают addListener (устаревший синтаксис). Рекомендуется использовать addEventListener с современным синтаксисом: mq.addEventListener('change', handleMQ).

Как отслеживать изменения размеров viewport с помощью ResizeObserver?

ResizeObserver может наблюдать за document.documentElement или window. При каждом изменении размера вызывается колбэк.


const observer = new ResizeObserver(entries => {
  for (const entry of entries) {
    console.log('Новый размер:', entry.contentRect.width, entry.contentRect.height);
  }
});
observer.observe(document.documentElement);
  

Цель: более эффективное отслеживание изменений по сравнению с событием resize (меньше накладных расходов) и возможность наблюдать за конкретными элементами.

Поддержка ResizeObserver в большинстве современных браузеров хорошая, но в Internet Explorer её нет. Также наблюдатель может вызывать колбэк при изменении дочерних элементов, что иногда излишне.

Как обрабатывать изменение размеров окна с помощью события resize?

Классический подход – слушать событие resize на объекте window. Для оптимизации используют throttle или debounce.


window.addEventListener('resize', function() {
  console.log('Новый размер:', window.innerWidth, window.innerHeight);
});
  

Цель: простой способ реакции на изменение окна, подходит для настольных устройств с небольшой частотой событий.

Событие может вызываться десятки раз в секунду, что приводит к снижению производительности. Решение: использовать debounce (например, setTimeout) или requestAnimationFrame.

На мобильных устройствах событие может не срабатывать при открытии клавиатуры; для этого лучше применять visualViewport.

Расширенные примеры работы с viewport в JavaScript

Пример 1: Реальное время с visualViewport и адаптация высоты под клавиатуру

Пример

// Выводим размеры видимой области и устанавливаем CSS-переменную
function updateViewport() {
  if (window.visualViewport) {
    const { width, height } = window.visualViewport;
    document.documentElement.style.setProperty('--vh', `${height * 0.01}px`);
    document.getElementById('sizeOutput').textContent = `${width} × ${height}`;
  }
}
window.visualViewport?.addEventListener('resize', updateViewport);
updateViewport();
Результат: CSS-переменная --vh равна 1% от высоты видимой области; при появлении клавиатуры высота уменьшается, и элементы с высотой 100vh (через calc(100 * var(--vh) + 0px)) корректно адаптируются.

Пример 2: Сравнение всех методов для отладки на разных устройствах

Пример

function logSizes() {
  console.log('inner', window.innerWidth, window.innerHeight);
  console.log('client', document.documentElement.clientWidth, document.documentElement.clientHeight);
  if (window.visualViewport) {
    console.log('visual', window.visualViewport.width, window.visualViewport.height);
  }
}
window.addEventListener('resize', logSizes);
logSizes();
Результат (пример для iPhone с клавиатурой):
inner: 375 812
client: 375 646
visual: 375 400

Видно, что visualViewport даёт наименьшую высоту при открытой клавиатуре, что наиболее точно.

Пример 3: Использование matchMedia для переключения между десктопным и мобильным кодом

Пример

const isDesktop = window.matchMedia('(min-width: 1024px)');
function toggleAppMode(e) {
  if (e.matches) {
    loadDesktopScript();
  } else {
    loadMobileScript();
  }
}
isDesktop.addEventListener('change', toggleAppMode);
toggleAppMode(isDesktop);
Результат: при изменении ширины окна автоматически подключаются разные скрипты (например, слайдер для десктопа и свайп-навигация для мобильных).

Пример 4: Коррекция 100vh на мобильных с помощью visualViewport и CSS custom properties

Пример

function setVh() {
  const vh = window.visualViewport ? window.visualViewport.height * 0.01 : window.innerHeight * 0.01;
  document.documentElement.style.setProperty('--vh', `${vh}px`);
}
window.addEventListener('resize', setVh);
// Использование в CSS:
// .fullscreen { height: calc(var(--vh, 1vh) * 100); }
Результат: блоки с 100vh не обрезаются под адресную строку на мобильных и корректно пересчитываются при её скрытии или появлении клавиатуры.

Пример 5: ResizeObserver для определения изменения размера контейнера (а не viewport)

Пример

const sidebar = document.getElementById('sidebar');
const observer = new ResizeObserver(entries => {
  for (let entry of entries) {
    if (entry.contentBoxSize) {
      const inlineSize = entry.contentBoxSize[0].inlineSize;
      console.log('Ширина сайдбара:', inlineSize);
    }
  }
});
observer.observe(sidebar);
Результат: при изменении ширины элемента sidebar (например, из-за коллапса) выводится актуальная ширина. Это удобно для адаптивных панелей, размер которых меняется динамически.

Пример 6: Debounced обработчик resize для производительности

Пример

function debounce(fn, delay) {
  let timer;
  return function(...args) {
    clearTimeout(timer);
    timer = setTimeout(() => fn.apply(this, args), delay);
  };
}
const handler = debounce(() => {
  console.log('Окно изменено:', window.innerWidth);
}, 200);
window.addEventListener('resize', handler);
Результат: событие resize обрабатывается не чаще одного раза в 200 мс, снижая нагрузку на рендеринг.

Viewport в JavaScript - comments

En
Viewport js (javascript)