Zoom and Pan Image Display

I’ve done enough zooming image displays recently that I formalized my code base into a class abstract. I thought I’d post it in case it’s useful to others.

The broad overview is that ZoomImage is a display container that acts as a mask for a child image. By calling the “zoomIn()” and “zoomOut()” methods,  the child image will zoom within the frame created by ZoomImage‘s scrollRect. Once the image is zoomed in on, you can click and drag the zoomed image to pan it within the frame. ZoomImage takes care of all the math for zooming the image in and out of the center point of origin, and for restraining the image’s position to within the bounds of the frame.

This class does have one dependency on another code library: that being the awesome TweenLite AS3 library by GreenStock. If you’ve never used TweenLite before, then check it out. It’s extremely easy to use and is unbelievably handy! Just download and unpack TweenLite into your class library where ZoomImage can reference it.

Cheers!

ZoomImage.as

package
{
    import flash.display.Sprite;
    import flash.geom.Rectangle;
    import flash.events.MouseEvent;
    import gs.TweenLite;
    import gs.easing.*;

    /**
    * ZoomImage is a container for a zooming, panning image.
    * NOTE : this class has a dependency on the TweenLite AS3 library, available at:
    * http://blog.greensock.com/tweenliteas3/
    */
    public class ZoomImage extends Sprite
    {
        /** Minimum allowed scale value for the image. */
        public var minScale:Number = 1;

        /** Maximum allowed scale value for the image. */
        public var maxScale:Number = 4;

        /** The ammount that the image's scale is modified with each zoom in/out call.  */
        public var scaleIncrement:Number = 0.5;

        /** @private the cropped, zooming image. This MUST be a Sprite so that start/stop drag methods can be utilized. */
        private var _image:Sprite;

        public function ZoomImage($width:int=400, $height:int=300):void
        {
            super();
            scrollRect = new Rectangle(0, 0, $width, $height);
            addEventListener(MouseEvent.MOUSE_DOWN, this._onMousePress, false, 0, true);
            _image = _setImage();
        }

        /**
        * Creates the image asset (this can be customized as needed).
        * In this case, the demo image is being drawn as a vector.
        */
        private function _setImage():Sprite
        {
            // create an image display object.
            var $image:Sprite = new Sprite();

            // draw rectangle
            $image.graphics.beginFill(0xCCCCCC, 1);
            $image.graphics.drawRect(0, 0, displayWidth, displayHeight);
            $image.graphics.endFill();

            // draw circle
            $image.graphics.beginFill(0x999999, 1);
            $image.graphics.drawCircle(displayWidth/2, displayHeight/2, 30);
            $image.graphics.endFill();

            // draw X
            $image.graphics.lineStyle(1, 0x000000, 1);
            $image.graphics.moveTo(0, 0);
            $image.graphics.lineTo(displayWidth, displayHeight);
            $image.graphics.moveTo(0, displayHeight);
            $image.graphics.lineTo(displayWidth, 0);

            // add and return new image child.
            addChild($image);
            return $image;
        }

        /**
        * Specifies the width of the visible image area.
        */
        public function get displayWidth():int {
            return scrollRect.width;
        }

        /**
        * Specifies the height of the visible image area.
        */
        public function get displayHeight():int {
            return scrollRect.height;
        }

        /**
        * Zooms out from the image.
        */
        public function zoomIn():void {
            _zoom(1);
        }

        /**
        * Zooms in on the image.
        */
        public function zoomOut():void {
            _zoom(-1);
        }

        /**
        * @private
        * Zooms the image display.
        * @param $dir Direction of zoom, as expressed by a value of "1" (in) or "-1" (out)
        */
        private function _zoom($dir:Number):void
        {
            var $scale:Number = Math.max(minScale, Math.min(_image.scaleX + (scaleIncrement * $dir), maxScale));
            var $postW:int = (_image.width / _image.scaleX) * $scale;
            var $postH:int = (_image.height / _image.scaleY) * $scale;
            var $x:int = Math.min(0, Math.max(_image.x+((_image.width - $postW)/2), displayWidth-$postW));
            var $y:int = Math.min(0, Math.max(_image.y+((_image.height - $postH)/2), displayHeight-$postH));

            TweenLite.killTweensOf(_image);
            TweenLite.to(_image, 1, {scaleX:$scale, scaleY:$scale, x:$x, y:$y, ease:Strong.easeInOut});
        }

        /** @private called upon any mouseDown event on the image */
        function _onMousePress($event:MouseEvent):void {
            stage.addEventListener(MouseEvent.MOUSE_UP, this._onMouseRelease, false, 0, true);
            var $dx:int = _image.width - displayWidth;
            var $dy:int = _image.height - displayHeight;
            _image.startDrag(false, new Rectangle(-$dx, -$dy, $dx, $dy));
            cacheAsBitmap = true;
        }

        /** @private called upon any mouseUp event following a press on the image */
        function _onMouseRelease($event:MouseEvent):void {
            stage.removeEventListener(MouseEvent.MOUSE_UP, this._onMouseRelease);
            _image.stopDrag();
            cacheAsBitmap = false;
        }
    }
}
Advertisements

7 comments so far

  1. Chris on

    Very helpful… I was just about to start working on something similar for a project and this will give me a great base to start from. Thanks!

  2. Steve on

    Having a bit of trouble with this one.. and would like a clarification. Here is my code so far:

    Why is does the image get masked but none of the event handler’s fire off that technically should since its creating a ‘stage’ over the image with its own event handlers and listeners. Any clues? Or am I not understanding this. Any help with code of implementation appreciated.

  3. rye on

    Thanks for this, found this very helpful to start from. saved me a lot of time..!

  4. Lindsay on

    Hi!

    So if I import ZoomImage.as into the fla file how is the image created through the class or do I have to have an image in the library and add the child inside the scrollRect? Sorry I am new to AS3.

    Thanks

    • bigmac on

      To set a different image, you’d just reconfigure the Sprite that gets returned by the class’ _setImage() method. Right now that method creates a new Sprite and draws graphics into it. However, you may change that behavior and have it load an image instead, or instantiate an image from the SWF library and return that. Ultimately, it doesn’t matter what the _setImage() method does to generate the image, so long as it results in returning a valid Sprite object.

  5. Lindsay on

    Thanks bigmac…
    Although I can’t even get the original graphics to generate. Currently I have the .as file with actionscript above in it. I also have a empty .fla file, no actionscript and nothing in the library saved in the same place as the .as file. I export the .fla file and no graphics are generated. I also have downloaded the greensock-as3 folder with tweenlite in it. Is there something I need to do to connect these files so they all interact like they are supposed to?

  6. Prasad Shetty on

    Good one :)


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: