Предотвращение одновременной прокрутки браузера и flash-контента, «расширенный курс»

от автора

Совсем не думал, что решая такой простой (как мне казалось) вопрос, придется здорово потрудиться и пораскинуть мозгами. Но в итоге получилась полноценная библиотека, которая адекватно работает со всеми видами flash — плагинов на странице (включая Pepper flash в Chrome, о котором позже).

Но обо всем по порядку.

Проблема одновременной прокрутки флеш контента и браузера существует давно, поэтому я был уверен, что есть готовое решение «из коробки».
Немного погуглив получаем желаемый результат — MouseWheelTrap. Библиотека отслеживает положение курсора, и если он находится над flash-контентом, отключает прокрутку в браузере, оставляя flash право единолично использованть ролик мыши. Скачиваем, подключаем, все работает.
Просто?
Отнюдь.
Первым неработоспособность блокировки заметил коллега — спросил, почему не работает скролл мышкой. Решил проверить на других машинах — проблема оказалась серьезней чем я думал сначала — на большинстве скролл так же не работал, причем только в Chrome.
Возникло два вопроса:

  1. Почему не работает готовая библиотека?
  2. Почему только в Chrome?

Chrome в этом плане здорово переплюнул IE8, поскольку IE с задачей справлялся на ура

Виной всему оказался плагин Pepper flash, который включается в сборку браузера Chrome начиная с версии 11.3. У Pepper flash своя API и «повышенная безопасность». Видимо как раз эта «безопасность» и стала корнем проблемы. Продолжив поиски решения, нашел большое количество упоминаний словосочетаний «Buggy pepper flash» и прочих не очень лестных замечаний в адрес этого флеш плагина. И очень удивило, что на Stackoverflow не было ни одного полноценного ответа по этой теме:
вопрос 1
вопрос 2

Это меня здорово вдохновило на поиск и написание собственного решения.

Первым что пришло в голову было следующее: не блокировать стандартную прокрутку браузера с помощью стандартной

event.preventDefault();     //Для всех браузеров, кроме IE event.returnValue = false;//Для IE 

а отслеживать передвижение колесика мыши и прокручивать браузер в противоположном направлении.
Идея, конечно, костыль с гвоздями, но как то начинать решение проблемы надо.
Оказалось что вызывать функции прокрутки страницы ни из обработчика события перемещения ролика мыши, ни из flash с помощью ExternalInterface нельзя.
Оно и к лучшему, решение в любом случае было далеко от изящного и оригинального.

Но немного разобрав ту самую библиотеку, удалось ее здорово улучшить. Основная её проблема была в том, что после каждого движения мыши она отправляла на выполнение довольно здоровый кусок кода на javascript, что в данном случае не имело совершенно никакого смысла:

if (ExternalInterface.available) {     ExternalInterface.call("eval", JAVASCRIPT);     return; } 

JAVASCRIPT — это «строка» кода, которая в развернутом состоянии занимает 41 строчку. Немного расточительно при каждом движении курсора мыши отправлять код с определением функций на страницу.
Пришлось перенести его в инициализацию библиотеки и в последствии там же и оставить.

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

Оказалось что Pepper flash (читай flash в Chrome) просто не получает событий передвижения колесика, если мы отключаем стандартную прокрутку с помощью event.preventDefault();

Решение оказалось простым до безумия — если браузер не хочет отправлять события о прокрутке — возьмем эту обязанность на себя.
Логика решения:

  1. Ловим событие прокрутки колесика мыши
  2. Отправляем детали события (дельта прокрутки) во Flash
  3. Блокируем прокрутку браузера
  4. Собираем событие прокрутки в flash с нуля
  5. Диспетчиризуем событие на сцену

А на сцене событие уже ловится подписанными на него компонентами.
Для отладки я использовал внешний .js скрипт, в конечной версии все функции js регистрируются на странице из Flash.
Отлично, приступим.
Основная часть работы возлагается на «внешний» код на странице:

var browserScrollAllow = true;            //По умолчанию разрешаем прокручивать страницу  /** * Функция вызывается один раз, регистрируем слушателей всевозможных событий прокрутки колесика */ function registerEventListeners() {     if (window.addEventListener) {         window.addEventListener('mousewheel', wheelHandler, true);         window.addEventListener('DOMMouseScroll', wheelHandler, true);         window.addEventListener('scroll', wheelHandler, true);     }     window.onmousewheel = wheelHandler;     document.onmousewheel = wheelHandler; }  /** * Наша основная функция, в которой ловим прокрутку и опционально заставляем flash  * сгенерировать свое событие прокрутки */ function wheelHandler(event) { 	var delta = event.wheelDeltaY;     if (!event) {         event = window.event     }     if (!browserScrollAllow) {         if (window.chrome) {             document.getElementById('flashObject').scrollHappened(delta);         }         if (event.preventDefault) {             event.preventDefault();         } else {             event.returnValue = false;         }     } }  /** * Эту функцию вызываем из flash каждый раз, когда курсор мыши заходит на область flash или покидает ее */ function allowBrowserScroll(allow) {     browserScrollAllow = allow; } 

Ключевой момент в данном случае:
if (window.chrome) { document.getElementById('flashObject').scrollHappened(delta); }
Если мы имеем дело с Chrome, то уведомляем флешку о том, что произошла прокрутка и передаем значение перемещения.

Теперь то, что находится внутри flash (код обработчиков перемещения мыши и ухода курсора со сцены в данном случае особой значимости не несет и может быть опущен):

/** *Функция инициализации "библиотеки" */ function initialize(stage: Stage): void {     if (ExternalInterface.available) {         nativeStage = stage;         stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseOverStage); //Курсор мыши над сценой         stage.addEventListener(Event.MOUSE_LEAVE, mouseLeavesStage);       //Курсор мыши за пределами сцены         stage.addEventListener(MouseEvent.MOUSE_WHEEL, onMouseWheel); //Проверочная функция прокрутки колесиком          ExternalInterface.addCallback("scrollHappened", scrollHappened);            } else {         throw new UninitializedError(NO_EXTERNAL_INTERFACE_ERROR);     } }  /** * Функция диспетчиризации нового события прокрутки */ function scrollHappened(wheelDelta: Number): void {     nativeStage.dispatchEvent(new MouseEvent(MouseEvent.MOUSE_WHEEL,  											true,  											false,  											nativeStage.mouseX,  											nativeStage.mouseY,  											null,  											false,  											false,  											false,  											false,  											wheelDelta / 40)); } 

Таким образом мы получаем и обрабатываем событие прокрутки до того, как вызываем event.preventDefault();. Флеш теперь не чувствует себя обделенным и исправно обрабатывает свои же события.

Все?
Не совсем.

Чуть позже оказалось, что на Mac OS-x присутствует та же проблема. При этом уже не в Chrome а во всех браузерах по умолчанию. Тут уже не получилось бы отделаться простой проверкой на тип браузера.
На помощь пришла стандартная функция .os из библиотеки flash.system.Capabilities
Определяем, сидит ли пользователь с OS-X (AS3):

isMac = Capabilities.os.toLowerCase().indexOf("mac") != -1; 

Немного поправляем функцию регистрации слушателей, заставляем принимать булеву переменную isMac в качестве параметра (JS):

function registerEventListeners(inputIsMac) {     isMac = inputIsMac; } 

И добавляем дополнительное условие для диспетчиризации собственного события:

if (window.chrome || isMac) {      document.getElementById('flashObject').scrollHappened(delta); } 

В итоге, если немного отполировать код(добавить весь JS код в AS3 в виде строковой величины и вызывать с помощью ExternalInterface.call("eval",...), и добавить входной параметр — flashID для большей гибкости), получим работоспособную библиотеку, полноценного аналога которой я почему-то не смог найти на просторах интернета

ссылка на оригинал статьи http://habrahabr.ru/post/188366/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *