Когда первые складные смартфоны появились на рынке несколько лет назад, большинство фронтенд-разработчиков отреагировало на них с легким скепсисом: мол, очередная экзотическая игрушка для гиков, которая скоро сойдет на нет. Но индустрия упорно двигалась вперед. Экраны сгибались по вертикали, раскладывались в планшеты по горизонтали, а появление симулятора долгожданного iPhone Duo в составе Xcode 27.1 beta 1 окончательно зафиксировало новую реальность: складные устройства стали полноценным форм-фактором потребительской электроники.
И вот здесь веб-разработка столкнулась с физическим миром. Мы десятилетиями верстали под плоские прямоугольники экранов. Все наши сетки, flexbox-контейнеры и абсолютные координаты исходили из того, что пиксели на экране непрерывны и лежат в одной плоскости. Но когда устройство складывается пополам, прямо поперек твоего сайта возникает физический шарнир (hinge) — шов из стекла, пластика и шарнирного механизма, искажающий изображение, ломающий текст и делающий интерактивные кнопки труднодоступными.
Как браузеру понять, что устройство согнуто, и как элегантно адаптировать верстку без костылей? Инженеры популярного открытого видеоплеера Video.js во главе с Дариусом Чепулисом провели глубокое исследование нативных стандартов W3C и показали, как построить интерфейс будущего.
Костыли прошлого против стандартов W3C
Раньше разработчики пытались решить проблему складных устройств дедовскими методами: парсили строку User-Agent браузера, определяли модель телефона, хардкодили в CSS фиксированные размеры в пикселях и надеялись, что пользователь не повернет экран.
Такой подход рассыпался при малейшем изменении контекста:
- пользователь повернул устройство из книжной ориентации в альбомную — пиксельные отступы поехали;
- операционная система изменила масштаб интерфейса — шарнир сместился мимо захардкоженных границ;
- пользователь приоткрыл смартфон наполовину (режим «ноутбука» на столе) — сайт продолжал думать, что перед ним плоский экран.
Консорциум W3C разработал два дополняющих друг друга стандарта, которые наконец-то появились в Safari под экспериментальными флагами: Device Posture API и CSS Viewport Segments API.
Device Posture API: определяем физическую позу
Первый стандарт отвечает на простой вопрос: в каком физическом состоянии сейчас находится шарнир устройства?
В JavaScript это состояние доступно через объект navigator.devicePosture. Свойство type возвращает одно из значений:
'continuous'— устройство полностью раскрыто в плоский экран либо является обычным монолитным смартфоном/ноутбуком;'folded'— экран физически согнут под углом (например, устройство стоит на столе как раскрытый мини-ноутбук или лежит как раскрытая книга).
Что особенно приятно, API предоставляет стандартное событийное уведомление:
if ('devicePosture' in navigator) {
// Подписываемся на физическое изменение угла сгиба корпуса
navigator.devicePosture.addEventListener('change', () => {
console.log('Текущая поза устройства:', navigator.devicePosture.type);
if (navigator.devicePosture.type === 'folded') {
document.body.classList.add('device-is-folded');
} else {
document.body.classList.remove('device-is-folded');
}
});
}
А в CSS для этого появилось нативное медиавыражение:
@media (device-posture: folded) {
/* Стили применяются только тогда, когда корпус физически согнут */
}
Viewport Segments API: геометрия двух экранов
Одного знания о том, что экран согнут, недостаточно. Нам нужно знать точные координаты и размеры каждого видимого сегмента по обе стороны от шарнира. За это отвечает спецификация Viewport Segments.
Браузер делит окно просмотра на двухмерную сетку сегментов, к которым можно обращаться через координаты (x, y):
(0, 0)— верхний (или левый) сегмент;(0, 1)— нижний сегмент (при горизонтальной линии сгиба);(1, 0)— правый сегмент (при вертикальной линии сгиба).
Спецификация добавляет новые переменные окружения env():
env(viewport-segment-width x y)— ширина конкретного сегмента;env(viewport-segment-height x y)— высота сегмента;env(viewport-segment-top x y)иenv(viewport-segment-left x y)— координаты начала сегмента относительно верхнего левого угла окна.
Медиавыражения @media (vertical-viewport-segments: 2) и @media (horizontal-viewport-segments: 2) позволяют декларативно проверять, на сколько частей разделен экран по соответствующей оси.
Практический кейс Video.js: плеер-ноутбук
Команда Video.js исследовала построение интерфейса, вдохновленного мобильными приложениями Netflix и YouTube.

Представь: пользователь смотрит видео в поезде или на кухне. Он ставит смартфон на стол, сгибая его на 90 градусов. Верхняя половина корпуса смотрит на зрителя, а нижняя лежит на горизонтальной поверхности как клавиатура ноутбука.
Было бы нелепо, если бы видеопоток продолжал растягиваться на весь согнутый экран, переламываясь пополам прямо по лицам актеров. Задача: при сгибе зафиксировать видео строго в верхнем сегменте (0, 0), а все кнопки управления (таймлайн, громкость, выбор звуковых дорожек и субтитров) переместить в нижний сегмент (0, 1), превратив его в удобный сенсорный пульт управления.
Вот как эта адаптация реализуется чистым CSS без единой строчки вычислений координат в JavaScript:
/* Базовое состояние: монолитный плоский экран */
.video-player-container {
display: grid;
grid-template-rows: 1fr auto;
width: 100vw;
height: 100vh;
background-color: #000;
}
/* Адаптация под складное устройство в режиме ноутбука */
@media (device-posture: folded) and (vertical-viewport-segments: 2) {
.video-player-container {
display: grid;
/* Верхний ряд сетки занимает точно высоту верхнего экрана,
а нижний ряд — точно высоту нижнего пульта управления */
grid-template-rows:
env(viewport-segment-height 0 0)
env(viewport-segment-height 0 1);
/* Зазор между рядами в точности равен физической толщине шарнира! */
row-gap: calc(
env(viewport-segment-top 0 1) - env(viewport-segment-bottom 0 0)
);
}
/* Видеопоток центрируется в верхнем сегменте */
.video-viewport {
grid-row: 1;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
}
/* Панель управления занимает нижний сегмент на столе */
.player-controls-panel {
grid-row: 2;
display: flex;
flex-direction: column;
justify-content: space-around;
padding: 1.5rem;
background: #181818;
border-top: 1px solid #333;
}
}
Обрати внимание на формулу вычисления row-gap: разница между верхним краем нижнего сегмента env(viewport-segment-top 0 1) и нижним краем верхнего сегмента env(viewport-segment-bottom 0 0) дает точную физическую толщину пластикового шва устройства. Твой контент гарантированно не попадет под «слепую зону» шарнира!
Подводные камни и нюансы Safari
Несмотря на очевидную мощь и элегантность стандартов, на этапе внедрения важно помнить о текущих ограничениях экосистемы:
- Статус стандартов: обе спецификации находятся в статусе W3C Working Draft. Это значит, что имена свойств уже стабилизировались, но синтаксис экспериментальных функций может уточняться. В Safari поддержка пока прячется под флагами в настройках для разработчиков.
- Ловушка Fullscreen API в iOS Safari: в мобильном Safari при нажатии кнопки полноэкранного режима браузер по умолчанию стремится перехватить воспроизведение в системный нативный плеер iOS. Нативный плеер пока не умеет разделять контролы по сегментам. Чтобы твоя кастомная раскладка Video.js продолжала работать при разворачивании на весь экран, требуется использовать флаг расширенного кастомного Fullscreen.
Веб-платформа в очередной раз доказывает свою удивительную гибкость: благодаря декларативным медиавыражениям и переменным окружения браузер способен адаптироваться к любым причудам аппаратной инженерии без раздувания клиентского JavaScript-кода.
