Mac OS X Mouse Wheel Support for ActionScript 3 Flash Applications (v.2+)
Posted on April 26, 2008
Filed under ActionScript 3, Featured, Flex, Mac, Programming
So I’ve finally updated the solution I’ve made earlier for enabling Mac OS X mouse wheel support in Flex applications to a second version. I didn’t want to continue adding stuff into the original post, so I decided to write a separate post just for this new version. As you can see from the title, this version should work in any Flash project you’re writing in ActionScript 3, as opposed to just in Flex projects. This change was contributed by Pavel Fljot, and all the other stuff I’ve added since have been added on top of that. Deployment should now be a lot easier and some features that were missing in the first version have also been added.
Changelog
First, here is an itemized list of what has changed:
- Flex dependencies have been removed, which makes it possible to use this in pure AS3 projects as well
- States of modifier keys (i.e. ctrl (or command in OS X,) alt, shift) are supported
- The initialization of the code on the JavaScript end requires much less effort
- An another method of determining which object on the Stage should dispatch the mouse wheel event has been added
- Is enabled for Safari for Windows
- Can be enabled for any number of different Flash objects on the same page
- The JavaScript code is encapsulated inside one top-level function
- The initialization state of the mouse wheel support is exposed
- The concept of “registering and unregistering objects” for mouse wheel support within AS3 code has been removed
This changes the way the support is initialized in AS3, but more about that a bit later. Courtesy of Pavel Fljot.
These can be accessed via the ctrlKey, altKey and shiftKey properties in the MouseEvent object, as usual.
You basically just have to include the one JavaScript file on the page and that’s it.
Read more about this below. Courtesy of a similar solution from Gabriel Bucknall.
For some mysterious reason the mouse wheel won’t work in Flash when accessed with the Windows version of Safari, so this solution will now enable itself for that browser. Courtesy of Pavel Fljot.
As long as the JavaScript file is linked on the page and the Flash apps have initialized the AS3 end of the code, they should be supported.
This is a much more elegant solution on the JavaScript end. Courtesy of a similar solution from Gabriel Bucknall.
You can read the value of the “initialized” property (or bind to it) to see if the mouse wheel support has been initialized.
This concept is no longer useful, given the new implementation.
Determining the InteractiveObject to dispatch the mouse wheel event
Like mentioned in the changelog above, I’ve added an another method for finding the InteractiveObject on stage and under the mouse cursor that should dispatch the mouse wheel event when one is sent from the JavaScript end. This is based on the solution to this same issue Gabriel Bucknall has come up with at pixelbreaker.com. My solution basically went through the whole display list hierarcy every time a mouse wheel event had to be dispatched, trying to find mouse-enabled InteractiveObjects that were under the mouse cursor, and then asked the topmost of these to dispatch the MouseEvent. Gabriel’s solution was to attach a MouseEvent.MOUSE_MOVE event listener to the Stage and keep a reference to the object that was the target for the last such event, and then use that reference to ask this object to dispatch the mouse wheel event whenever required.
Now, the main difference between these two solutions, I’ve found, is that Gabriel’s is faster but inaccurate in some border cases, while mine is slower but more accurate. The slowness of my solution is very clear if you understand the amount of processing that goes on whenever the user starts rolling the wheel: for each event, the application needs to go through the whole hierarchy of objects on the Stage, find InteractiveObjects that are under the mouse cursor, mouse-enabled and visible, and then find the one of those that is deepest in the display list. Gabriel’s solution doesn’t need to do any of this: it already has the InteractiveObject reference it wants to use at this point. The inaccuracy, on the other hand, stems from the fact that if the user doesn’t move their mouse, the reference won’t get updated. What this means is that if the InteractiveObjects on Stage have moved (in relation to the mouse cursor,) or been added to or removed from the display list while the cursor has stayed put, the reference will not be up to date, and probably incorrect.
As you’ve probably deduced from the last paragraph, there are upsides and downsides to both of these approaches, and I at least would like to be able to choose which one to use, based on the context of what I happen to be doing at the moment. This is why I’ve added this exact feature into the code in the form of a property the value of which you can set at will:
private var _mwSupport:ExternalMouseWheelSupport = ExternalMouseWheelSupport.getInstance(stage); _mwSupport.dispatchingObjectDeterminationMethod = ExternalMouseWheelSupport.COPY_MOUSEMOVE_EVENTS; // or _mwSupport.dispatchingObjectDeterminationMethod = ExternalMouseWheelSupport.TRAVERSE_DISPLAY_LIST;
You can test the differences between these two options with the demo.
Deployment and use
So like I wrote before, the process of deploying and initializing this solution has changed a bit from the last version. This is how you do it now.
- Include the JavaScript file onto the page that embeds the Flash object(s)
- Make sure your embedded Flash objects have a parent div element
- In your AS3 code, get a reference to the singleton instance of ExternalMouseWheelSupport in at least one place
<!-- add Flash object's <object> and <embed> tags here -->
</div>
As you can see, the singleton instance accessor is not a property anymore, but a method, so that you can pass a reference to the Stage to it. This change is because of the removal of dependencies to Flex libraries, which could be used to access the Stage instance in the previous version.
import org.hasseg.externalMouseWheel.ExternalMouseWheelSupport; private var _mwSupport:ExternalMouseWheelSupport = ExternalMouseWheelSupport.getInstance(stage);
I generally do this in the applicationComplete event handler, but you can of course call it wherever you want, as long as you make sure the Stage reference you’re using is not null (an Error will be thrown if it is.)
…and that’s it. :)
Downloads and demo
This code is licensed under the MIT License.
Other info
I’ve tested this solution with the following browsers (sadly, Opera on the Mac isn’t supported since ExternalInterface doesn’t seem to work in the latest version:)
- On OS X 10.5.2:
- Safari 3.1.1
- Firefox 3 beta 5
- Camino 1.6
- OmniWeb 5.7
- On Windows XP SP2:
- Internet Explorer 7.0
- Firefox 2.0
- Safari 3.1.1
- Opera 9.27
If someone could please test this on the Mac OS version of Firefox 2 and the Windows versions of Firefox 3 beta and IE 6, I’d very much appreciate it.
Comments
61 Responses to “Mac OS X Mouse Wheel Support for ActionScript 3 Flash Applications (v.2+)”
[...] OSX (Tiger +) desktops for Flex projects. Flex doesn’t support it natively. Finally got this interesting post and a utility which will do it for not just Flex, but any projects which is made of AS3. [...]
just need to find a way to make the wheel support work when i set wmode to transparent when using firefox then would be perfect!
[...] pas sur Mac ! Il a donc fallu utiliser un bon vieux hack ! L’explication du hack se trouve sur ce blog. En résumé, il faut, d’une part, importer une librairie dans le fichier Flash et [...]
I Don’t have Flash CS3 so I can’t be sure — maybe someone else who is reading this and who has the Flash authoring software could help you out better, but I’ll do my best:
The initialization of the mouse wheel support should work exactly the same as in step three of “deployment and use”. The only thing I can’t say is where to put this code in pure AS3 projects, though. It should be ran when the application initializes and the stage reference is available.
I’m currently using this with swfobject 2.0 in the main app I’m working with at work and no extra effort is required to make it work.
Just import the JavaScripts:
<script type="text/javascript" src="js/extMouseWheel.js"></script>
And then, when you use swfobject to embed your .swf, it runs, gets initialized, and if everything has been implemented on the Flash side (in this .swf you’re embedding,) it should work.
in the head tag:
var flashvars = {};
var params = {};
var attributes = {};
swfobject.embedSWF(“mwdemo.swf”, “myAlternativeContent”, “600″, “600″, “9.0.0″, false, flashvars, params, attributes);
in the body tag:
Make sure you have a parent DIV element for whichever temporary element you’re replacing with swfobject (id==”myAlternativeContent”). Like in point two under “deployment and use”.
Good point about the code tag — what an idiot I am, I’ve forgotten to add a note about that below the reply textarea. Well, I just added it there so see below for more info.
in the header tag:
<script type="text/javascript" src="extMouseWheel.js"></script>
<script type="text/javascript">
var flashvars = {};
var params = {};
var attributes = {};
swfobject.embedSWF("mwdemo.swf", "myAlternativeContent", "600", "600", "9.0.0", false, flashvars, params, attributes);
</script>
and in the body tag i have this:
<div id="myAlternativeContent">
<a href="http://www.adobe.com/go/getflashplayer">
<img src="http://www.adobe.com/images/shared/download_buttons/get_flash_player.gif" alt="Get Adobe Flash player" />
</a>
</div>
</div>
Everything there looks fine to me, I can’t see any obvious problems. I think we should continue this conversation via email — I’ll send you the wrapper HTML (with the relevant javascript) I’m using so you can check if any differences between the two will guide you to the right direction.
I started to use the solution, but was not able to deploy it successfully. This is what I had done:
1. Put in the JS and also the location to the JS my HTML template
2. I put a div around the object as:
3. I put in the ActionScript in my main application MXML.
The wheel support does not work. When I started to debug the code, i noticed that in the function “initJSConnection”, i get a false for “_initialized = ExternalInterface.call(“extMouseWheel.initCaptureFor”, ExternalInterface.objectID);”.
Can you help me on this one.
Thanks in advance
A quote from the API docs of ExternalInterface:
“For local content running in a browser, calls to the ExternalInterface.call() method are permitted only if the SWF file and the containing web page (if there is one) are in the local-trusted security sandbox. Also, you can prevent a SWF file from using this method by setting the allowNetworking parameter of the object and embed tags in the HTML page that contains the SWF content.”
from: http://livedocs.adobe.com/flash/9.0/ActionScriptLangRefV3/flash/external/ExternalInterface.html#call()
So first I’d check if any security sandbox features are blocking the ExternalInterface calls. The best way to do this is to use the Debug version of Flash Player, enable logging, try it out, and then check the log:
http://www.adobe.com/devnet/flex/articles/client_debug_print.html
Then I’d check the JavaScript log for errors. All modern browsers have this (develop > show error console in Safari, tools > error console in Firefox.)
Since extMouseWheel.initCaptureFor is returning false, though, instead of null, the problem is probably that the JavaScript can’t find your flash object’s parent div. Are you sure your flash object (the object and embed tags) has the “name” and “id” properties set (to the same value?) It has to, for this to work. Maybe I should add a mention of that into the post…
Let me know via email if it isn’t working.
1. I was using the HTML template that Flex provides with support enabled for history. The template has a tag for and . That was leading to rendering the parent “div” incorrectly and hence the script was breaking. I have not yet looked what else could be breaking – have that planned over next few days.
2. Also, I noticed that we need to write code to make scrolling working for individual controls like for a TextArea – we need to handle MouseEvent.MOUSE_WHEEL and then increment / decrement the “Vertical scroll”. This means that all over in my application, I will have write some snippets or override controls to provide this functionality. Is there not a better way of doing this?
2.1. I saw the version 1.x and found that there was a way to register controls that we needed to add to this. or is there a better way?
Good to hear it works for you now. I’ll try to answer the couple of questions you posed as best as I can:
2: Yes, that’s a known issue. The TextArea Flex class is a wrapper for an UITextField instance, which is a grandchild of TextField. TextField is a “native” (i.e. non-Flex) class (it’s in the package flash.text,) which makes its implementation of the mouse wheel functionality unknown (at least to me.) The ExternalMouseWheelSupport singleton will make the UITextFields wrapped by TextAreas dispatch MouseEvent.MOUSE_WHEEL events when you scroll the wheel on top of them but unfortunately it isn’t doing any good because apparently there’s no event listener listening for those evens and acting on them — the mouse wheel functionality seems to be implemented in some other (more native?) way in the TextArea class (or somewhere else.) If you look at the mx:TextArea source code, you’ll notice this:
* @private
* Mouse wheel scroll handler.
* TextField scrolls automatically so we don't need to handle this.
*/
override protected function mouseWheelHandler(event:MouseEvent):void
{
event.stopPropagation();
}
The solution I’d suggest for your situation, based on how it sounds from how you’re describing it, would be to create a subclass of mx:TextArea where you’d add your own mouse wheel handler and related functionality for initiating scrolling, and then use instances of this class instead of mx:TextArea within your app.
2.1: The concept of registering and unregistering objects for mouse wheel support is not relevant anymore in this version. Using these terms, you could say that all objects on stage are “registered”. So unless you’re using the older version, you can forget about this registration thingamabob entirely. :)
1: Any updates on this one?
I’m not sure what the question was for #1 ?
Also, it seems you had typed in some html tags, which were removed by the blogging software. You can use the cc tags described below to type in blocks of code, or you can type html tags inline into the comments like this:
<tag>hello</tag>
(output: <tag>hello</tag>)
First of all, great work!
I have the exact same problem Negro has, using swfObject. Any insights on that?
Thanks.
I Emailed Negro the example of how I’ve had this set up with swfobject 2.0 in a project of mine and he told me he got it working based on that. I’m not sure what the problem might be, but maybe it has something to do with the sizing of the flash object — I’ve been setting its width and height to 100% so that it would fill the whole container div, and it has worked for me without any problems so I haven’t investigated further. Here’s the example:
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
<title>example</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<script type="text/javascript" src="js/swfobject/swfobject.js"></script>
<script type="text/javascript" src="js/extMouseWheel.js"></script>
<script type="text/javascript">
var attributes = {
id: "xxxFlash",
name: "xxxFlash"
};
swfobject.embedSWF("xxx.swf", "myContent", "100%", "100%",
"9.0.0", null,null, attributes, attributes);
</script>
</head>
<body>
<div id="flashContainerDiv" style="width:100%;height:100%;">
<div id="myContent" style="width:100%;height:100%;">
<p>You need to install <a href="http://www.adobe.com/shockwave/download/download.cgi?P1_Prod_Version=ShockwaveFlash">Flash Player</a> (version 9 or later) in order to use xxx.</p>
</div>
</div>
</body>
</html>
If you figure out what the problem is and have any insights on how the JavaScript (and/or the ActionScript) could be modified to avoid it in the future, please do share.
{
//super.mouseWheelHandler( event );
if (verticalScrollBar && verticalScrollBar.visible)
{
var oldPosition:Number = verticalScrollPosition;
var newPos:int = verticalScrollPosition;
newPos -= event.delta * verticalScrollBar.lineScrollSize;
newPos = Math.max(0, Math.min(newPos, verticalScrollBar.maxScrollPosition));
verticalScrollPosition = newPos;
/* if (oldPosition != verticalScrollPosition)
{
var scrollEvent:ScrollEvent = new ScrollEvent(ScrollEvent.SCROLL);
scrollEvent.direction = ScrollEventDirection.VERTICAL;
scrollEvent.position = verticalScrollPosition;
scrollEvent.delta = verticalScrollPosition - oldPosition;
dispatchEvent( event );
} */
dispatchEvent( event );
}
No luck though so far. Anyone have any ideas?
Page scrolling happens when scrolling:
- While mouse is above any flash content without flash in actual focus.
- While mouse is above flash combobox or slider with flash in actual focus.
External mouse wheel support not initialized. This usually means that it is simply not needed (i.e. if running in Windows Flash Player in a browser other than Safari.)
Any help is appreciated. Thanks!
Thanks,
The first thing I would check is whether the Flash Player security sandbox features are blocking the ExternalInterface calls: if you’re running the app from the local filesystem, try setting the path as trusted in the Flash Player global security settings, or alternatively try running the app on a web server (localhost or remote).
I found this by googling for “externalinterface local filesystem”: http://www.judahfrangipane.com/blog/?p=215
after trying all day to get the mouse wheel to
work with my flex application, at last
it works!! Thank you very much for this solution.
I had the same problem as Pri, I was using the
standard html-template generated by Flex
and it didn’t work until I deleted the AC_OETags.js
-Script that is also embedded there that is used for
Version-detection etc.
There seems to be a conflict between those two scripts though
I am not sure why. Do you have any idea?
Cheers, Taro
I Don’t use Flex Builder myself (I just use the Flex SDK and edit my code with jEdit), so I can’t debug that kind of a problem very easily. I’ll try to see if I can figure out what the problem might be if I happen to have time in the near future, but no promises :/
If anyone else has a solution for this that entails changing the AS3/JS code I’ve posted here, please let me know.
in fact the solution is simple and logic. the class needs to be able to identify the flash object. this means you have to give your flash the same id and name attribute.
in swfobject 2 it looks like this:
var params = {};
var attributes = {};
attributes.id = "myName";
attributes.name = "myName";
swfobject.embedSWF("flash.swf", "myFlashContent", "800", "600", "9.0.0", false, flashvars, params, attributes);
besides that just follow the instroctions of alis docs (container div, etc).
i hope this helps everybody
The Windows version of Flash Player supports mouse wheel events out of the box, so this code won’t get initialized there because it won’t be needed. Whatever problem you might be having in FF3 on Windows probably stems from some other issue.
The trick is as follows….don’t just put a DIV around the OBJECT tag, because the object tag is within a NOSCRIPT tag and does not even get called unless javascript is disabled. Instead, put your div around the SCRIPT tag that programmatically embeds your SWF file.
It’s the script tag that has this line just inside it:
// Version check for the Flash Player …
You also have to be accessing your page via a web server, not as a local file, or you’ll get a security error.
[...] wonder why things like mouse wheel are not natively supported on the Mac. Fortunately there is a workaround but it’s still a [...]
Thanks,
Just put a tag like this:
<div id=”flashInit1″>
above the lines in the index.template.html file that say:
<script language=”JavaScript” type=”text/javascript”>
<!–
// Version check for the Flash Player that has the ability to start Player Product Install (6.0r65)
And put a closing:
</div>
tag after the end of the SCRIPT tag that starts as above.
I remembered that I also had to add the appropriate event listener for the objects that I wanted scrollable. In my case it’s an mx:TextArea called “console”. So I needed this code:
console.addEventListener(MouseEvent.MOUSE_WHEEL, function(event:MouseEvent):void {
if (console.verticalScrollPosition-event.delta >= 0) console.verticalScrollPosition -= event.delta;
});
For this you need to:
import flash.events.MouseEvent;
HTH
Dan
document.write(”);
AC_FL_RunContent(
“src”, “/swf/index”,
“FlashVars”, var1,
“width”, “100%”,
“height”, “100%”,
“align”, “middle”,
“id”, “flexObject”,
“quality”, “high”,
“bgcolor”, “#dddcd6″,
“name”, “flexObject”,
“allowScriptAccess”,”sameDomain”,
“type”, “application/x-shockwave-flash”,
“wmode”,”opaque”,
“pluginspage”, “http://www.adobe.com/go/getflashplayer”
);
document.write(”);
——-
thanks once again
Sudhan
[...] Continue reading at Hasseg blog . . . SHARETHIS.addEntry({ title: “Mac OS X Mouse Wheel Support for ActionScript 3 Flash Applications (v.2+) (Hasseg)”, url: “http://kelsocartography.com/blog/?p=1427″ }); [...]
[...] Luckily for us, Ali Rantakari has created a solution for this very problem. You can read up about the solution on his post. [...]
i have tested other sample flex apps on my computer and mouse-wheel events were never an issue. just with this one a big nothing.
your code now fixed that
thanks :)
[...] Mac OS X Mouse Wheel Support for ActionScript 3 Flash Applications [...]
http://bugs.adobe.com/jira/browse/FP-503
[...] Here’s a detailed blog post and solution to scrolling Flex components / application with mouse wheel – in all…. [...]
I was wondering if anyone has successfully managed to modify this code to work with the 2-dimensional scroll wheel on the Apple Mighty Mouse. In many Apple applications you can scroll horizontally as well as vertically.
Has anyone solved this shortcoming of the Hasseg scroll wheel software?
Ian
That’s probably due to the fact that some components implement scrolling by listening for MouseEvent.MOUSE_WHEEL events (these should work since what this hack does is make the components under the mouse dispatch these events) but others use some unknown (and proprietary to Adobe) method of “tapping into” the Flash Player mouse API or something (these won’t work). See my answer to Kapil’s question (#15) above.
I would hesitate to add any more stress on our memory “battle” – any comments or experience to share on impact to memory.
Anyone seen this?
I did find that the default behavior of the extMouseWheel.js didn’t fit to the progressive scrolling the piece we were working on needed (using tweening for scrolling and have a bit of inertial feel at the moment). This tracked to the setting in the js file var keepDeltaAtPlusMinusThree = true;
Once altered to false, I found that the delta would range as expected (at least in Safari 4 OS X) but for small moves often a delta of 0 would be returned to Flash. This would appear to be due to the rounding from the type conversion of the delta calculation for Safari and the integer values it is being passed as into Flash. Though I am sure this could be done more elegantly, we added a small alteration to the js file to ensure that small scrolling would not return as a 0 delta move:
{
// Safari
delta = event.wheelDelta/120;
if (window.opera) delta = -delta;
}
else if (event.detail) // Firefox
delta = -event.detail; //*3;
if (keepDeltaAtPlusMinusThree)
{
if (delta > 0) delta = 3;
else if (delta == 0) delta = 0;
else delta = -3;
} else {
if (delta < 1 && delta > 0) delta = 1;
else if (delta > -1 && delta < 0) delta = -1;
}
Also Ali, thanks for the guidance on SWFObject, and if its any help this tested out and works under the latest 2.2 release.
Best,
-Koof
I have been able to implement this hack without any hitches, but an issue I am right off the bat is the speed at which the mouse wheel is actually scrolling. It scrolls significantly slower and it seems to be a problem with Flex 4 cause I am not having this issue with your example. Any thoughts?
When I view the solution from the link above, it seems to work perfectly, however I downloaded the source and ran it on my box and it does not work :(
Is there a reason to this, am I doing something wrong, do I need to check / change anything ???
Cheers,
Jon.
You actually have to nest it in two DIVs, because for some reason SWFObject replaces the div you assign it to. Also, you have to name the swf in the attributes so it’s referencable by extMouseWheel.js.
Here’s the code:
…
<script type="text/javascript" src="extMouseWheel.js"></script>
<script language="JavaScript" type="text/javascript">
var flashvars = {};
var params = {};
var attributes = {
id: "myswf",
name: "myswf"
};
swfobject.embedSWF("${swf}.swf", "swfreplace", "${width}", "${height}", "10.0.0", "expressInstall.swf", flashvars, params, attributes);
</script>
…
And the HTML (hope this works)
…
<div id="swfcontainer" name="swfcontainer">
<div id="swfreplace"></div>
<p><a href="http://www.adobe.com/go/getflashplayer"><img src="http://www.adobe.com/images/shared/download_buttons/get_flash_player.gif" alt="Get Adobe Flash player" /></a></p>
</div>
</body>
…
<!–
Content on this page requires a newer version of Adobe Flash Player.
<!–
var gaJsHost = ((“https:” == document.location.protocol) ? “https://ssl.” : “http://www.”);
document.write(unescape(“%3Cscript src=’” + gaJsHost + “google-analytics.com/ga.js’ type=’text/javascript’%3E%3C/script%3E”));
try {
var pageTracker = _gat._getTracker(“UA-10307333-1″);
pageTracker._trackPageview();
} catch(err) {}
Use the cc tags (see below for an example) to post some HTML. Use “html” as the lang parameter.
Excellent work!! One question though: the scrolling speed is a bit slow. Is there a way to change the scroll amount/speed?
Mikko
You could set the keepDeltaAtPlusMinusThree boolean (in the javascript file) to false, maybe that’ll help. If not, you can play with the different multipliers that are applied to the delta value in the beginning of the onWheelHandler function.