Flash image replacement!

Archonic Flash scroll bar

The Flash community is a generous one. So here’s my first small gift back to a community that has already given me quite a bit.

Scroll bars in Flash are largely taken for granted. Until you try to craft your own from scratch, you don’t realize the math and combination of mouse listeners that go into a seemingly simple but proper scroll bar. In frustration, some settle for an even more frustrating user experience with just arrows that scroll content a painfully small amount with each click. There is of course the Flash components scroll bar, but try to edit it’s look and feel and you’re presented with a limited and confusing interface. Although editing components in CS4 is notably easier than previous versions, I found a scroll bar class by fellow Flash blogger Kyle Brekke which did just what I needed it to. What I’ve added to Kyle’s framework is this:

  • A stage presence in form of a blue box. Resize this box (presence_mc) and the scroll bar will adopt the appropriate dimensions.
  • The container movie clip the scroll bar is dynamically added to allows you to use layers to cover and tween it’s properties. This is more adaptive than adding it to the stage.
  • Added scroll track click and hold functionality (more like conventional scroll bars).
  • No support for horizontal scrollbars. No one likes using them and it slims down the code.

A word of caution: If you’re using GreenSock’s TweenLite, make sure this scrollbar and your project use the same version.

Here’s the example:

You require Flash to view the Archonic Scroll bar example.

Get Adobe Flash player

And here’s the source:

/**
 * Archonic Customizable Scrollbar Component
 *
 * Derived from Kyle Brekke's Scrollbar
 * 	Derived from Li Jiansheng's Scollbar
 *
 * @author Joshua Mark
 * @date 31/07/2009
 * @version 1.0.0
 * @website http://www.Archonic.com
 */

package com.archonic.utils {

	import com.greensock.TweenLite;
	import flash.display.*;
	import flash.events.*;
	import flash.geom.Rectangle;
	import flash.geom.Point;

	public class scrollbar extends MovieClip {

		// Private Get/Set Type Constants
		private const XY:String = "xy";
		private const WH:String = "widthHeight";
		private const TB:String = "topBottom";

		// Speed of Content Scroll
		private const arrowMove:Number = 10;
		private const scrollSpeed:Number = 10;
		private const transSpeed:Number = 0.1;//seconds to tween

		// Assets used in Drawing the Class
		private var _arrowProgress:arrowProgress = new arrowProgress();
		private var _arrowRegress:arrowRegress = new arrowRegress();
		private var _scrollTrack:scrollTrack = new scrollTrack();
		private var _scrollButton:scrollButton = new scrollButton();

		// Standard Vars Set in Constructor
		private var _target:MovieClip;
		private var _stage:Stage;
		private var _container:MovieClip;

		// Numbers used in Calculating Slide Ratio
		private var trackTop:Number;
		private var dragBot:Number;
		private var trackRange:Number;
		private var slideRect:Rectangle;

		// Allow user to click and hold arrows and continue to scroll
		private var isUp:Boolean = false;
		private var isDown:Boolean = false;
		private var trackDown:Boolean = false;

		// Constructor
		public function scrollbar(s:Stage, c:MovieClip, target:MovieClip, maskWidth:Number, maskHeight:Number):void {
			_stage = s;
			_container = c;
			_target = target;			// Set the _target
			_target.startPoint = new Point(_target.x, _target.y);// Set the _target's original point

			// Set positions of the assets (into scrollbar form)
			initAssets();
			// Sets all listeners in one place
			initListeners();
			// Find the sliding range with which to create the ratio
			initTrack();
			// Draws and sets the mask
			initMask(maskWidth, maskHeight);
		}
		private function initAssets():void {
			var targetWidth = _container.presence_mc.width;
			var targetHeight = _container.presence_mc.height - (2*targetWidth);

			_scrollTrack.width = targetWidth;
			_scrollTrack.height = targetHeight;

			_scrollButton.width = targetWidth;
			_scrollButton.y += _scrollButton.height/2;//stops jumping when scrolling up with _scrollTrack

			_arrowRegress.height = targetWidth;
			_arrowRegress.width = targetWidth;
			_arrowRegress.y = _arrowRegress.height * -1;

			_arrowProgress.height = targetWidth;
			_arrowProgress.width = targetWidth;
			_arrowProgress.y += _scrollTrack.height;

			// Set arrows as buttons
			_arrowRegress.buttonMode = true;
			_arrowProgress.buttonMode = true;
			_scrollButton.buttonMode = true;

			// Add assets to this class
			addChild(_scrollTrack);
			addChild(_scrollButton);
			addChild(_arrowProgress);
			addChild(_arrowRegress);
			y = _container.presence_mc.y + _arrowRegress.height;

			// Add this class to the timeline container
			_container.addChild(this);
		}
		private function initListeners():void {
			// Event listeners for dragging and scroll wheel
			_scrollButton.addEventListener(MouseEvent.MOUSE_DOWN, dragScroll);
			_scrollButton.addEventListener(MouseEvent.MOUSE_OVER, stopTrackScroll);
			_stage.addEventListener(MouseEvent.MOUSE_UP, stopScroll);
			_stage.addEventListener(MouseEvent.MOUSE_WHEEL,mouseWheelHandler);

			// Event listeners for arrows
			_arrowRegress.addEventListener(Event.ENTER_FRAME, _arrowRegressHandler);
			_arrowRegress.addEventListener(MouseEvent.MOUSE_DOWN, upScroll);
			_arrowRegress.addEventListener(MouseEvent.MOUSE_UP, stopScroll);
			_arrowProgress.addEventListener(Event.ENTER_FRAME, _arrowProgressHandler);
			_arrowProgress.addEventListener(MouseEvent.MOUSE_DOWN, downScroll);
			_arrowProgress.addEventListener(MouseEvent.MOUSE_UP, stopScroll);

			// Event listeners for track
			_scrollTrack.addEventListener(Event.ENTER_FRAME, _trackHandler);
			_scrollTrack.addEventListener(MouseEvent.MOUSE_DOWN, trackScroll);
			_scrollTrack.addEventListener(MouseEvent.MOUSE_UP, stopScroll);
		}
		private function initTrack():void {
			trackTop = _get(XY, _scrollTrack) + (_scrollButton.height/2);
			trackRange = _get(WH, _scrollTrack) - _get(WH, _scrollButton);
			dragBot = trackTop + trackRange;

			slideRect = new Rectangle(0, trackTop, 0, trackRange);
		}
		private function initMask(w:Number, h:Number):void {
			if (_target.mask == null) {
				var square:Sprite = new Sprite();
				square.graphics.beginFill(0xFF0000);
				square.graphics.drawRect(_target.x, _target.y, w, h);
				_stage.addChild(square);
				_target.mask = square;
			}
		}

		// Next 4 functions deal with hitting the up/down arrows on the scrollbar
		private function upScroll(e:MouseEvent):void {
			isUp = true;
		}
		private function downScroll(e:MouseEvent):void {
			isDown = true;
		}
		private function _arrowRegressHandler(e:Event):void {
			if (isUp) {
				if ( _get(XY, _scrollButton) > trackTop ) {
					_set(XY, _scrollButton, _get(XY, _scrollButton) - arrowMove);
					if ( _get(XY, _scrollButton) < trackTop ) {
						_set(XY, _scrollButton, trackTop);
					}
					startScroll();
				}
			}
		}
		private function _arrowProgressHandler(e:Event):void {
			if (isDown) {
				if ( _get(XY, _scrollButton) < dragBot ) {
					_set(XY, _scrollButton, _get(XY, _scrollButton) + arrowMove);
					if ( _get(XY, _scrollButton) > dragBot ) {
						_set(XY, _scrollButton, dragBot);
					}
					startScroll();
				}
			}
		}
		private function _trackHandler(e:Event):void {
			if((isUp || isDown) && trackDown) {
				if(mouseY < _scrollButton.y) {
					_arrowRegressHandler(null);
				} else if (mouseY > _scrollButton.y) {
					_arrowProgressHandler(null);
				}
			}
		}
		private function trackScroll(e:MouseEvent):void
		{
			isUp = true;
			isDown = true;
			trackDown = true;
		}
		private function stopTrackScroll(e:MouseEvent):void
		{
			trackDown = false;
		}

		// Next 3 functions deal with dragging and mouse wheel
		public function dragScroll(e:MouseEvent):void {
			_scrollButton.startDrag(false, slideRect);
			stage.addEventListener(MouseEvent.MOUSE_MOVE, moveScroll);
		}
		public function mouseWheelHandler(e:MouseEvent):void {
			if (e.delta < 0) { //Scrolling Down
				if ( _get(XY, _scrollButton) < dragBot ) {
					_set(XY, _scrollButton, _get(XY, _scrollButton) - (e.delta * scrollSpeed));
					if ( _get(XY, _scrollButton) > dragBot ) {
						_set(XY, _scrollButton, dragBot);
					}
					startScroll();
				}
			} else { // Scrolling Up
				if ( _get(XY, _scrollButton) > trackTop ) {
					_set(XY, _scrollButton, _get(XY, _scrollButton) - (e.delta * scrollSpeed));
					if ( _get(XY, _scrollButton) < trackTop ) {
						_set(XY, _scrollButton, trackTop);
					}
					startScroll();
				}
			}
		}

		// Replicates the startScroll function except it works during the drag
		// because it runs off the MOUSE_MOVE event
		public function moveScroll(e:MouseEvent):void {
			startScroll();
		}
		private function stopScroll(e:MouseEvent):void {
			isDown = false;
			isUp = false;
			_scrollButton.stopDrag();

			stage.removeEventListener(MouseEvent.MOUSE_MOVE, moveScroll);
		}
		// Major controlling function
		private function startScroll():void {
			var ratio:Number = ( _get(WH, _target) - trackRange ) / trackRange;
			var sPos:Number =  ( (_get(XY, _scrollButton) - trackTop) * ratio ) - _get(XY, _target.startPoint);

			TweenLite.to( _target, transSpeed, {y:-sPos} );
		}

		// Functions used to minimize code switch for verticle/horizontal bars
		private function _set(type:String, targ:*, val:Number):void {
			if (type == XY) {
				targ.y = val;
			} else if (type == WH) {
				targ.height = val;
			} else if (type == TB) {
				targ.bottom = val;
			}
		}
		private function _get(type:String, targ:*):Number {
			if (type == XY) {
				return targ.y;
			} else if (type == WH) {
				return targ.height;
			} else if (type == TB) {
				return targ.bottom;
			}
			return 0;
		}
	}
}

Download the Archonic scrollbar

The license is MIT open source – use it, distribute it, edit it. Enjoy!

Posted on Sunday, July 5th, 2009 at 11:05 am and is filed under Flash. You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.

Leave a Reply