Let’s say you are loading in an external asset that has an alpha channel, such as a PNG or a SWF*. Now let’s say that you want that asset to act like a button, with rollover and click actions. No problem. No reason you can’t do that.
But, actually, there is a problem. The hit area for that external asset will be its entire bounding box.
Take this octy image for example:

Even if you rolled over the top left of the image where it’s totally transparent, it will still trigger rollover actions, which is certainly not ideal and could potentially be very confusing for a user. This isn’t what happens if you have that image sitting in the library and you then add it to the stage at run time. But c’mon, we need it to work right with externally loaded assets! Of course I wouldn’t be writing this if I was just bitching and moaning. I’ve got a solution!
First, here’s the code in its entirety:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 | package { import flash.display.*; import flash.events.*; import flash.net.*; import flash.filters.*; public class Main extends Sprite { // this will hold the external asset and will act like a button public var s:Sprite public function Main() { init(); } private function init():void { var l:Loader = new Loader(); l.contentLoaderInfo.addEventListener(Event.INIT, assetLoaded); l.load(new URLRequest("octy.png")) } private function assetLoaded(e:Event):void { // copy the content of the loaded asset into a new BitmapData object var bmpData:BitmapData = new BitmapData(e.target.content.width, e.target.content.height, true, 0); bmpData.draw(e.target.content, null, null, null, null, true); var newBmp:Bitmap = new Bitmap(bmpData, "auto", true); s = new Sprite(); s.x = s.y = 50; s.addChild(newBmp); addChild(s); // make our Sprite act like a button s.addEventListener(MouseEvent.CLICK, clickHandler) s.addEventListener(MouseEvent.ROLL_OVER, rollOverHandler) s.addEventListener(MouseEvent.ROLL_OUT, rollOutHandler) s.buttonMode = true; } // when you roll over the Sprite, create a MOUSE_MOVE listener private function rollOverHandler(e:MouseEvent):void { s.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler) } // when you roll off of the Sprite, remove the MOUSE_MOUSE listener private function rollOutHandler(e:MouseEvent):void { s.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler) hideGlow(); } // constantly check the alpha value of the pixel under the mouse pointer private function mouseMoveHandler(e:MouseEvent):void { var av:int = getAlpha(s) // if the pixel under the mouse isn't transparent, // show the hand cursor and turn on the image's glow if (av > 0) { s.useHandCursor = true; showGlow() } // otherwise hide the hand cursor and turn off the glow else { s.useHandCursor = false; hideGlow() } } private function clickHandler(e:MouseEvent):void { var av:int = getAlpha(s) if (av > 0) { //do whatever it should do on click! } } private function showGlow():void { s.filters = [new GlowFilter()] } private function hideGlow():void { s.filters = [] } // a little helper function that returns the alpha value of // the pixel under the mouse pointer private function getAlpha($t:Sprite):int { var bmd:BitmapData = Bitmap($t.getChildAt(0)).bitmapData; var av:int = (bmd.getPixel32($t.mouseX, $t.mouseY)) >> 24 & 0xFF return av; } } } |
The idea is pretty simple. Since Flash isn’t smart enough to handle things the way we’d like, we’ll just have to do it ourselves. To do that we’ll use BitmapData.getPixel32() to find the alpha value of the pixel underneath the mouse whenever the mouse moves. If that pixel’s alpha value is 0, then we’ll act like we aren’t over the button. But if the alpha value is greater than 0, then we will act accordingly by showing the hand cursor and triggering any cool rollover effects (in this case just a simple Glow Filter).
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 | private function mouseMoveHandler(e:MouseEvent):void { var av:int = getAlpha(s) // if the pixel under the mouse isn't transparent, // show the hand cursor and turn on the image's glow if (av > 0) { s.useHandCursor = true; showGlow() } // otherwise hide the hand cursor and turn off the glow else { s.useHandCursor = false; hideGlow() } } |
But since you don’t want to waste CPU cycles by constantly checking the mouse position when you don’t need to, we’ll only enable that MOUSE_MOVE event listener when the mouse is over the bounding box of the externally loaded asset.
45 46 47 48 | private function rollOverHandler(e:MouseEvent):void { s.addEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler) } |
When you roll off of the bounding box, remove the listener.
51 52 53 54 55 | private function rollOutHandler(e:MouseEvent):void { s.removeEventListener(MouseEvent.MOUSE_MOVE, mouseMoveHandler) hideGlow(); } |
The same kind of logic is used for the CLICK event handler. You only actually do anything if the mouse isn’t over the transparent part of the image.
76 77 78 79 80 81 82 83 | private function clickHandler(e:MouseEvent):void { var av:int = getAlpha(s) if (av > 0) { //do whatever it should do on click! } } |
So that should give you a pretty good idea of how to do this in your own project where needed. Hopefully someone in need finds this post and saves themselves a lot of effort!
*A word on SWFs as images
SWFs are a great alternative to PNGs if you need external images to have nice alpha channels. By using a tool like Fireworks you can open a PNG with a nice alpha channel and then save it as a SWF. This is great because by saving the PNG as a SWF, you keep the alpha channel AND get JPEG compression which will give you MUCH smaller file sizes. The PNG to SWF conversion is an extra step (why why why can’t you just save to SWF direct from Photoshop?) but it might be worth it depending on your situation.
9 Comments
Cool article! But if you found any way to skip the event capture for transparent region ? for example, below the octy image, imagine a sea shell background. If user click on the background image, it should output “clicked on bg” and if user click on octy, then it should output “clicked on octy”. With above way, we can easily track if user clicked on non transparent area of octy (really a nice job!), but when user click on transparent area of octy, we can’t get mouse event on movie clip below that octy (that is sea shell).
Good point! I’m sure there’s got to be a way to build another hack on top of this hack. :) I might look into it if I find the time. Or maybe someone else out there has a fix up their sleeve!
Erik
I would think that using something like this in combination with DisplayObjectContainer.getObjectsUnderPoint() would help solve the issue of accessing items under the transparent parts of an image. It would probably be a it messy, though.
When the time comes to solve that problem, that sounds like a good method to investigate.
Thanks :)
Erik
Hi Erik, hope you are doing well. Like yedda mentioned one way to do this can be a wrapper function which get object under point and find the next immediate object listening mouse click event with no transparent area and then we manually dispatch click event for that object. But since all points used in getObjectsUnderPoint are assumed to be in global coordinate space, some sort of hack will be required to convert mouse event location into local coordinate space of that object.
Hi Erik,
Great post, just what I was looking for! However, I can’t seem to get it working. I’m not really an expert on external classes etc. So I was wundering if you could be so kind to provide me with a working example…. If not, that’s fine too, then I will have to figure out a workaround. Thanks in advance!
Best, Toine
What about using the InteractivePNG library ?
http://blog.mosessupposes.com/?p=40
how about creating a bitmap using threshold(), then using that bitmap as the mouse over area? would that work or would it just use the rect of the bitmap instead of it’s visible alpha channels?
Thanks a lot, this is very helpful indeed, simple but yet powerful, i was wandering if there was a way to obtain a “border” or a sprite from the image so you wouldn’t have to check every time for the alpha channel.