
| home | AJAX (8) || C#.NET (7) || Coldfusion Development (16) || DHTML (15) || Flash Development (19) || jQuery (8) || MSSQL (2) || UNIX (10) |
| 9.27.06 | Flash “wait” animation - i.e. browser throbber |
So it’s key in any application to keep the user informed and it can be a huge disconnect if the user doesn’t know if they should interact or wait. Such is the case with many applications - where the user doesn’t know if the program is “thinking” and if they should wait. Hence the simple elegance of the animated “busy” icon present in every popular operating system. Hence the need to add it to my growing list of custom components.
With custom ecommerce flash RIA’s I see an immediate need for a generic and non-instrusive “please wait” animation that essentially keeps the user in sync with the application. This is especially necessary considering the amount of remoting involved with every action made in an ecommerce application. This standard was literally set since the first “throbber” in the NCSA Mosiac program with the ugly “N” graphic moving in and out. Very non-exciting - but it got it’s job done and rather well for that matter.
I tried to put a couple test animations together using purely AS2.0. I’ve found that lately the FireFox “throbber” animation has a good adoption for numerous flash RIAs and plain animations as well as fancy pre-loaders. I essentially wanted a lightweight piece of AS2.0 code that could render such an animation… The code below draws either a circle or a bar in a circular manner and uses the Tween class change the opacity of each leaf node in a circular fashion - just like the FireFox throbber.
New way of doing it
I tried to manifest this idea through tween running for 1000millisecond which were started on a 100millisecond interval if there were 10 leaf nodes and relying on Tween::onMontionFinished() to precisely loop the animation. This is impossible. now… on to purely using a setInterval without any Tween madness so that the only affect a few millisecond slippage will have is if the viewer’s eyes are quick enough to notice it (highly unlikely considering we can’t notice the difference at 33.3milliseconds (NTSC 30 frames per second format)).
Here’s the much better manifestation - that doesn’t “bog” down or get out of sync:
and for the new code:
private var _leafSize:Number = 4;
private var _cycleSpeed:Number = 1000;
private var _color:Number = 0x808080;
private var _leafCount:Number = 12;
private var _trailCount:Number = 5;
private var _circleRadius:Number = 10;
private var _startLeaf:Number = 9;
private var _padding:Number = 10;
private var throbber:MovieClip;
private var leafNodeItr:Number = 1;
private var interval;
// no constructor
function init(width:Number, height:Number, Txt:String) {
this.throbber = this.createEmptyMovieClip("throbber", this.getNextHighestDepth());
this.throbber._x = this._circleRadius + this._padding;
this.throbber._y = 0;
var offsetMilliseconds = new Date().getMilliseconds();
var offsetLeafCnt:Number;
for (var i=this._startLeaf; i < (this._leafCount + this._startLeaf); i++) {
offsetLeafCnt = i - this._startLeaf + 1;
this.throbber["leaf" + offsetLeafCnt] = this.throbber.createEmptyMovieClip("leaf" + offsetLeafCnt, this.throbber.getNextHighestDepth());
//this.drawLeafCircle(this.throbber["leaf" + offsetLeafCnt], (Math.cos((i * (360 / this._leafCount)) * (3.14/180)) * this._circleRadius), (Math.sin((i * (360 / this._leafCount)) * (3.14/180)) * this._circleRadius));
this.drawLeafStub(this.throbber["leaf" + offsetLeafCnt], (Math.cos((i * (360 / this._leafCount)) * (3.14/180)) * this._circleRadius), (Math.sin((i * (360 / this._leafCount)) * (3.14/180)) * this._circleRadius));
this.throbber["leaf" + offsetLeafCnt]._alpha = 25;
}
this.interval = setInterval(
mx.utils.Delegate.create(this,
function () {
if (this.leafNodeItr > this._leafCount)
this.leafNodeItr = 1;
var preLo = (this._trailCount > this.leafNodeItr) ? 0 : (this.leafNodeItr - this._trailCount);
var preHi = this.leafNodeItr;
var apreLo = (this._trailCount > this.leafNodeItr) ? (this._leafCount - (this._trailCount - this.leafNodeItr)) : this._leafCount;
var apreHi = this._leafCount;
var fadeCount:Number = 25;
for (var i:Number = 1; i <= this._leafCount; i++) {
if (i == this.leafNodeItr) {
this.throbber["leaf" + i]._alpha = 100;
} else if ( i > preLo && i <= preHi ) {
this.throbber["leaf" + i]._alpha = 25 + (Math.round(75 / this._trailCount) * (i - preLo - (apreLo - this._leafCount)));
} else if ( i > apreLo && i <= apreHi ) {
this.throbber["leaf" + i]._alpha = 25 + (Math.round(75 / this._trailCount) * (i - (this._leafCount - (preLo - (apreLo - this._leafCount)))));
} else {
this.throbber["leaf" + i]._alpha = 25;
}
}
this.leafNodeItr++;
}
),
Math.round(this._cycleSpeed / this._leafCount));
}
function drawLeafStub (target:MovieClip, x:Number, y:Number) {
target.lineStyle(this._leafSize, this._color, 100);
target.moveTo(x, y);
target.lineTo(x*1.5, y*1.5);
}
function drawLeafCircle (target:MovieClip, x:Number, y:Number) {
var w = this._leafSize;
var h = this._leafSize;
// center the circle
w /= 2; h /= 2;
x += w; y += h;
var xc1 = w*(Math.SQRT2-1), xc2 = w*(Math.SQRT2/2);
var yc1 = h*(Math.SQRT2-1), yc2 = h*(Math.SQRT2/2);
target.lineStyle(0, this._color, 100);
target.beginFill(this._color);
target.moveTo(x+w, y);
target.curveTo(x+w, y+yc1, x+xc2, y+yc2);
target.curveTo(x+xc1, y+h, x, y+h);
target.curveTo(x-xc1, y+h, x-xc2, y+yc2);
target.curveTo(x-w, y+yc1, x-w, y);
target.curveTo(x-w, y-yc1, x-xc2, y-yc2);
target.curveTo(x-xc1, y-h, x, y-h);
target.curveTo(x+xc1, y-h, x+xc2, y-yc2);
target.curveTo(x+w, y-yc1, x+w, y);
target.endFill();
}
function _close () {
clearInterval(this.interval);
this.removeMovieClip();
}
}
Download this code: throbber-alpha1-snippet
This is the wrong way to do it (this is the old flawed code I first posted)
I found quickly that this throbber doesn’t work well outside of the flash IDE seeing as there’s no way to rely on internal tween setinterval timing. I quickly found that the setInterval is anything but precise in terms of timing events in Milliseconds. This is especially true in the browser flash plugin.
I’m keeping this code here for the sake of pointing out the trial-by-fire learning curve. Such event timing is always an issue hence relying on it for precise animations on the Milliseconds level is just a no-go.
Bar (aka “stub”) leaf node example:
Circle leaf node example:
Here’s some uncommented code:
class Throbber extends MovieClip {
private var _leafSize:Number = 8;
private var _color:Number = 0x808080;
private var _leafCount:Number = 8;
private var _circleRadius:Number = 14;
private var _padding:Number = 10;
private var throbber:MovieClip;
private var intervalArray:Array;
private var tweenArray:Array;
// no constructor
function init(width:Number, height:Number) {
this.throbber = this.createEmptyMovieClip("throbber", this.getNextHighestDepth());
this.throbber._x = this._circleRadius + this._padding;
this.throbber._y = 0;
this.intervalArray = new Array();
this.tweenArray = new Array();
var offsetMilliseconds = new Date().getMilliseconds();
for (var i=1; i <= this._leafCount; i++) {
this.throbber["leaf" + i] = this.throbber.createEmptyMovieClip("leaf" + i, this.throbber.getNextHighestDepth());
this.drawLeafCircle(this.throbber["leaf" + i], (Math.cos((i * (360 / this._leafCount)) * (3.14/180)) * this._circleRadius), (Math.sin((i * (360 / this._leafCount)) * (3.14/180)) * this._circleRadius));
//this.drawLeafStub(this.throbber["leaf" + i], (Math.cos((i * (360 / this._leafCount)) * (3.14/180)) * this._circleRadius), (Math.sin((i * (360 / this._leafCount)) * (3.14/180)) * this._circleRadius));
this.throbber["leaf" + i]._alpha = 25;
this.intervalArray[i] = setInterval(
Delegate.create(this,
function (i) {
this.tweenArray[i] = new Tween(this.throbber["leaf" + i], "_alpha", Regular.easeOut, 100, 25, 1, true);
this.tweenArray[i].onMotionFinished = function () {
this.rewind();
this.start();
};
clearInterval(this.intervalArray[i]);
}
)
, (((1000 / this._leafCount) * i) - (new Date().getMilliseconds() - offsetMilliseconds)), i);
}
}
function drawLeafStub (target:MovieClip, x:Number, y:Number) {
target.lineStyle(this._leafSize, this._color, 100);
target.moveTo(x, y);
target.lineTo(x*1.5, y*1.5);
}
function drawLeafCircle (target:MovieClip, x:Number, y:Number) {
var w = this._leafSize;
var h = this._leafSize;
// center the circle
w /= 2; h /= 2;
x += w; y += h;
var xc1 = w*(Math.SQRT2-1), xc2 = w*(Math.SQRT2/2);
var yc1 = h*(Math.SQRT2-1), yc2 = h*(Math.SQRT2/2);
target.lineStyle(0, this._color, 100);
target.beginFill(this._color);
target.moveTo(x+w, y);
target.curveTo(x+w, y+yc1, x+xc2, y+yc2);
target.curveTo(x+xc1, y+h, x, y+h);
target.curveTo(x-xc1, y+h, x-xc2, y+yc2);
target.curveTo(x-w, y+yc1, x-w, y);
target.curveTo(x-w, y-yc1, x-xc2, y-yc2);
target.curveTo(x-xc1, y-h, x, y-h);
target.curveTo(x+xc1, y-h, x+xc2, y-yc2);
target.curveTo(x+w, y-yc1, x+w, y);
target.endFill();
}
/* It is important to stop previous Tween object instantiation if this class
* is to be created/removed more than once in a specific animation */
function _close () {
for (var tCnt in this.tweenArray) {
this.tweenArray[tCnt].stop();
delete(this.tweenArray[tCnt]);
}
for (var iCnt in this.intervalArray) {
clearInterval(this.intervalArray[iCnt]);
delete(this.intervalArray[iCnt]);
}
this.removeMovieClip();
}
}
Download this code: throbber-alpha-snippet
| Nikolaj on 11.30.06 at 11am |
|
This r0xORz!!!111111111 j00 have some 31337 flash sk1lzz |
| Jim Palmer on 11.30.06 at 11am |
|
Me thinks Nikolaj is afraid of the defunct tweenArray container for this object. Either that or the horribly amazing circle drawing function. |
| Art on 3.6.07 at 2am |
|
Hi, I’ve been looking for a tutorial that had this kind of visual for a flash loader. Then I found it here, but when I copied and pasted the code into Flash 8, nothing happened. Would it be possible to provide a tutorial or flash file on how to make this a flash preloader? Thanks. Also how effective is the Anti-spam verify code, and would you recommend it on all websites with a contact form? thanks again. |
| Jim Palmer on 3.6.07 at 11am |
|
That’s a really good question. I built this as a class so the above code is not ideal for being a pre-loader animation. It could easily be changed to be so though. I can whip a new version together to be a simple and straight-forward pre-loader that can be just cut-n-pasted into Frame 1 and will work. To do this, we’ll just need to not make it a class or a function - but just take the guts of the _init() function and paste them directly onto Frame 1 (with the private variables and draw function in the code as well). The anti-spam verify code works rather well, simple yet effective. It’s AuthImage thanks to Sandy Lee. |
| Dermot Williams on 10.3.07 at 3am |
|
I’d love to use this throbber for a personal project I’m working on but I’m a bit of an AS newb. How do I actually get my script to display one? |
| Jim Palmer on 10.3.07 at 9am |
|
Dermot, |
| Dermot Williams on 10.4.07 at 5am |
|
Worked like a charm - thanks! |
| Victor on 12.8.07 at 10pm |
|
Thank you very much great code. Here is the code in ActionScript 3. var _leafSize:Number = 5; // create movie clip // create instance of arrays // loop and create circles function aniTween(i:Number) { target.graphics.lineStyle(0, _color, 100); |
| Victor on 12.8.07 at 10pm |
|
My Bad the code above is a translation of the old version. var _leafSize:Number = 4; // no constructor this.throbber = new MovieClip(); function _close () { |
| Neal on 1.31.08 at 5pm |
|
What does ‘this["message"]‘ refer to in the init() function? |
| Jim Palmer on 2.13.08 at 4pm |
|
I’ll take that out - it’s useless. this["message"] was another TextField that was added to sit beside the throbber - for instance “Please Wait…”. It’s useless in the context of this post. |
11 Comments