Letexa

Learning & Teaching Exchange

Script: Building a Flash-based Image Viewer

Overview

This course explains the Flash Actionscript MovieClip-method startDrag(...) and its parameters. With this knowledge and some more we build a simple but nice image viewer, where you can zoom in and out and drag the visible part of the image around. It also includes a small Actionscript-generated preview thumbnail which shows where in the picture you are when you are zoomed in. Of course, the source code for this course and therefore for the image viewer MovieClip is available here! The code is Actionscript 2.0, but it is exactly the same when you use AS 3.0, only that some names have changed.

The Actionscript function MovieClip.startDrag()

Audio

We want to build a feature into our piece of Flash software that lets a user view objects that are larger than the available viewport.

S5: [0:0.750 show what I mean] For example, we may want to show large image files, or a preview for printing. A user does not just want an overview, they want to be able to zoom into some detail and examine it.

So we need to provide two functions: the ability to S10: [0:22.900 show text points] zoom inand out of the object, and the ability to moveobject around on the screen while zoomed in.

Let us think about how to do this now. We are going to write the code in Flash's Actionscript but that really doesn't matter, the general considerations are independent of any concrete implementation.

S15: [0:33.100 show next text points] Flash has an easy way to implement zooming, and that is the _xscale and _yscale properties of a MovieClip. It also has the infrastructure for the object dragging already in place with the MovieClip method startDrag().

S20: [0:45.500 point out zoom buttons] The implementation of zooming is easy. Simply create any two buttons for zooming in and out and attach event handlers to them that add to and subtract from the x- and yscale values of the MovieClip, respectively. The default value is 100.

The dragging seems to be simple too.

S25: [1:03.300 show simple dragging functions] What we have to do to get this working at all is not more than for the zoom buttons. Two very short event handling functions for the MovieClip object that we would like to drag are enough, one to start dragging when the user starts pressing the left mouse button down and one to stop it when the user releases the mouse button.

However, there is one difficulty, and that is that we allow the user too much. Right now they can accidentally drag the object S30: [1:27.950 show move animation] completely outside the viewable area! We should make sure the image can only be dragged around so that a part of it always remains on screen. Fortunately S35: [1:37.600 show function] the MovieClip method startDrag() already provides for this and accepts parameters to constrain the movement to an area. The question that seems so simple is: what area?

1st test case

Scase1: [1:49.100 show test case 1] I have prepared a test case. I would like to constrain movement of the blue box in the middle to the white area. The blue box is a MovieClip object and I have give it the id dragMC.

Scase1_1: [2:01.150 show test case 1] To dragMC I have attached two event handlers. One is called when the user presses the left mouse button, the other one when that button is released again. By calling startDrag() I let the user move the object around. Try it now! As you can see the upper left constraint works fine, but the lower right constraint does not work as hoped. The object can almost completely be moved outside the white area, only its own upper-left-most origin is constrained from leaving the area!

When you are ready to move on click on the button Scase1_2: [2:33.000 show button] labeled "Continue"! Scase1_3: [2:35.000 pause audio]

2nd test case

Scase2: [2:36.450 show test case 2] In this test case – which you can try for yourself while I'm speaking – I have added a small improvement. Now the blue box remains in the white area also when you try to move it to the lower right corner.

Scase2_1: [2:47.850 show code] As you can see, this time we have taken the size of the blue box into account. That means the constraint area is now the white area MINUS Scase2_2: [2.55.540 show real constraint area] the blue box width and height.

When you are ready to move on click on the button Scase2_3: [3:00.000 show button] labeled "Continue"! Scase2_4: [3:01.400 pause audio]

3rd test case

Scase3: [3:02.800 show test case 3] The next test case shows what happens when the object the user drags around is larger than the viewport, which is almost always true when we build an image viewer. Go ahead and move the blue box!

[Pause 4s]

It works! But the values are all messed up? Well, that is because of how the constraining rectangle is defined.

Scase3_1: [3:21.900 show rectangle] We can see that what is meant to be the bottom right corner of the constraining rectangle has now become the upper left corner instead!

Scase3_2: [3:29.600 show rectangle left top] Conversely, the top left corner is the bottom right corner now. This is an important feature of the startDrag function! If we specify the coordinates "upside down" it's still going to work.

When you are done playing click on the Scase3_3: [3:45.300 show button] "Continue" button. Scase3_4: [3:46.550 pause audio]

S500: [3:47.800] Now you know how to use the constraining parameters for the MovieClip function. What remains is to show you the final result of our efforts – an image viewer MovieClip object.

As a bonus I added some Actionscript code that creates and displays a thumbnail overview of the image. Inside it shows a red rectangle when you see less than the whole picture, to mark the visible area. Try it. Check out the code. Have fun!

 

Actionscript 2.0 Code for the viewer

It is very easy to adapt it for AS 3.0. All elements must exist in stage (the graphical art- and framework and the viewed object itself with id="dragMC").

// viewer rectangle
x1 = 10;
y1 = 10;
x2 = 430;
y2 = 570;

// create small overview thumbnail image, then calculate its size...
duplicateMovieClip(dragMC, "overviewMC", this.getNextHighestDepth());

// ...target size: 120x120 or whatever is smaller (maintain aspect ratio)
var w:Number = overviewMC._width;
var h:Number = overviewMC._height;
var r:Number = w/h;    // ratio

// portrait or landscape?
if (w > h) {
	overviewMC._width  = 120;
	overviewMC._height = 120*r;
} else {
	overviewMC._width  = 120*r;
	overviewMC._height = 120;
}

// put the overview into the upper left corner
overviewMC._x = 20;
overviewMC._y = 20;

// add drop shadow
var dropShadowFilter = new flash.filters.DropShadowFilter();
var myFilters:Array = overviewMC.filters;
myFilters.push(dropShadowFilter);
overviewMC.filters = myFilters;


// create a black border around the overview thumbnail
overviewMC.createEmptyMovieClip("borderMC", overviewMC.getNextHighestDepth());
overviewMC.borderMC.beginFill();
overviewMC.borderMC.lineStyle(1,0x000000,100,false,"none","round","miter",1);
overviewMC.borderMC.moveTo(-1, -1);
overviewMC.borderMC.lineTo((overviewMC._width*100+2)/overviewMC._xscale, -1);
overviewMC.borderMC.lineTo((overviewMC._width*100+2)/overviewMC._xscale, (overviewMC._height*100+2)/overviewMC._yscale);
overviewMC.borderMC.lineTo(-1, (overviewMC._height*100+2)/overviewMC._yscale);
overviewMC.borderMC.lineTo(-1, -1);
overviewMC.borderMC.endFill();

// create a red frame and hide it, later used to show what section of the image we are viewing
overviewMC.createEmptyMovieClip("frameMC", overviewMC.getNextHighestDepth());
overviewMC.frameMC._visible = false;
overviewMC.frameMC.beginFill();
overviewMC.frameMC.lineStyle(1,0xff0000,100,false,"none","square","miter",1);
overviewMC.frameMC.moveTo(0,0);
overviewMC.frameMC.lineTo((overviewMC._width*100)/overviewMC._xscale, 0);
overviewMC.frameMC.lineTo((overviewMC._width*100)/overviewMC._xscale, (overviewMC._height*100)/overviewMC._yscale);
overviewMC.frameMC.lineTo(0, (overviewMC._height*100)/overviewMC._yscale);
overviewMC.frameMC.lineTo(0,0);
overviewMC.frameMC.endFill();


// make it draggable - within the viewing area!
dragMC.onMouseDown = function() {
	dragMC.startDrag(false, x1, y1, x2-dragMC._width, y2-dragMC._height);
	if (overviewMC.frameMC._visible) {
		this.onEnterFrame = function() {
			overviewMC.frameMC._x = (-1) * overviewMC._width  * dragMC._x / dragMC._width  * 100 / overviewMC._xscale;
			overviewMC.frameMC._y = (-1) * overviewMC._height * dragMC._y / dragMC._height * 100 / overviewMC._yscale;
		}
	}
}

dragMC.onMouseUp = function() {
	dragMC.stopDrag();
	delete this.onEnterFrame;
}

// zoom button event handlers
zoomInBtn.onPress = function() {
	// Set the maximum zoom level to 2 * object size.
	// Pixel objects like images: 1 point in the image = 2x2 pixels on screen
	// For vector data, e.g. for print-previews, the allowed zoom can be
	// higher! Adobe Acrobat (PDF) Reader zooms in to 1600(%)...
	if (dragMC._xscale < 200) {
		dragMC._xscale *= 1.1;
		dragMC._yscale *= 1.1;
		// calculate new size and position of red rectangle in the overview thumbnail
		overviewMC.frameMC._visible = true;  // always okay when zooming in
		overviewMC.frameMC._x = (-1) * overviewMC._width  * dragMC._x / dragMC._width  * 100 / overviewMC._xscale;
		overviewMC.frameMC._y = (-1) * overviewMC._height * dragMC._y / dragMC._height * 100 / overviewMC._yscale;
		overviewMC.frameMC._width  = overviewMC._width  * (x2-x1) / dragMC._width  * 100 / overviewMC._xscale;
		overviewMC.frameMC._height = overviewMC._height * (y2-y1) / dragMC._height * 100 / overviewMC._yscale;
	}
}
zoomOutBtn.onPress = function() {
	if (dragMC._width > x2-x1) {
		dragMC._xscale /= 1.1;
		dragMC._yscale /= 1.1;
		// if the image now fills less than the entire viewport, *make it* fill ALL the space.
		if (dragMC._width <= x2-x1) {
			// hide red frame in the thumbnail: we are fully zoomed out
			overviewMC.frameMC._visible = false;
			// assumption: aspect ratio of viewer area the same as for the viewed object
			dragMC._x = x1;
			dragMC._y = y1;
			dragMC._width  = x2-x1;
			dragMC._height = y2-y1;
		}
		// Special case: if we are near the right and/or bottom edges when zooming out,
		// the image may "retreat" and leave background showing. Shift the image by the
		// amount of now visible background to again fill all viewport space.
		var d = dragMC._width + dragMC._x;
		if (d < (x2-x1)) {
			dragMC._x += x2-d;
		}
		d = dragMC._height + dragMC._y;
		if (d < (y2-y1)) {
			dragMC._y += y2-d;
		}
		// recalculate the red frame's position and size
		overviewMC.frameMC._x = (-1) * overviewMC._width  * dragMC._x / dragMC._width  * 100 / overviewMC._xscale;
		overviewMC.frameMC._y = (-1) * overviewMC._height * dragMC._y / dragMC._height * 100 / overviewMC._yscale;
		overviewMC.frameMC._width  = overviewMC._width  * (x2-x1) / dragMC._width  * 100 / overviewMC._xscale;
		overviewMC.frameMC._height = overviewMC._height * (y2-y1) / dragMC._height * 100 / overviewMC._yscale;
	}
}