Совсем не думал, что решая такой простой (как мне казалось) вопрос, придется здорово потрудиться и пораскинуть мозгами. Но в итоге получилась полноценная библиотека, которая адекватно работает со всеми видами flash — плагинов на странице (включая Pepper flash в Chrome, о котором позже).
Но обо всем по порядку.
Проблема одновременной прокрутки флеш контента и браузера существует давно, поэтому я был уверен, что есть готовое решение «из коробки».
Немного погуглив получаем желаемый результат — MouseWheelTrap. Библиотека отслеживает положение курсора, и если он находится над flash-контентом, отключает прокрутку в браузере, оставляя flash право единолично использованть ролик мыши. Скачиваем, подключаем, все работает.
Просто?
Отнюдь.
Первым неработоспособность блокировки заметил коллега — спросил, почему не работает скролл мышкой. Решил проверить на других машинах — проблема оказалась серьезней чем я думал сначала — на большинстве скролл так же не работал, причем только в Chrome.
Возникло два вопроса:
- Почему не работает готовая библиотека?
- Почему только в 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();
Решение оказалось простым до безумия — если браузер не хочет отправлять события о прокрутке — возьмем эту обязанность на себя.
Логика решения:
- Ловим событие прокрутки колесика мыши
- Отправляем детали события (дельта прокрутки) во Flash
- Блокируем прокрутку браузера
- Собираем событие прокрутки в flash с нуля
- Диспетчиризуем событие на сцену
А на сцене событие уже ловится подписанными на него компонентами.
Для отладки я использовал внешний .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
для большей гибкости), получим работоспособную библиотеку, полноценного аналога которой я почему-то не смог найти на просторах интернета
Ссылка на репозиторий GitHub