Website development
JavaScript
January 2015 13

Задача коммуникации между вкладками и выявления активной вкладки

Tutorial
Наглядный пример задачи — сайт vk.com. Каждый раз, когда вы воспроизводите музыку или видео в одной вкладке, в других вкладках воспроизведение останавливается. И если вы обратитесь в интернет за помощью в решении данной задачи, то наверняка найдете описание Storage Events или Page Visibility API или даже готовые решения, к примеру Visibility.js.

На хабре уже был обзор этих вещей, к примеру вот и ещё.



Я прошел по этому пути, но не одно из решений меня не устроило. Мне хотелось чего-нибудь простого, легковесного и не требующего глубокого изучения деталей.

Решение, которое получилось в итоге, обрело название DuelJS (просто рандомное уникальное название) и ниже я попробую сравнить его с Visibility.js, дабы избежать лишней критики в свою сторону.

Активная вкладка


Удобная кроссбраузерная обертка для простого отражения состояния «эта вкладка сейчас активна», в visibility.js будет выглядеть следующим образом:

if ('visible' == Visibility.state()) {
    // эта вкладка активна
}

У вкладки Visibility есть 3 состояния: visible, hidden и prerender.

Преимуществом Visibility также являются callbacks, которые вы можете вешать на множество событий, таких как активизация вкладки, setInterval в активном окне и тому подобное.

Философия DuelJS слегка упрощена:
1. Все вкладки имеют лишь 2 состояния — Master и Slave
2. Мастер вкладка это вкладка в которой ведется работа — ничего лишнего, все остальные есть Slave.

При таком подходе достаточно лишь одной функции: window.isMaster() — проверить является ли вкладка мастером.

if (window.isMaster()) {
    // эта вкладка активна
}

Коммуникации между вкладками


Теперь перейдем к коммуникациям между вкладками. Наиболее подходящим решением мне показалось использование Storage Events, хотя они и не без проблем. К слову находил я в гугле еще такие варианты как использование postMessage API или WebSockets.

Основная проблема Storage Events заключается в их плохой поддержке некоторыми MSIE, хотя до недавнего времени и у других браузеров тоже могли возникать с этим проблемы.

Так как Visibility.js является по сути оберткой над Page Visibility API — работа со Storage Events в ней отсутствует.

В DuejJS существует кроссбраузерная обертка над Storage Events, которая выражается в следующей философии:
1. Коммуникации между вкладками осуществляются при помощи каналов
2. Внутри канала вкладки могут запускать события, на которые могут реагировать другие вкладки в этом канале

Создать канал достаточно просто:

var ch = duel.channel('channel_name'); // channel_name - имя канала

Теперь определим поведение при вызове события qwerty:

ch.on('qwerty', function (a, b, c, ...) {})

Метод on у канала определяет его поведение. В функции, передаваемой вторым параметром может быть сколько угодно аргументов, или не быть вовсе.

Запустить событие так же легко. Слово on теперь заменим на broadcast, а передаваемые аргументы вставим после названия события:

ch.broadcast('qwerty', a, b, c, ...)


Создание плеера с поведением как на vk.com


Для нетерпеливых читателей сразу даю ссылку на рабочий пример.

Основная суть приложения заключается в трех строках:
1. var player = duel.channel('player'); (определение канала)
2. player.on('stop', function () {… (определение поведения при событии stop)
3. player.broadcast('stop'); (запуск события stop)

Полный код страницы выглядит следующим образом:
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Остановлено</title>
    <!-- Заголовок (title) страницы отражает состояние воспроизведения. -->

    <script type="text/javascript" src="duel.min.js"></script>
    <link rel="stylesheet" type="text/css" href="pretty.css">
</head>
<body>
    <!-- div #preview представляет из себя кликабельную картинку, при клике по которой инициализируется iframe с роликом youtube -->
    <div id="preview"><div>PLAY</div></div>
    <a href="index.html" target="_blank">Открыть новую вкладку</a>
    <script type="text/javascript">
    /** открываем канал с именем player */
    var player = duel.channel('player');

    /** превью для видео-ролика */
    var previewDiv = document.getElementById('preview');

    /**
     * Регистрируем новое событие канала player
     */
    player.on('stop', function () {
        /**
         * Удаляем iframe
         */
        var frame = document.getElementsByTagName('iframe')[0];
        frame.parentNode.removeChild(frame);

        /**
         * Показываем превью
         */
        previewDiv.style.display = 'block';

        /**
         * Обновляем заголовок вкладки
         */
        document.title = 'Остановлено';
    });

    previewDiv.onclick = function () {
        /**
         * Посылаем сигнал stop в канал player
         */
        player.broadcast('stop');

        /**
         * Создаем новый элемент iframe с видео из youtube
         */
        var frame = document.createElement("iframe");
        frame.width = '859';
        frame.height = '480';
        frame.src = '//www.youtube.com/embed/xsV8TrF4gN0?rel=0&autoplay=1';
        frame.frameborder = '0';

        /**
         * Встраиваем его под превью и скрываем превью
         */
        previewDiv.parentNode.insertBefore(frame, previewDiv.nextSibling);
        previewDiv.style.display = 'none';

        /**
         * Обновляем заголовок вкладки
         */
        document.title = 'Играем...';
    }
    </script>
</body>
</html>



Заключение


Буду очень рад если вам поможет данное решение и вы используете его в своих проектах. Я открыт к идеям по улучшению. Либа — кроссбраузерная и работает в том числе и в IE, за счет встроенных хаков.



Полезные ссылки


Краткая документация DuelJS на сайте
Репозиторий DuelJS на GitHub
Ещё одна демка DuelJS
Документация на readthedocs
+22
16k 233
Comments 24