Email Updates RSS Subscribe
Line

This blog is created and maintained by the technical team at Hook in an effort to preserve and share the insights and experience gained during the research and testing phases of our development process. Often, much of this information is lost or hidden once a project is completed. These articles aim to revisit, expand and/or review the concepts that seem worth exploring further. The site also serves as a platform for releasing tools developed internally to help streamline ad development.

Launch
Line

Hook is a digital production company that develops interactive content for industry leading agencies and their brands. For more information visit www.byhook.com.

Line

Flash Mouse Wheel Support

Line
Posted on April 9th, 2010 by Jake
Line

Ah, the mouse scroll wheel. It used to be so simple, and so obvious. Couldn’t we have just left it alone, and come to some gentleman’s agreement on how the browsers notify your objects of what the user just did with the wheel?

Sadly however, the reality is that this is not the case. Different browsers fire the event in different manners and with different deltas. Whats worse, is depending on the hardware used to “scroll” with, the deltas can be seriously varied. For instance Safari 531.21.8 on windows has a raw delta value of 120, while on the mac it has a raw delta value of 12. However if you scroll fast enough, that delta value can reach a pretty high number. Whats worse, is that is you have a multi-touch track pad on your Mac or a “Magic Mouse”, the numbers are again different.

Additionally Safari will fire at least two events for every wheel notch, while FireFox will only fire one. Add in the fact that when embedding a flash object, the wmode setting has a major impact on how the flash object is drawn and how the events are handled, we have an unmanageable matrix of possible hardware/software/implementation combinations.

With just flash alone, there isn’t much you can do about even getting those scroll events in certain wmodes, and if you do, its impossible to know what kind of numbers you are going to be getting.

So in comes JavaScript to the rescue. We can set up Javascript listeners and force that to call methods in the swf. This is starting to sound better, but you still run into issues with deployment. Depending on the service used to distribute your swf, say an ad banner distribution company, you won’t be able to put custom Javascript in the html. This is where the Javascript Injection technique becomes the weapon of choice.

Download Classes Here.

Get Adobe Flash player

First the demo. If you scroll with a mouse wheel, or a multitouch pad, or whatever you have that triggers scroll events in the browser, you will see the swf react. The list of deltas represents what was sent to flash from the browser. The event count is the total number of events fired so far. The “Use Raw Values” and “Use Scaled Values” buttons will send the swf either the browsers delta values, or a scaled version of the delta number based on the rules of the script. The text field at the bottom will show you what browser information it has detected. Lastly you can click “Clear” to clear out the old delta values from the list.

If you view the source on the page, you will not see any code related to handling the flash scrolling. That is because it was injected into browser memory after the page has been rendered. When looking for ways to do this, we came across two very good resources:
http://blog.earthbrowser.com/2009/01/simple-solution-for-mousewheel-events.html
and
http://www.actionscript.org/resources/articles/745/1/JavaScript-and-VBScript-Injection-in-ActionScript-3/Page1.html

Much of the code we are using came from the Earth Browser blog, but we added some safety checking, and a few other niceties to make it a littler easier to implement and use.

Setup for this is very easy. Simply include this line somewhere early in your swf startup:

MouseWheelEnabler.init(stage);

Be sure to include the import:

import com.jac.mouse.MouseWheelEnabler;

That’s all there is to it. This should work on different browsers, platforms, and window modes. The MouseWheelEnabler will do the rest. When it gets a call from the Javascript, it builds a new mouse event, and dispatches it from whatever flash element the mouse is over at the time.

There are two parts to the actual injection, that I will mention briefly. The first is the registerJS() method in MouseWheelEnabler.as:

private static function registerJS() : void
        {
            if( ExternalInterface.available )
            {
                var id:String = 'mws_' + Math.floor(Math.random()*1000000);
                ExternalInterface.addCallback(id, function():void{});
                ExternalInterface.call(MouseWheelEnabler_JavaScript.CODE);
                ExternalInterface.call("mws.InitMouseWheelSupport", id);
                ExternalInterface.addCallback('externalMouseEvent', handleExternalMouseEvent);
            }
        }

This is called from the init() method mentioned earlier. The first thing this does is make a new “class” and adds it to the DOM by using the addCallback() method. The only real reason this is required is so that we can identify the swf that has been embeded. Once we can find the swf, we can check the properties on the swf to get info like which wmode (if any) is in use.

Next ExternalInterface.call() is used to do the actual “injection”. This is essentially adding all of the Javascript that manages the events at the browser level to the current DOM. If you look at the bottom of the MouseWheelEnabler.as file, there is a class defined called MouseWheelEnabler_JavaScript. In it is simply a constant that has an HTML <script> tag that contains all of the JS code. When this is executed with the call() method, it defines an anonymous function, then creates a new namespace. This namespace is how we talk to the newly defined JS methods and properties.

So what kinds of things did we add that can make your life easier? Well a few things, such as the BrowserInfo object, the userRawValues flag, and the eventTimeout.

First up, the BrowserInfo object. This bit of code is used to fill in the “Browser Info Detected” portion of the demo:

var browserInfo:BrowserInfo = MouseWheelEnabler.getBrowserInfo();
if (browserInfo)
{//show
      _detectedText.text = (browserInfo.browser + " / " + browserInfo.platform + " / " + browserInfo.version);
}//show

When MouseWheelEnabler.getBrowserInfo() is called, it talks to the browser through the injected JS and gets back a few different objects that contain info about the browser. These objects are taken, broken down, and then stored in a brand new BrowserInfo object for easy consumption. BrowserInfo has a three properties and a bunch of public constants. The properties are myInfoObject.platform, myInfoObject.browser, myInfoObject.version. These can be compared against the constants that are provided in the BrowserInfo class:

Next up is the useRawValues property on the MouseWheelEnabler class:

MouseWheelEnabler.useRawValues = true;

This will instruct the MouseWheelEnabler to grab the real value that was returned from the browser if set to true. If this is set to false, the JS will take the browser value, and divide it to scale it down to a smaller number. For instance it will take the 120 from safari and make it 10, or whatever you want the divisor to be, on a per browser basis. To change these divisors, see the if(event.wheelDelta) section of the injected JS. There are some example rules in there to demonstrate how to set divisors based on browsers.

Lastly is the eventTimeout property on the MouseWheelEnabler class.

//This is in milliseconds
//This is why you don't see multiple events firing in the demo above.
MouseWheelEnabler.eventTimeout = 50;

This is used to help reduce the number of events fired from the browser to one per wheel notch. Its basically an event throttling system. By default the timeout is set to 50 milliseconds, which seems to be long enough to skip the second events fired by some of the browser combination. If you set this to zero, you will get all of the events that are fired.

That pretty much does it for this round. Good luck, and as always let us know if you run into any issues, in the comment section below.

Line
60 Responses to “Flash Mouse Wheel Support”
Newer Comments »
  1. What an amazing job you guyz are doing . Keep up sharing good stuff .

  2. Bams says:

    thank you so much..
    sometimes it’s good to find a solution that works with just an imported class and one line of code.
    even if i don’t really understand all the javascript stuff.

    thanks again for sharing, it works like a charm and i definitely could never find this out without you

    cheers, from France

  3. Stan says:

    I changed the code a little bit so that I could make the event cancelable.

    My solution involved only changing a couple lines of codes and it works excellent:
    - In the MouseEvent constructor, I set cancelable to true
    - I let handleExternalMouseEvent return a boolean that indicates if the event was canceled or not (true if canceled or if not fired because the timer was still running)
    - In javascript, I made event.preventDefault / return false conditional

    It works like a charm, I wish I could submit the code somehow.

  4. Interesting post about The Power of Information Review launches
    Good day

  5. [...] Jake over at byHook posted some extremely handy classes which use javascript injection to pickup the js mousewheel events in the browser which are much more reliable. It then uses the info from these events to fire off a new mouseevent in Flash. [...]

  6. Jake says:

    @Pine, I think you are right about the DOM focus issue. I don’t have any time left to look into it right at this moment, but the JS needs a good rewrite for this, to make it more browser compatible. If you want to have a go at a fix, you will probably want to start by making sure that the initial mouse over is detected in the JS, if you don’t click on anything. The swf mouseOver/Out is what enables and disables the sending of the mouse wheel events to flash. Also be sure you are not using “window” as your wmode.

    Please let us know if you many any head way :)

  7. Pine says:

    Jake,

    Thanks so much for your efforts! It is awesome and really easy to use.

    While I just came up with a small issue, while using Mac + FF/Chrome. (It almost works perfectly with safari and IE)
    The issue is:
    I use the scroll bar in one window inside of my flash app, and for the first time I open it, the scroll bar doesn’t work (for sure because I cannot catch the delta value). I have to click on some other part of the web page and then move the mouse over on the window, and it will work, and almost perfectly from then.

    I believe it’s something with the focusing (on the DOM objects), right? But I have almost 0 experience with js, I don’t think I can find the problem.

    Do you have any idea about how could this happen?

  8. Jake says:

    @Filip, awesome! The reason that its not working in IE is because events are handled differently in IE. I believe its something like event.cancelBubble = true; for IE and stopPropagation() for the other W3C compliant browsers…

  9. Filip says:

    Update: I got it working in FF on PC by adding the line event.stopPropagation();
    to your code so that it says:

    if(event.preventDefault)
    {
    event.preventDefault();
    event.stopPropagation();
    return false;
    }

    I have yet to make it work in IE 7 and 8 (PC). Thanks again for a great class!

  10. Filip says:

    Thanks for answering so fast! But the example swf on this page does not stop the page from scrolling when i try it on a PC (both chrome and Firefox). When I tried it on a Mac it works though (Chrome and Safari). Is this because there’s something wrong with mac scrollwheel support or vice versa?

    I think too that my code stops MouseWheelEnabler from getting the event. But maybe it’s possible to force only MouseWheelEnabler to get the scrollevent while stopping the browser from getting it?

  11. Jake says:

    @Filip, also in the example at the top of this post, if you mouse over the swf and then use the mouse wheel, the page shouldn’t scroll, so you shouldn’t need your code…

  12. Jake says:

    @Filip: I haven’t tried your code, but you could probably take out these two lines from MouseWheelEnabler.as:
    swf.onmouseover = mws.addScrollListeners();
    swf.onmouseout = mws.removeScrollListeners();

    and just add:
    mws.addScrollListeners()

    in their place… maybe…

    It also looks like your code is stopping the propagation of the mouse wheel event, which means the MouseWheelEnabler may never even get the event…

  13. Filip says:

    Thank you so much for this, it works excellent and allows me to run my flash content with wmode=opaque and still keep mousewheel scroll support! I have one question though: is it possible to disable the browser from scrolling the page when hovering over the embedded swf? For my project I am using a javascript that disables the mousewheel when the mouse is over the div sorrounding my embedded swf, but it conflicts with your code and totally disables scrolling. The js im using looks like this:

    function hookEvent(element, eventName, callback)
    {
    if (typeof(element) == “string”)
    element = document.getElementById(element);
    if (element == null)
    return;
    if (element.addEventListener)
    {
    if (eventName == ‘mousewheel’)
    element.addEventListener(‘DOMMouseScroll’, callback, false);
    element.addEventListener(eventName, callback, false);
    }
    else if (element.attachEvent())
    element.attachEvent(“on” + eventName, callback);
    }

    function unhookEvent(element, eventName, callback)
    {
    if (typeof(element) == “string”)
    element = document.getElementById(element);
    if (element == null)
    return;
    if (element.removeEventListener)
    {
    if (eventName == ‘mousewheel’)
    element.removeEventListener(‘DOMMouseScroll’, callback, false);
    element.removeEventListener(eventName, callback, false);
    }
    else if (element.detachEvent)
    element.detachEvent(“on” + eventName, callback);
    }

    function cancelEvent(e)
    {
    e = e ? e : window.event;
    if (e.stopPropagation)
    e.stopPropagation();
    if (e.preventDefault)
    e.preventDefault();
    e.cancelBubble = true;
    e.cancel = true;
    e.returnValue = false;
    return false;
    }
    hookEvent(‘SurroundingDiv’, ‘mousewheel’, cancelEvent);

    Maybe there’s some way to implement this in your code so that you could enable scrolling events in the swf but disabling it on the rest of the page?

  14. Edwin says:

    Thanks! Worked like a charm. Never knew about the javascript injection. Great read.

  15. Simon says:

    Hey Jake, jeah opaque works, but wmode doesnt. cause i want to use that nice class in my “full browser” site (100%w&h)

    but we came to the conclusion, that the swfobject, which is empting the flash, is the problem no? i tried several swf object versions with my knowhowitdoesnt, but no success :(

  16. Jake says:

    @AHernandez: I would have to see the project, off hand its tough to take a guess.. do you have a link to the project that exhibits the problem? Is it a full page swf (100%x100%) or just a portion embedded in the html?

  17. AHernandez says:

    I am trying to use this script but its not fixing the problem I’m having. Basically there is a bug in Firefox 3.6 when using wmode=opaque. In this case sometimes no scrollwheel events fire (about half the time it doesn’t work at all you have to refresh the page). Any clues how to fix this?

  18. jake says:

    Hey simon… what was the reason you neede window mode again? You said it was woking in opaque mode right?

  19. Simon says:

    Well i didnt success :D did u have time to check that problem out with window mode? cheeeeers

  20. Simon says:

    hehe, i’m so confused with that swfobject :)

    u too! cheers

  21. Jake says:

    ah… I have noticed that in “window” mode, flash gets the mousewheel event, then the js calls the external mousewheel event… so that is where the double up is coming from with the current code..

    So on mousewheel I would get:
    “Target: [object TextField] / [MouseEvent type="mouseWheel" bubbles=true cancelable=false eventPhase=3 localX=96 localY=33 stageX=485 stageY=65.35 relatedObject=null ctrlKey=false altKey=false shiftKey=false buttonDown=false delta=-3]

    And then the JS would fire:
    Over
    External: [object MovieClip]
    Target: [object MovieClip] / [MouseEvent type="mouseWheel" bubbles=true cancelable=false eventPhase=2 localX=100 localY=14 stageX=451 stageY=26 relatedObject=null ctrlKey=false altKey=false shiftKey=false buttonDown=false delta=-3]”

    Which is an interesting problem… for now, in the handler you could compare the event.target with what you want the wheel event to come from, and only run that code if the target matches..

    At some point I’ll have to see what I can do about detecting “window mode” and dealing with that differently…

    Good luck!

  22. Simon says:

    Ahhh sorry, my bad, with fullscreen i meant to scale the swf to 100%

  23. Jake says:

    To go full screen requires a user action, so if you make a button and do the following, it will go full screen. When I tested that, it seems to work ok still..

    _fullScreenBTN.addEventListener(MouseEvent.CLICK,handleFullScreenClick, false, 0, true);

    private function handleFullScreenClick(e:MouseEvent):void
    {
    stage.displayState = StageDisplayState.FULL_SCREEN;
    }

  24. Simon says:

    perhaps u know how to make it fullscreen? i cant find anything with swfobjt 2.2

  25. Jake says:

    Yeah, what seems to be happening is that the event target for the mouse events does not end up being the swf, but the containing div… so the actual handleExternalMouseEvent method isn’t being called… This intern is just calling flash’s mouse wheel events… which means there isn’t any of the filtering happening.. so the timeout doesn’t do anything..

    However using swfobject to do the embedding seems to get around the issue… are you using swfobject to embed the swf in the html page? Like the new example? When I switch it to window mode it seems to work fine here..

    since you are running full screen you can try going to line 288 in MouseWheelEnabler.as and changing it from if(mouseOver) to if(true) and force that code to always run…

    That might work for you…
    As always, let us know how it works out..

  26. Simon says:

    When i change

    params.wmode = “opaque”; to “window” I receive again the double :)

    Cause my SWF is fullscreen. There is the Problem i think.

  27. Simon says:

    whooo, great. I have to be thankful. Thanks jake.

  28. Jake says:

    Alrighty Simon, I think we’ve got it now :)
    http://labs.byhook.com/swfs/WheelExample.zip

    Turns out to be an issue with embedding the swf without the use of swfobject… I’ll see if I can find a workaround for it.. but for now, you can use the example above (same link as before, new code) that should get you all fixed up..

    I really appreciate the troubleshooting from your end!

  29. Simon says:

    Ok,

    PC 1: Win XP, FF=doesnt work
    PC 2: Win XP, Opera=worked
    PC 3: Win 7, FF=doesnt work
    PC 4: Win 7, Opera=worked

    but the Example on this site worked on every station. I also reinstalled FF, nothing happend.

  30. Simon says:

    Yes, each single tick shoot “neg neg” or “pos pos”.

    Got Windows 7 with a Logitech MX510 Mouse. I’m gonna test it now on each computer in the house and my ibook :)

  31. Jake says:

    Haha unfortunately I don’t have the issue here or even with your hosted example.. Which OS and Mouse are you using? And just so we are on the same page, each single tick of the wheel in the example would display “pos pos” or “neg neg” in the example correct?

    I tried it on windows and osx both with different versions of firefox and it seems to hold up ok… The timeout should take care of any double events especially if you have it set to 1000, which would wait a full second before looking for the next event… Do you happen to have another computer to try it on? Just trying to narrow down where the issue is…

  32. Simon says:

    Thanks for your effort again. Sadly it still doesnt work on FF. Getting the double event. So i guess for you it works? :)

    http://www.focusmode.ch/WheelExample/Example.html

  33. Jake says:

    Hmmm.. thats strange.. I made up a quick example here:
    http://labs.byhook.com/swfs/WheelExample.zip

    That is a fully working (yet simple) example of the mouse wheel support.
    See if that works for you, or exhibits the same behavior as yours..

    Let me know how that goes….

  34. Simon says:

    private function onMouseWheel( event:MouseEvent ):void {
    event.delta<0?circleMenu.next():circleMenu.prev();
    event.delta<0?circleMenuNum.next():circleMenuNum.prev();
    }

    this is my mousewheel function, but i dont think that this has any influence :)

  35. Simon says:

    Hi Jake, the error is gone!
    The scrolling problem is still there, strange. I changed the MouseWheelEnabler.eventTimeout to 1000, but always getting two events. Like i said, the demo on this site works fine. Phew :( Thanks for your kind help!

  36. Jake says:

    Hey Simon, thanks for the info… so what you need to do is go here:
    http://www.macromedia.com/support/documentation/en/flashplayer/help/settings_manager04.html

    That will bring up the Flash Player Settings Manager, then click the Always Allow radio button in the middle. Next click Edit Locations->Add Location and browse to your local swf that is giving you the error. Then you have to Click on one of the other tabs in the settings manager, and then back to the Security settings tab (this seems to be a bug where your new location isn’t saved unless you leave the tab).

    That should fix your security error, which just meant that a local swf was trying to access an html file. This won’t be an issue once deployed to a server.

    Then to fix the multiple events thing you will need to change the event throttling like so:
    MouseWheelEnabler.eventTimeout = 50;

    You will want to change that 50 to something bigger like 100 or even more. That is the number of milliseconds to ignore incoming events.

    Hopefully that will get you fixed up… Let us know how it goes!

  37. Simon says:

    http://www.focusmode.ch/error.jpg

    I try to translate as good as I can:

    Adobe Flash Player stopped a probably unsecure process

    The following file:
    /test.swf
    tries to communicate with the internet enabled memory location
    /test.html

    Click on EINSTELLUNGEN/Settings, that this application can communicate with the internet.

    The html is generated by flash

  38. Simon says:

    Hi, thanks for answering

    It just says, that Adobe Flash stopped an unsecure Prosess. The /test.swf cant communicate with the /test.html. Then I shold click on Settings to do that, but when I do so, nothing happens.

    No, on this page everything works perfect.

    FireFox version 3.6.3

    Adobe Flash Player version 10.0.45.2

    No, i dont have any add ons for that.

    Javascript is turned on.

    I test it localy, then i get the error at point 1. If i test it from the server, i dont get an error, but the scrolling behaviour is still wrong (2 times instead of 1)

    Cheers

  39. Jake says:

    @Simon: Yup, that should be it… I need to know a bit more about your issue though to help… sorry for all the questions :)

    What is the exact error you are getting?
    Are you getting that error on this page also?
    Which version of FireFox?
    Which version of Flash Player?
    Are you running any Script Blocking add ons for FF?
    Do you have javascript turned off?
    Are you testing it locally or from a hosted location on the web?

  40. Simon says:

    i mean, that should be all no?:

    import com.jac.mouse.MouseWheelEnabler;

    MouseWheelEnabler.init(stage);

  41. Simon says:

    hey, thats exactly what i need, but when i open the it in firefox/ie, there is an alert about flash security… dont understand what the problem is :(

  42. Alex says:

    Don’t worry about it, Jake. It just seems like Flash in Firefox calls mouse wheel event twice or so. MouseWheelEnabler.eventTimeout might be the solution. Gotta try it out.

  43. Jake says:

    Alex, I don’t think I know what you mean by jumps… the delta value is almost useless, but the sign on the delta value should be correct. That is really all you can use to determine “direction”… I don’t know of a better way to detect the actual “delta” if thats what you mean…

  44. Alex says:

    @Jake That’s the weird part. When detecting direction i.e. if(d > 0) i++ else if(d < 0) i– I get the same i -= 3 / i += 3 jumps depending on direction in Firefox . Is there any way to better detect direction?

  45. Jake says:

    Martin: Thanks!

    Karl: :)

    Alex: Well, its not really a “bug” per say… all browser/hardware combos report the delta differently. You can use the divisor setting (in the injected js) on a per browser basis to get things down to 1, but its not really going to work well… any change in hardware or the brower or even mouse wheel speed settings for the OS will effect this number. So, unfortunately the best we can hope to get is the direction, and then you can decide in flash how far to scroll things.

  46. Alex says:

    Nice post. But looks like it doesn’t fix Firefox mouse wheel bug. My results are: Delta = -1/1 for Chrome 4.1 and Delta = -3/3 for Firefox 3.6.3. I’ve got the same jumps without any JS too.

  47. oops I didn’t see the post above. lol
    Well +1 then. :)

    Karl

  48. There is also a flash native mouse wheel scroll script that is very good.

    http://blog.pixelbreaker.com/flash/swfmacmousewheel

    It can detect the mac mouse wheel and in turn detects windows as well.
    I used it in my project to create a zoom for project previews.
    Works great.

    Best,

    Karl DeSaulniers

  49. [...] This post was mentioned on Twitter by Martin R├Ądlinger. Martin R├Ądlinger said: Very smart class for Flash Mouse Wheel Support with JS injection http://bit.ly/9MN6aG [...]

  50. Martin says:

    wow, that works pretty well. Normally I use SWFMacMouseWheel but your class seems to be much smarter especially the JS injection. cheers Martin

Newer Comments »

Leave a Reply

*

Line
Line
Pony