Оффтоп Digital Skynet
296

React - отслеживаем видимость страниц

В этой статье мы создадим простой React компонент, который отслеживает “Page Visibility State.”

В закладки
Офис "Digital Skynet"

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

На первый взгляд эта функция кажется довольно простой для реализации, но это не совсем так. Отслеживание активности пользователя довольно сложный процесс.

Есть Page Visibility API, который отлично работает в большинстве случаев, но не обрабатывает все возможные случаи неактивности вкладки браузера. Page Visibility API отправляет событие visibilitychange, чтобы listeners знали, что состояние видимости страницы изменилось. Он не запускает событие в некоторых случаях, если окно или соответствующая вкладка браузера скрыты из виду.

Чтобы обрабатывать некоторые из этих случаев, нам нужно использовать комбинацию событий focus и blur как в document, так и на window.

Начнем

Мы будем использовать Codesandbox для выполнения React приложения (вы также можете использовать create-react-app). Создадим небольшое приложение, в котором HTML5 элемент Video будет воспроизводиться только в том случае, если вкладка браузера находится в фокусе или активна, иначе оно будет приостановлено автоматически. Мы используем Video, так как это облегчит тестирование функций приложения.

Создадим простой React класс и элемент video с его исходным кодом, принимающим URL-адрес, переданный с помощью src. Мы будем использовать новый ref API для присоединения ссылки к DOM-node video. Настроим video на автовоспроизведение, предполагая, что когда мы запустим приложение, страница будет активна.

Отметим, что Safari не позволяет автоматически воспроизводить элементы мультимедиа без взаимодействия с пользователем. Метод componentDidUpdate очень удобен при обработке эффектов при изменении свойства компонента. Поэтому здесь будем использовать этот метод для воспроизведения и приостановки видео на основе текущего значения this.props.active.

Различия в префиксах браузера не всегда удобны в использовании определенных API-интерфейсов, и Page Visibility API один из них. Мы создадим простую функцию утилиты, которая будет обрабатывать эти различия и возвращать нам единый API на основе браузера пользователя. Мы создадим и экспортируем эту функцию из pageVisibilityUtils.js в src directory.

В этой функции будем использовать оператор if-else, чтобы вернуть API-интерфейс, специфичный для браузера. Видно, что мы добавляем префикс ms для Internet Explorer и префикс webkit для Webkit браузеров. Мы будем хранить нужный API в hidden и visibilityChange переменных и возвращать их из функции в виде простого объекта. Наконец экспортируем функцию.

Затем перейдем на наш основной компонент. Мы инкапсулируем всю логику отслеживания видимости страницы в используемом класс-компоненте React, используя шаблон Render Props. Создадим класс-компонент VisibilityManager. Этот компонент будет обрабатывать добавление и удаление всех событий на основе DOM listeners.

Начнем с импорта ранее созданной вспомогательной функции и ее вызова для получения правильных API-интерфейсов браузера. Затем создадим React компонент и инициализируем его состояние с одним единственным полем isVisible установленным в true. Это Boolean поле будет отвечать за состояние видимости страницы.

В componentDidMount мы добавим event listener к document для visibilitychange с помощью метода this.handleVisibilityChange в качестве обработчика. Также добавим event listener для событий focus и blur в document, а также элементе window. На этот раз мы установим this.forceVisibilityTrue и this.forceVisibilityFalse как обработчики для событий focus и blur.

Затем мы создадим метод handleVisibilityChange, который принимает аргумент forceFlag. Аргумент forceFlag будет использоваться для определения того, вызван ли метод из-за события visibilitychange или событий focus и blur. Это происходит потому, что методы forceVisibilityTrue и forceVisibilityFalseничего не делают, кроме вызова метода handleVisibilityChange с истинным и ложным значением для forceFlag.

Внутри метода handleVisibilityChange сначала проверяем является ли значение аргумента forceFlag логическим (если он вызывается из обработчика события visibilitychange, то переданный аргумент будет объектом SyntheticEvent).

Если это Boolean, тогда мы проверяем истинно оно или ложно. Когда истинно, вызываем метод setVisibility с true или вызываем метод с false. Метод setVisibility использует this.setState метод для обновления значения isVisibleв состоянии компонента.

Если forceFlag не Boolean, мы проверяем значение скрытого атрибута в document и соответствующим образом вызываем метод setVisibility. Это завершает логику отслеживания состояния видимости страницы.

Чтобы сделать компонент многоразовым, мы используем паттерн Render Props. То есть вместо рендеринга компонента из метода render мы вызываем this.props.children как функцию с this.state.isVisible.

Наконец, мы устанавливаем React приложение в DOM в файле index.js. Мы импортируем два компонента React VisibilityManager и Video и создаем компонент App, соединив их. Мы передаем функцию как дочерний элемент компонента VisibilityManager, который принимает на вход isVisible и передает его компоненту Video на выходе. Также передаем URL-адрес видео как src для компонента Video. Вот как мы применяем компонент VisiblityManager на основе Render Props. В конце используем метод ReactDOM.render для рендеринга приложения на DOM-node с идентификатором «root».

Заключение

Современные API-интерфейсы браузера становятся действительно мощными и используются для создания потрясающих вещей. Большинство из этих API являются обязательными по своей сути и могут быть сложными в работе. Полезно использовать паттерн Render Props для переноса этих API-интерфейсов в их собственные повторно-используемые React компоненты.

Адаптированный перевод статьи How to track page visibility in React using render props от Digital Skynet :)

Материал опубликован пользователем. Нажмите кнопку «Написать», чтобы поделиться мнением или рассказать о своём проекте.

Написать
{ "author_name": "Digital Skynet", "author_type": "self", "tags": [], "comments": 3, "likes": -4, "favorites": 0, "is_advertisement": false, "subsite_label": "flood", "id": 45860, "is_wide": false, "is_ugc": true, "date": "Mon, 17 Sep 2018 09:56:07 +0300" }
{ "id": 45860, "author_id": 187179, "diff_limit": 1000, "urls": {"diff":"\/comments\/45860\/get","add":"\/comments\/45860\/add","edit":"\/comments\/edit","remove":"\/admin\/comments\/remove","pin":"\/admin\/comments\/pin","get4edit":"\/comments\/get4edit","complain":"\/comments\/complain","load_more":"\/comments\/loading\/45860"}, "attach_limit": 2, "max_comment_text_length": 5000, "subsite_id": 199791 }

3 комментария 3 комм.

Популярные

По порядку

2

Зачем это на vc?

Ответить
1

А можно оторвать руки за скриншоты кода? Ну и плюс да, зачем это тут?

Ответить
0

Почему не на Хабр?

Ответить
0

Прямой эфир

[ { "id": 1, "label": "100%×150_Branding_desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox_method": "createAdaptive", "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "ezfl" } } }, { "id": 2, "label": "1200х400", "provider": "adfox", "adaptive": [ "phone" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "ezfn" } } }, { "id": 3, "label": "240х200 _ТГБ_desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "fizc" } } }, { "id": 4, "label": "240х200_mobile", "provider": "adfox", "adaptive": [ "phone" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "flbq" } } }, { "id": 5, "label": "300x500_desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "ezfk" } } }, { "id": 6, "label": "1180х250_Interpool_баннер над комментариями_Desktop", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "pp": "h", "ps": "bugf", "p2": "ffyh" } } }, { "id": 7, "label": "Article Footer 100%_desktop_mobile", "provider": "adfox", "adaptive": [ "desktop", "tablet", "phone" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "fjxb" } } }, { "id": 8, "label": "Fullscreen Desktop", "provider": "adfox", "adaptive": [ "desktop", "tablet" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "fjoh" } } }, { "id": 9, "label": "Fullscreen Mobile", "provider": "adfox", "adaptive": [ "phone" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "fjog" } } }, { "id": 10, "disable": true, "label": "Native Partner Desktop", "provider": "adfox", "adaptive": [ "desktop", "tablet" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fmyb" } } }, { "id": 11, "disable": true, "label": "Native Partner Mobile", "provider": "adfox", "adaptive": [ "phone" ], "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "clmf", "p2": "fmyc" } } }, { "id": 12, "label": "Кнопка в шапке", "provider": "adfox", "adaptive": [ "desktop" ], "adfox": { "ownerId": 228129, "params": { "p1": "bscsh", "p2": "fdhx" } } }, { "id": 13, "label": "DM InPage Video PartnerCode", "provider": "adfox", "adaptive": [ "desktop", "tablet", "phone" ], "adfox_method": "createAdaptive", "adfox": { "ownerId": 228129, "params": { "pp": "h", "ps": "bugf", "p2": "flvn" } } }, { "id": 14, "label": "Yandex context video banner", "provider": "yandex", "yandex": { "block_id": "VI-223676-0", "render_to": "inpage_VI-223676-0-1104503429", "adfox_url": "//ads.adfox.ru/228129/getCode?pp=h&ps=bugf&p2=fpjw&puid1=&puid2=&puid3=&puid4=&puid8=&puid9=&puid10=&puid21=&puid22=&puid31=&puid32=&puid33=&fmt=1&dl={REFERER}&pr=" } }, { "id": 15, "label": "Плашка на главной", "provider": "adfox", "adaptive": [ "desktop", "tablet", "phone" ], "adfox": { "ownerId": 228129, "params": { "p1": "byudx", "p2": "ftjf" } } }, { "id": 16, "label": "Кнопка в шапке мобайл", "provider": "adfox", "adaptive": [ "tablet", "phone" ], "adfox": { "ownerId": 228129, "params": { "p1": "byzqf", "p2": "ftwx" } } }, { "id": 17, "label": "Stratum Desktop", "provider": "adfox", "adaptive": [ "desktop" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "fzvb" } } }, { "id": 18, "label": "Stratum Mobile", "provider": "adfox", "adaptive": [ "tablet", "phone" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "pp": "g", "ps": "bugf", "p2": "fzvc" } } }, { "id": 19, "label": "Тизер на главной", "provider": "adfox", "adaptive": [ "desktop", "tablet", "phone" ], "auto_reload": true, "adfox": { "ownerId": 228129, "params": { "p1": "cbltd", "p2": "gazs" } } } ]
Приложение-плацебо скачали
больше миллиона раз
Подписаться на push-уведомления