В этой статье рассматривается ориентация экрана для PWA (прогрессивного веб-приложения). Если вы разрабатываете собственное ионное приложение, уже существует простой в использовании плагин Cordova.

Помните, что в этой статье рассматриваются относительно новые функции по состоянию на март 2021 года. В будущем все может измениться. Но прежде всего это демонстрация конечного результата: хорошая кроссплатформенная блокировка ориентации, сделанная в Ionic Angular.

Демо: https://ionic-orientation-service.stackblitz.io/

Введение

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

Ну тогда! Давайте посмотрим, насколько хорошо это поддерживается типичными браузерами:

… По крайней мере, API должен облегчить вам жизнь. Хотя Screen Orientation API уже широко поддерживается, Apple не поддерживает его, и нам нужно с этим разобраться. Мы могли бы динамически проверять текущее соотношение сторон и заставлять пользователя вращать экран в качестве альтернативного решения. В этом случае мы могли бы также использовать еще одну замечательную функцию: полноэкранный API.

Технически заблокированный экран - это просто полноэкранный режим с принудительной ориентацией. Таким образом, объединение сообщения пользователю о переключении устройства в полноэкранный режим кажется отличным откатом к API ориентации экрана. Также, чтобы функция блокировки Screen Orientation API работала, приложение в любом случае должно быть в полноэкранном режиме. Насколько хорошо тогда поддерживается полноэкранный API?

Что ж, похоже, мы к цели! 👌

Даже если полноэкранный API не полностью поддерживается, мы все равно можем заставить его работать как запасной вариант. Однако специально для Safari на iOS есть одна неприятная особенность: Полноэкранный режим поддерживается только на iPad, но не на iPhone.

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

Составление логики

Подведем итоги тому, что мы узнали на данный момент:

  • API ориентации экрана можно использовать для всех устройств кроме устройств iOS.
  • Полноэкранный API будет работать на всех устройствах, кроме iPhone.
  • И блокировка экрана, и переход в полноэкранный режим возможны только в том случае, если пользователь взаимодействовал с приложением.
  • Для работы блокировки экрана приложение должно быть в полноэкранном режиме.

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

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

  1. (Снятие) блокировки экрана должно происходить внутри службы, чтобы мы могли принудительно установить определенную ориентацию, когда захотим, в любом месте приложения.
  2. Мы должны отслеживать события, которые могут произойти всякий раз, когда пользователь поворачивает устройство или покидает и снова входит в приложение.
  3. Если устройство заблокировано и ориентация неправильная, мы показываем модальное окно, которое можно отклонить только при правильной ориентации.
  4. При отклонении кнопки будет использоваться либо API ориентации экрана, либо полноэкранный API в зависимости от поддержки.

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

Приступим к кодированию

Начнем с создания новой услуги. Если хотите, это можно сделать с помощью команды ionic generate service. Я просто звоню в нашу службу OrientationService.

На данный момент это может показаться довольно специфическим, но позвольте мне объяснить, что мы здесь делаем, шаг за шагом. Сначала давайте взглянем на некоторые переменные:

currentOrientation$ - Это Наблюдаемое, которое представляет нашу текущую Ориентацию.

lockedOrientation$ - это либо BehaviorSubject типа OrientationType, либо null, если ориентация не заблокирована.

lockIsReady$ - Субъект поведения типа boolean. Это говорит нам, готова ли блокировка экрана.

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

Проверка реактивной ориентации

Внутри конструктора мы хотим добавить следующие слушатели событий:

document.visibilitychange - срабатывает всякий раз, когда вы выходите / входите в приложение или переключаете вкладки

document.fullscreenchange - это срабатывает всякий раз, когда мы переходим в полноэкранный режим или выходим из него.

window.resize - срабатывает при изменении размера окна.

screen.change - срабатывает при изменении ориентации.

Теперь, когда мы знаем, какие EventListeners нам нужны, мы можем позволить RxJS сделать это волшебство, чтобы проверить ориентацию элегантным способом, используя операторы fromEvent, merge и map.

Наш массив событий содержит множество наблюдаемых, и мы хотим прослушать их все. Поэтому мы объединяем весь массив с использованием синтаксиса Spread. Мы также применяем оператор debounceTime для решения сразу двух задач:

  1. Многие из этих событий происходят почти одновременно.
  2. В зависимости от события обновленный размер окна доступен не сразу.

С debounceTime мы гарантируем, что будет выдано только последнее значение между временным интервалом. Затем мы используем оператор map для запуска функции checkOrientation (), поскольку на самом деле мы не заботимся о значениях наблюдаемых. Что нас волнует, так это то, что все они могут быть связаны с изменением ориентации.

Мы вычисляем window.outerWidth / window.outerHeight, чтобы получить соотношение сторон. Принципиально важно использовать externalWidth / Height, а не innerWidth / Height, потому что последний не работает на iOS. В iOS есть собственный способ изменения ориентации путем поворота внутреннего окна на 90 °.

Установка ориентации экрана

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

setOrientation() либо возвращает истину, либо ложь в зависимости от того, может ли быть разрешена функция API ориентации экрана lock(). Прежде чем блокировка может быть применена, приложение также должно быть в полноэкранном режиме. Для лучшей совместимости функции переключения полноэкранного режима и блокировки экрана проходят проверку префиксов браузера при коротком замыкании и всегда разрешаются с помощью логического значения, чтобы не вызывать никаких ошибок.

Блокировка и разблокировка

Наша функция lock () сначала подталкивает ориентацию, которую мы хотим зафиксировать, к lockOrientation $ BehaviorSubject. Мы также подписываемся на операторы currentOrientation $ Observable и pipe two.

С takeWhile мы принимаем значения, пока значение lockedOrientation $ BehaviorSubject не равно нулю. Если оно равно нулю, подписка будет завершена, в этом случае экран разблокируется, и мы выйдем из полноэкранного режима.

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

В уведомлении next мы проверяем правильность ориентации. Для настольных устройств нет возможности изменить ориентацию - поэтому всегда выдается true. Мы проверяем, активен ли модальный в настоящее время, и в зависимости от этого, а также от статуса ориентации, мы либо показываем модальное окно, либо испускаем истину.

Создание модального

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

Наш модальный компонент - это компонент, которому требуется OrientationService, и наша OrientationService также вызывает OrientationModalComponent. Почему это важно? Потому что, если мы разделим эти два и импортируем друг друга, это вызовет предупреждения о циклической зависимости.

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

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

В этой заключительной части я просто собираюсь разместить здесь StackBlitz всего Service и Modal - он даже включает комментарии JSDoc, чтобы максимально упростить вашу жизнь. :)