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;
        }
    }
}

2 Responses to “Zoom and Pan Image Display”

  1. Chris Says:

    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 Says:

    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.

Leave a Reply