Совсем не думал, что решая такой простой (как мне казалось) вопрос, придется здорово потрудиться и пораскинуть мозгами. Но в итоге получилась полноценная библиотека, которая адекватно работает со всеми видами 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
Автор: KumoKairo