Scrollbar in ActionScript 3.0 AS3 | Light weight and simple without flex

I was annoyed that I have to rely on bloated Flex components to do simple stuff. I ported the kirupa AS2 scrollbar to AS3 and then added a few touches to make it a little more versatile. It is really just a starting point. For example, if you are using a very small scroll bar then when dragging too fast you can loose the focus on the scroll bar. Again, this is a simplified class for you to build on and to add features like the one I am suggesting. Enjoy!

package
{
import flash.display.InteractiveObject;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.events.MouseEvent;
import flash.geom.Rectangle;
public class ScrollBarLite {
//controlable assets
//the dragable rectange in the center of the scroll bar that also shows the user where in the scrolling they are.
private var scrollFace:Sprite;
//the top button at the top of the scroll bar that moves scrolls the view to the top of the page
private var btnUp:InteractiveObject;
//the bottom button at the bottom of the scroll bar that moves the view to the bottom of the page
private var btnDown:InteractiveObject;
//the content that is being scrolled
private var contentMain:Sprite;
//the viewport through which the content is being viewed
private var maskedView:Sprite;
//the scroll bar
private var scrollTrack:Sprite;
//the area where the mouse must be over to pick up MOUSE_WHEEL events.
private var _mouseWheelTarget:Sprite;
//the external function that gets called to do scrolling
private var _onCustomContentScrollProxy:Function;
//numbers
private var scrollHeight:Number;
private var contentHeight:Number;
private var scrollFaceHeight:Number;
private var maskHeight:Number;
private var initPosition:Number;
private var initContentPos:Number;
private var finalContentPos:Number;
private var left:Number;
private var top:Number;
private var right:Number;
private var bottom:Number;
private var dy:Number;
private var speed:Number;
private var moveVal:Number;
private var scrollWheelSpeed:Number = 20;
private var minScrollFaceHeight:Number = 10;
private var orgScrollFaceHeight:Number;
/**
* Control class for controlling scroll bar graphics
*
* @param scrollFaceParam the dragable rectange in the center of the scroll bar that also shows the user where in the scrolling they are.
* @param btnUpParam the top button at the top of the scroll bar that moves scrolls the view to the top of the page
* @param btnDownParam the bottom button at the bottom of the scroll bar that moves the view to the bottom of the page
* @param contentMainParam the content that is being scrolled
* @param maskedViewParam the viewport through which the content is being viewed
* @param scrollTrackParam the scroll bar
*
*/
public function ScrollBarLite(scrollFaceParam:Sprite, btnUpParam:InteractiveObject, btnDownParam:InteractiveObject, contentMainParam:Sprite, maskedViewParam:Sprite, scrollTrackParam:Sprite)
{
var scrollTrackRect:Rectangle;
this.scrollFace = scrollFaceParam;
this.btnUp = btnUpParam;
this.btnDown = btnDownParam;
this.contentMain = contentMainParam;
this.maskedView = maskedViewParam;
this.scrollTrack = scrollTrackParam;
scrollHeight = scrollTrack.height;
contentHeight = contentMain.height;
maskHeight = maskedView.height;
orgScrollFaceHeight = scrollFace.height;
if (contentHeight < maskHeight) {
scrollFace.visible = false;
btnUp.mouseEnabled = false;
btnDown.mouseEnabled = false;
} else {
scrollFace.visible = true;
btnUp.mouseEnabled = true;
btnDown.mouseEnabled = true;
//change the size of the scroll dragger aka scrollFace based on the size of the content vs the viewport
scrollFace.height = orgScrollFaceHeight*(maskHeight/contentHeight);
if (minScrollFaceHeight > scrollFace.height){
scrollFace.height = minScrollFaceHeight;
}
}
scrollFaceHeight = scrollFace.height;
initPosition = scrollFace.y = scrollTrack.y;
initContentPos = contentMain.y;
finalContentPos = maskHeight-contentHeight+initContentPos;
scrollTrackRect = scrollTrack.getBounds(scrollTrack.parent);
left = scrollTrackRect.x;
top = scrollTrack.y;
right = scrollTrackRect.x;
bottom = scrollTrack.height-scrollFaceHeight+scrollTrack.y;
dy = 0;
speed = 10;
moveVal = (contentHeight-maskHeight)/(scrollHeight-scrollFaceHeight);
//set up listeners
scrollFace.addEventListener(MouseEvent.MOUSE_DOWN, _onPressScrollFace);
scrollFace.addEventListener(MouseEvent.MOUSE_UP, _onMouseUpScrollFace);
btnUp.addEventListener(MouseEvent.MOUSE_DOWN, _onPressBtnUp);
btnUp.addEventListener(MouseEvent.MOUSE_OUT, _onDragOutBtnUp);
btnUp.addEventListener(MouseEvent.MOUSE_UP, _onUpBtnUp);
btnDown.addEventListener(MouseEvent.MOUSE_DOWN, _onPressBtnDown);
btnDown.addEventListener(MouseEvent.MOUSE_OUT, _onDragOutBtnDown);
btnDown.addEventListener(MouseEvent.MOUSE_UP, _onUpBtnDown);
//make sure scrollFace is centered over the scrollTrack, you may want to comment this out if you are doing something weird with your scrollFace graphics
scrollFace.x = scrollTrackRect.x+( (scrollTrack.width - scrollFace.width)/2 );
}
/**
* call this when ever the size of the content changes to that the scroll bar can update
* @param artificialHeight if you dont want to base the scroll bar on conent height and instead some other value
*
*/
public function onContentSizeChange(artificialHeight:Number = -1):void{
scrollHeight = scrollTrack.height;
if (artificialHeight != -1){
contentHeight = artificialHeight;
} else {
contentHeight = contentMain.height;
}
maskHeight = maskedView.height;
if (contentHeight < maskHeight) {
scrollFace.visible = false;
btnUp.mouseEnabled = false;
btnDown.mouseEnabled = false;
scrollFace.height = orgScrollFaceHeight;
scrollFace.y = scrollTrack.y;
contentMain.y = initContentPos;
_removeListener(_mouseWheelTarget, MouseEvent.MOUSE_WHEEL, _onMouseWheel);
_removeListener(_mouseWheelTarget, MouseEvent.MOUSE_OUT, _onMouseUpScrollFace);
} else {
scrollFace.visible = true;
btnUp.mouseEnabled = true;
btnDown.mouseEnabled = true;
//change the size of the scroll dragger aka scrollFace based on the size of the content vs the viewport
scrollFace.height = orgScrollFaceHeight*(maskHeight/contentHeight);
if (minScrollFaceHeight > scrollFace.height){
scrollFace.height = minScrollFaceHeight;
}
if ( !_mouseWheelTarget.hasEventListener(MouseEvent.MOUSE_WHEEL) ){
_mouseWheelTarget.addEventListener(MouseEvent.MOUSE_WHEEL, _onMouseWheel);
_mouseWheelTarget.addEventListener(MouseEvent.MOUSE_OUT, _onMouseUpScrollFace);
}
}
scrollFaceHeight = scrollFace.height;
bottom = scrollTrack.height-scrollFaceHeight+scrollTrack.y;
finalContentPos = maskHeight-contentHeight+initContentPos;
moveVal = (contentHeight-maskHeight)/(scrollHeight-scrollFaceHeight);
}
/**
* run this function if you want to enable the mouse wheel when the mouse is over the target area.
* @param target the area that picks up MOUSE_WHEEL events
*
*/
public function registerMouseWheelTarget(target:Sprite):void{
target.addEventListener(MouseEvent.MOUSE_WHEEL, _onMouseWheel);
target.addEventListener(MouseEvent.MOUSE_OUT, _onMouseUpScrollFace);
_mouseWheelTarget = target;
}
/**
* if you don't want the scroll bar to work by changing the y value of the content and instead do something else you can register your own handler
* @param callback should look something like the following onScrollCallBack(xLocOfContent:Number)
*
*/
public function registerCustomContentScroll(callback:Function):void{
_onCustomContentScrollProxy = callback;
}
/**
* Removes all listeners that are currently running so that unused scroll bar components can be garbage collected.
*/
public function destroy():void{
_removeListener(scrollFace, MouseEvent.MOUSE_DOWN, _onPressScrollFace);
_removeListener(scrollFace, MouseEvent.MOUSE_UP, _onMouseUpScrollFace);
_removeListener(btnUp, MouseEvent.MOUSE_DOWN, _onPressBtnUp);
_removeListener(btnUp, MouseEvent.MOUSE_OUT, _onDragOutBtnUp);
_removeListener(btnUp, MouseEvent.MOUSE_UP, _onUpBtnUp);
_removeListener(btnUp, Event.ENTER_FRAME, _onEnterFrameBtnUp);
_removeListener(btnDown, MouseEvent.MOUSE_DOWN, _onPressBtnDown);
_removeListener(btnDown, MouseEvent.MOUSE_OUT, _onDragOutBtnDown);
_removeListener(btnDown, MouseEvent.MOUSE_UP, _onUpBtnDown);
_removeListener(btnDown, Event.ENTER_FRAME, _onEnterFrameBtnDown);
_removeListener(_mouseWheelTarget, MouseEvent.MOUSE_WHEEL, _onMouseWheel);
_removeListener(_mouseWheelTarget, MouseEvent.MOUSE_OUT, _onMouseUpScrollFace);
}
private function _onMouseWheel(e:MouseEvent):void{
var d:Number;
var delta:Number = e.delta;
if (delta > 1){
delta = 1;
}
if (delta < -1){
delta = -1;
}
d = -delta * scrollWheelSpeed;
if (d > 0) {
scrollFace.y = Math.min(bottom, scrollFace.y+d);
}
if (d < 0) {
scrollFace.y = Math.max(top, scrollFace.y+d);
}
_updateContentPos();
}
private function _updateContentPos():void {
dy = Math.abs(initPosition-scrollFace.y);
if (_onCustomContentScrollProxy == null){
contentMain.y = Math.round(dy*-1*moveVal+initContentPos);
} else {
try{
_onCustomContentScrollProxy(Math.round(dy*-1*moveVal+initContentPos));
} catch(e:Error){
trace("_onCustomContentScrollProxy callback failed : "+e.message);
}
}
}
private function _onPressScrollFace(e:MouseEvent):void {
var currPos:Number = scrollFace.y;
//startDrag(this, false, left, top, right, bottom);
scrollFace.startDrag( false, new Rectangle( scrollTrack.x+( (scrollTrack.width-scrollFace.width)/2 ), top, 0, bottom-top ) );
scrollFace.addEventListener(MouseEvent.MOUSE_MOVE, _onMouseMoveScrollFace);
}
private function _onMouseMoveScrollFace(e:MouseEvent):void{
//dy = Math.abs(initPosition-scrollFace.y);
//contentMain.y = Math.round(dy*-1*moveVal+initContentPos);
_updateContentPos();
}
private function _onDragOutBtnDown(e:MouseEvent):void {
btnDown.removeEventListener(Event.ENTER_FRAME, _onEnterFrameBtnDown);
}
private function _onDragOutBtnUp(e:MouseEvent):void {
btnUp.removeEventListener(Event.ENTER_FRAME, _onEnterFrameBtnUp);
}
private function _onMouseUpScrollFace(e:MouseEvent):void {
scrollFace.stopDrag();
//delete this.onMouseMove;
//trace(e.relatedObject);
scrollFace.removeEventListener(MouseEvent.MOUSE_MOVE, _onMouseMoveScrollFace);
}
private function _onPressBtnUp(e:MouseEvent):void {
btnUp.addEventListener(Event.ENTER_FRAME, _onEnterFrameBtnUp);
}
private function _onUpBtnUp(e:MouseEvent):void {
btnUp.removeEventListener(Event.ENTER_FRAME, _onEnterFrameBtnUp);
}
private function _onEnterFrameBtnUp(e:Event):void {
if (contentMain.y+speed<maskedView.y) {
if (scrollFace.y<=top) {
scrollFace.y = top;
} else {
scrollFace.y -= speed/moveVal;
}
//contentMain.y += speed;
} else {
scrollFace.y = top;
//contentMain.y = maskedView.y;
//delete this.onEnterFrame;
btnUp.removeEventListener(Event.ENTER_FRAME, _onEnterFrameBtnUp);
}
_updateContentPos();
}
private function _onPressBtnDown(e:MouseEvent):void {
btnDown.addEventListener(Event.ENTER_FRAME, _onEnterFrameBtnDown);
}
private function _onUpBtnDown(e:MouseEvent):void {
btnDown.removeEventListener(Event.ENTER_FRAME, _onEnterFrameBtnDown);
}
private function _onEnterFrameBtnDown(e:Event):void{
if ( contentMain.y-speed > finalContentPos ) {
if (scrollFace.y >= bottom) {
scrollFace.y = bottom;
} else {
scrollFace.y += speed/moveVal;
}
//contentMain.y -= speed;
} else {
scrollFace.y = bottom;
//contentMain.y = finalContentPos;
//delete this.onEnterFrame;
btnDown.removeEventListener(Event.ENTER_FRAME, _onEnterFrameBtnDown);
}
_updateContentPos();
}
private function _removeListener(object:EventDispatcher, eventStr:String, callback:Function):void{
if ( object && object.hasEventListener(eventStr) ){
object.removeEventListener(eventStr, callback);
}
}
}
}