В этой статье рассматривается ориентация экрана для 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.
- И блокировка экрана, и переход в полноэкранный режим возможны только в том случае, если пользователь взаимодействовал с приложением.
- Для работы блокировки экрана приложение должно быть в полноэкранном режиме.
Я всегда рекомендую концептуализировать логику с учетом всех ограничений. Это дает нам хорошее представление об основной логике, которую необходимо раскрыть.
Следующая важная часть - как мы наилучшим образом интегрируем эту логику. Для этого я обычно составляю список дополнительной логики:
- (Снятие) блокировки экрана должно происходить внутри службы, чтобы мы могли принудительно установить определенную ориентацию, когда захотим, в любом месте приложения.
- Мы должны отслеживать события, которые могут произойти всякий раз, когда пользователь поворачивает устройство или покидает и снова входит в приложение.
- Если устройство заблокировано и ориентация неправильная, мы показываем модальное окно, которое можно отклонить только при правильной ориентации.
- При отклонении кнопки будет использоваться либо 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
для решения сразу двух задач:
- Многие из этих событий происходят почти одновременно.
- В зависимости от события обновленный размер окна доступен не сразу.
С 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, чтобы максимально упростить вашу жизнь. :)