// effects v0.1, // Copyright (c) 2003 Bogdan Ionescu (bogdan@febee.ro) // This script is distributed under the terms of the GPL (gnu.org) //Tool class provides some cross browser functions function Tool() { this.document = document; this.browserType; //0 IE, 1-netscape/mozilla 2-opera this.quirk_mode = true; this.getCompatMode = function(){ this.browserType = (navigator.userAgent.indexOf('Gecko') != -1) + 2*(navigator.userAgent.indexOf('Opera') != -1); //assuming IE if its not opera or netscape this.quirk_mode = (!this.document.compatMode || this.document.compatMode == "BackCompat" || this.document.compatMode == "QuirksMode"); } this.getCompatMode(); } Tool.prototype.findDocument = function(obj) { if(!this.isNode(obj)) return this.getDocument(); while(obj.nodeName != '#document') { obj = obj.parentNode; } return obj; } Tool.prototype.getDocument = function() { return this.document; } Tool.prototype.isNode = function(obj) { return (obj.nodeType != null); } Tool.prototype.getElement = function( obj ) { if(this.isNode(obj)) return obj; else return this.getDocument().getElementById( obj ); } Tool.prototype.getStyle = function(id){ var obj = this.getElement(id); var doc = this.findDocument(obj); if(obj.currentStyle) { return obj.currentStyle; //IE } if( doc.defaultView && doc.defaultView.getComputedStyle) { return doc.defaultView.getComputedStyle(obj, ''); //Mozilla } return obj.style; //Opera. Quite sadly styles defined in stylesheets wont work; } Tool.prototype.getAbsoluteLeft = function(id) { return this.getAbsolutePosition(id)[0]; } Tool.prototype.getAbsoluteTop = function(id) { return this.getAbsolutePosition(id)[1]; } Tool.prototype.getAbsolutePosition = function (id) { var x = y = 0; /* * Workaround for IE. Some very strange bug occurs when the first child of a node * has styleFloat/cssFloat set to 'right'. The offsetLeft of the Node somehow is * set to the offsetLeft of its child, therefore here comes the workaround */ // if(!this.browserType && id.childNodes.length) { var firstElement = id.firstChild; if(firstElement.currentStyle != null && firstElement.currentStyle.styleFloat == 'right'){ firstElement.style.styleFloat="none"; x += id.offsetLeft; y += id.offsetTop; firstElement.style.styleFloat="right"; id=id.offsetParent; } } // while(id){ x += id.offsetLeft; y += id.offsetTop; id = id.offsetParent; if( id ) { var st=this.getStyle(id); x+= this.Int(st.borderLeftWidth); y+= this.Int(st.borderTopWidth); } } return new Array(x,y) }; Tool.prototype.setInnerHeight = function (element, sH) { if (this.browserType == 1 || !this.quirk_mode ) { element.style.height = this.Px(sH); } else { element.style.height = this.Px(sH + element.offsetHeight - this.getInnerHeight(element)); } } Tool.prototype.setInnerWidth = function (element, sW) { if (this.browserType == 1 || !this.quirk_mode ) { element.style.width = this.Px(sW); } else { element.style.width = this.Px(sW + element.offsetWidth - this.getInnerWidth(element)); } } Tool.prototype.getInnerHeight = function (element) { var st = this.getStyle(element); var dh = this.Int(st.borderTopWidth) + this.Int(st.borderBottomWidth) + this.Int(st.paddingTop) + this.Int(st.paddingBottom); return element.offsetHeight - dh; } Tool.prototype.getInnerWidth = function (element) { var st = this.getStyle(element); var dw = this.Int(st.borderLeftWidth) + this.Int(st.borderRightWidth) + this.Int(st.paddingLeft) + this.Int(st.paddingRight); return element.offsetWidth - dw; } Tool.prototype.getWindowHeight = function() { if(this.browserType || this.quirk_mode) { return document.body.clientHeight; } else {//workaround for IE return document.documentElement.clientHeight; } } Tool.prototype.getWindowWidth = function() { return document.body.clientWidth; } Tool.prototype.Int = function(src){ var val = parseInt(src); if(isNaN(val)) return 0; else return val; } Tool.prototype.Px = function(val){ return val + 'px'; } Tool.prototype.addListener = function(object, evStr, handler) { var obj = this.getElement(object); if(evStr.indexOf('on') == 0) evStr = evStr.substr(2, evStr.length - 2); if(obj.attachEvent) return obj.attachEvent('on' + evStr, handler); if(obj.addEventListener) { return obj.addEventListener(evStr, handler, true); } eval(obj + '.on' + evStr + '=' + handler); } /* * displayEffect is the function that offers access to the current defined * effects. You can expand it easily. The parameters are explained below */ displayEffect = function (id, timeout, effect, subeffect, useClone, onStop) { var obj = (new Tool()).getElement(id); this.middle = middleEffect; this.bring = moveEffect; this.margin = marginEffect; var effects = new Array(this.middle, this.bring, this.margin); var type = effect % effects.length; new effects[type]().go(obj,timeout, subeffect, useClone, onStop); } /*********************************************** * effects functions * ***********************************************/ /* * effectBase is the base class of all defined effects * It provides some predefined functionality so that new effects can be added * without much trouble. * * Everything starts with the go(obj, timeout, effect, clone, onStop) method. * obj - the target object * timeout - the elapsed time for the demo * effect - the subtype of the effect * clone - if true, a clone of obj is created so the layout of the page wont * suffer while the effect takes place * onStop - if set, it must be a function. It will be called when the effect * ends. */ effectBase = function() { var dis = this; this.Getter = new Tool(); this.init = function(obj) { return true; } this.savecssText = function() { var bst = this.obj.style; this.cssText= this.Getter.browserType < 2 ?bst.cssText: new Array(bst.left, bst.top, bst.width, bst.height, bst.margin, bst.cursor, bst.position); } this.restorecssText = function() { if(this. Getter.browserType < 2){ this.obj.style.cssText = this.cssText; } else{ // //for some reason Opera doesn't support cssText as the other 2 browsers do this.obj.style.left = this.cssText[0]; this.obj.style.top = this.cssText[1]; this.obj.style.width = this.cssText[2]; this.obj.style.height = this.cssText[3]; this.obj.style.margin = this.cssText[4]; this.obj.style.cursor = this.cssText[5]; this.obj.style.position = this.cssText[6]; } } this.baseInit = function(obj, timeout, effect, clone, onStop) { if(obj.effectInProgess) return -1; obj.effectInProgess = true; this.onStop = onStop; this.effect = effect; this.obj = obj; this.clone = obj; this.counter = 0; this.steps = 25; this.delay=timeout/this.steps; this.savecssText(); if(clone) this.initClone(); this.Getter.setInnerHeight(this.clone, this.Getter.getInnerHeight(this.obj)); this.Getter.setInnerWidth(this.clone, this.Getter.getInnerWidth(this.obj)); this.clone.style.display = 'block'; this.clone.style.overflow = 'hidden'; return this.init(obj); } this.initClone = function() { this.cloned = true; this.visibility = this.obj.style.visibility; this.clone = this.obj.cloneNode(true); this.obj.style.visibility = 'hidden'; this.clone.style.position = 'absolute'; this.clone.style.left = this.Getter.Px(this.Getter.getAbsoluteLeft(this.obj)); this.clone.style.top = this.Getter.Px(this.Getter.getAbsoluteTop(this.obj)); document.body.appendChild(this.clone); } this.removeClone = function() { document.body.removeChild(this.clone); this.obj.style.visibility=this.visibility; } this.restore = function() { this.obj.effectInProgess = null; this.restorecssText(); if(this.cloned)this.removeClone(); if(this.onStop) { this.onStop(); } } this.display = function () { //this function will be overriden in the derived classes window.status='Step ' + this.counter + 1; } this.nextStep = function() { if(this.counter == this.steps) { clearInterval(this.interval); this.interval = null; this.restore(); return; } else { if(!this.counter) this.clone.style.visibility = 'visible'; this.display(); this.counter++; } } this.go = function(obj, timeout, effect, clone, onStop){ if(this.baseInit(obj, timeout, effect, clone, onStop) != -1) this.interval = setInterval(function(){dis.nextStep();}, this.delay); } } /* * here come the defined effects */ var marginEffect = function (){ /* values of this.effect: * from 0 to 5 the object expands. * from 10 to 15 the object shrinks. * 0/10 - from top/left to right/bottom * 1/11 - left to right, * 2/12 - top to bottom, * 3/13 - right to left, * 4/14 - bottom to top, * 5/15 - right/bottom to top/left */ if(effectBase.call) effectBase.call(this); this.prototype = effectBase.prototype; this.init = function(obj) { this.dX = this.Getter.getInnerWidth(this.obj)/this.steps; this.dY = this.Getter.getInnerHeight(this.obj)/this.steps; this.dX0 = this.clone.offsetLeft; this.dY0 = this.clone.offsetTop; this.shrink = this.effect >= 10; if(this.shrink) this.effect%= 10; } this.display = function() { if(!(this.effect % 5) || (this.effect % 2) ) { this.Getter.setInnerWidth(this.clone, this.dX * Math.abs(this.shrink*this.steps-this.counter)); if(this.effect > 1) { this.clone.style.left = this.Getter.Px(this.dX0 + this.dX * Math.abs((1-this.shrink)*this.steps - this.counter)); } } if(!(this.effect % 5) || !(this.effect % 2)) { this.Getter.setInnerHeight(this.clone, this.dY * Math.abs(this.shrink*this.steps -this.counter)); if(this.effect > 2) { this.clone.style.top = this.Getter.Px(this.dY0 + this.dY * Math.abs((1-this.shrink)*this.steps -this.counter)); } } } } var middleEffect = function () { /* values of this.effect: * from 0 to 3 it expands the object * from 10 to 13 it shrinks the object * effect: 0/10 - both direction, 1/11 - horizontal, 2/12 - vertical */ if(effectBase.call) effectBase.call(this); this.init = function(obj) { this.dX = this.Getter.getInnerWidth(this.obj) / (2 * this.steps); this.dY = this.Getter.getInnerHeight(this.obj) / (2 * this.steps); this.dX0 = this.clone.offsetLeft; this.dY0 = this.clone.offsetTop; this.shrink = this.effect >= 10; if(this.shrink) this.effect%= 10; } this.display = function() { if(!this.effect || this.effect == 1) { this.Getter.setInnerWidth(this.clone, 2 * this.dX * Math.abs(this.shrink * this.steps - this.counter)); this.clone.style.left = this.Getter.Px(this.dX0 + Math.abs((1-this.shrink) * this.steps - this.counter) * this.dX); } if(!this.effect || this.effect == 2) { this.Getter.setInnerHeight(this.clone, 2*this.dY * Math.abs(this.shrink * this.steps - this.counter)); this.clone.style.top = this.Getter.Px(this.dY0 + Math.abs((1-this.shrink) * this.steps - this.counter) * this.dY); } } } var moveEffect = function() { /* * Values of this.effect: * from 0 to 7 -moving the object from outside to inside * from 10 to 17 - moving the object from inside to outside * 0/10 - from/to left, * 1/11 - from/to (left/top) * 2/12 - from/to (left/bottom) * 3/13 - from/to bottom * 4/14 - from/to top * 5/15 - from/to right * 6/16 - from/to (right/bottom) * 7/17 - from/to (right/top) */ if(effectBase.call) effectBase.call(this); this.init = function(obj) { this.moveout = this.effect >= 10; if(this.moveout) this.effect%= 10; // moveLeft 0-from left, 1-from right , 2 -unchanged var moveLeft = this.effect < 3? 0 : (this.effect > 4 ? 1 : 2); // moTop 0-unchanged, 1-from top, 2-from bottom var moveTop = (this.effect % 3 == 1)? 0: (this.effect % 5 ? 1 : 2); switch(moveLeft) { case 0: this.dX = (this.Getter.getAbsoluteLeft(obj) + this.clone.offsetWidth)/this.steps *(this.moveout?-1:1); this.dX0 = this.moveout * this.Getter.getAbsoluteLeft(obj) + (this.moveout - 1) * this.clone.offsetWidth; break; case 1: this.dX = (this.Getter.getAbsoluteLeft(this.clone) + 1.1 * this.obj.offsetWidth - this.Getter.getWindowWidth()) / this.steps * (this.moveout?-1:1); if(this.moveout) this.dX0 = this.Getter.getAbsoluteLeft(this.clone); else this.dX0 = this.Getter.getWindowWidth() - 1.1 * this.obj.offsetWidth; break; case 2: this.dX = 0; this.dX0 = this.Getter.getAbsoluteLeft(this.obj); break; } switch(moveTop) { case 0: this.dY = (this.Getter.getAbsoluteTop(obj) + this.clone.offsetHeight)/this.steps *(this.moveout?-1:1); this.dY0 = this.moveout * this.Getter.getAbsoluteTop(obj) + (this.moveout - 1) * this.clone.offsetHeight; break; case 1: this.dY = (this.Getter.getAbsoluteTop(this.clone) + 1.1 * this.obj.offsetHeight - this.Getter.getWindowHeight()) / this.steps * (this.moveout?-1:1); if(this.moveout) this.dY0 = this.Getter.getAbsoluteTop(this.clone); else this.dY0 = this.Getter.getWindowHeight() - 1.1 * this.obj.offsetHeight; break; case 2: this.dY = 0; this.dY0 = this.Getter.getAbsoluteTop(obj); break; } this.clone.style.left = this.Getter.Px(-1000); this.clone.style.top = this.Getter.Px(-1000); } this.display = function() { this.clone.style.left = this.Getter.Px(this.dX0 + this.dX * this.counter); this.clone.style.top = this.Getter.Px(this.dY0 + this.dY * this.counter); } } /* * Notes for those who might want to add new effects * * * Below is an example of a basic effect derived from * effectBase. It will change the background of the * passed object on each step. function basicEffect() { if(effectBase.call) effectBase.call(this); //inheriting from effectBase class this.init = function(obj) { this.colours = new Array('white', 'red', 'blue', 'green', 'orange', 'black', 'yellow', '#123456', '#cc00cc'); this.length = this.colours.length; } this.display = function() { this.obj.style.background = this.colours[this.counter % this.length]; } } * init is called once, when the effect starts. If possible, try to make your * code do the heavy stuff here and try to simplify the next function. * if init returns -1 the effect is canceled * display is called for each step of the effect. In order to get best effects * avoid using a lot of code here, and try keeping this function for the * stuff vital for the effect to take place * An example of how to call this for: Basic Test * */ /* * More advanced information: * * The defined effect can override any of the methods defined in baseEffect * but this must be done with caution if its a method other than init or display * go - the main entry point * baseInit - initializes the members of the defined effect and calls the init * method * initClone/removeClone - if you chosed to clone the object, it creates and * removes a clone that will replace the object during the effect's * life span. The clone will be created in the beginning and removed * at the end of the effect. Its purpose is to preserve the initial * display (useful for objects positioned relative); * savecssText/restorecssText - its aim is to preserve the state of the object * so that the effect wont do any undesired changes on it. * init/restore - are called when the effect starts and when its over * normally the default behaviour of restore does the restoring but * if needed, it should be overriden * nextStep - it's the function triggered on the timer. It calls display() * for each step of the effect, and calls restore() when it's over */