I'm running into issues while trying to make some simple code to loop through and display different combinations of 3 colours.
I want to show 3 boxes on screen and then loop through/increment each possible combination of colours.
For simplicity's sake I'm trying to go from black to white in each box like this:
Yes, I know the thing will take a long time to complete!
The colour is being set by the statement
document.getElementById("box#").style.backgroundColor = "rgb(" + p1 + "," + p1 + "," + p1 + ")"; where p1 is the incrementing variable.
Firstly I tried to do it using nested "for" loops.
This worked smoothly for a single box but I couldn't work out how to nest this format 3 levels deep and have each level increment only when the deeper level had completed the cycle.
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title></title>
<style type="text/css">
/*<![CDATA[*/
.message {
width:100px;height:100px;background-Color:#FFCC66;
}
/*]]>*/
</style>
<script type="text/javascript">
// AnimateII (08-October-2010)
// by Vic Phillips http://www.vicsjavascripts.org.uk
// To progressively change the
// left, top, width, height, color, background-Color, Opacity, border-Width, border-Color, clip
// style of an element over a specified period of time.
// **** Application Notes
// **** The HTML Code
//
// when moving an element the inline or class rule style position of the element should be assigned as
// 'position:relative;' or 'position:absolute;'
// If not assigned the style position of the element will be assigned as 'position:relative;' by the script.
//
// The element would normally be assigned a unique ID name.
//
// **** Initialising the Script.
//
// The script is initialised by assigning an instance of the script to a variable.
// e.g A = new zxcAnimateII('left','id1')
// where:
// A = a global variable (variable)
// parameter 0 = the mode(see Note 1). (string)
// parameter 1 = the unique ID name or element object. (string or element object)
// parameter 2 = the initial value. (digits, default = 0)
//
// **** Executing the Effect
//
// The effect is executed by an event call to function 'A.animate(10,800 ,5000,[10,800]);'
// where:
// A = the global referencing the script instance. (variable)
// parameter 0 = the start value. (digits, for opacity minimum 0, maximum 100)
// parameter 1 = the finish value. (digits, for opacity minimum 0, maximum 100)
// parameter 2 = period of time between the start and finish of the effect in milliseconds. (digits or defaults to previous or 0(on first call) milliSeconds)
// parameter 3 = (optional) the type of progression, 'sin', 'cos' or 'liner'. (string, default = 'liner')
// 'sin' progression starts fast and ends slow.
// 'cos' progression starts slow and ends fast.
// **** Notes
//
// Note 1: Examples modes: 'left', 'top', 'width', 'height', 'opacity', clip, 'color', 'background-Color'.
//
// Note 2: The default units(excepting opacity) are 'px'.
// For hyphenated modes, the first character after the hyphen must be upper case, all others lower case.
//
// Note 3: To animate colors('color' or 'background-Color') the start and finish values may be.
// HEX('#FF0000') or RGB('rgb(255,0,0)'), abbreviated HEX or named colors are not allowed.
//
// Note 4: To animate 'clip' the start and finish values may be
// arrays of the top, right, bottom and left values([0,100,20,0])
// or the standard clip format 'rect(0px,100px,20px,0px)'.
//
// Note 5: It may be required to access the current value of the effect.
// An array storing the current, start and finish values of the element effect may be accessed
// from the element effect.data as fields 0, 1 and 2 respectively && each field is an array.
// For color each field is an array storing the R, G, B values.
// The current effect value is recorded in A.data[0].
//
// Note 6: A function may be called on completion of the effect by assigning the function
// to the animator instance property .Complete.
// e.g. [instance].Complete=function(){ alert(this.data[0]); };
//
// **** Functional Code(2.61K) - NO NEED to Change
function zxcAnimateII(mde,obj,srt){
this.obj=(typeof(obj)=='string')?document.getElementById(obj):obj;
this.mde=mde.replace(/[-#]/g,'');
this.data=[srt?this.convert(srt,this.mde):0];
this.to=null;
}
zxcAnimateII.prototype={
animate:function(srt,fin,ms,c){
this.std=!(this.mde.indexOf('olor')>0||this.mde=='clip'||this.mde=='opacity');
srt=this.convert(srt,this.mde);
fin=this.convert(fin,this.mde);
this.pos=true;
for (var z0=0;z0<srt.length;z0++){
if (srt[z0]<0||fin[z0]<0){
this.pos=false;
}
}
c=c||'';
this.c=c.charAt(0).toLowerCase();
this.mS=ms||2000;
clearTimeout(this.to);
this.srttime=new Date().getTime();
this.inc=Math.PI/(2*this.mS);
this.data=[[],srt,fin];
this.cng();
},
cng:function(){
var ms=new Date().getTime()-this.srttime;
for (var z0=0;z0<this.data[2].length;z0++){
this.data[0][z0]=Math.floor(this.c=='s'?(this.data[2][z0]-this.data[1][z0])*Math.sin(this.inc*ms)+this.data[1][z0]:this.c=='c'?(this.data[2][0])-(this.data[2][z0]-this.data[1][z0])*Math.cos(this.inc*ms):(this.data[2][z0]-this.data[1][z0])/this.mS*ms+this.data[1][z0]);
if (this.pos&&this.data[0][z0]<0){
this.data[0][z0]=0;
}
}
this.cngstyle();
if (ms<this.mS){
this.to=setTimeout(function(oop){ return function(){oop.cng(); } }(this), 10);
}
else {
this.data[0]=this.data[2];
this.cngstyle(this.data[0]);
if (this.Complete){
this.Complete(this);
}
}
},
cngstyle:function(){
var v=this.data[0];
if (this.std){
this.obj.style[this.mde]=v[0]+'px';
}
else if (this.mde.indexOf('olor')>0){
this.obj.style[this.mde]='rgb('+v[0]+','+v[1]+','+v[2]+')';
}
else if (this.mde=='clip'){
this.obj.style[this.mde]='rect('+v[0]+'px,'+v[1]+'px,'+v[2]+'px,'+v[3]+'px)';
}
else if (this.mde=='opacity'){
zxcOpacity(this.obj,v[0]);
}
},
convert:function(col,mde){
if (typeof(col)=='object'){
return col;
}
else if (mde=='clip'){
col=col.replace(/[rect()px]/g,'').split(',');
return [col[0]*1,col[1]*1,col[2]*1,col[3]*1];
}
else if (!col.toString().match('#')){
return [parseInt(col)];
}
else {
col=parseInt(col.substring(1,3),16)+','+parseInt(col.substring(3,5),16)+','+parseInt(col.substring(5,7),16);
col=col.replace(/[rgb()\s]/g,'').split(',');
return [parseInt(col[0]),parseInt(col[1]),parseInt(col[2])];
}
}
}
function zxcOpacity(obj,opc){
if (opc<0||opc>100) return;
obj.style.filter='alpha(opacity='+opc+')';
obj.style.opacity=obj.style.MozOpacity=obj.style.WebkitOpacity=obj.style.KhtmlOpacity=opc/100-.001;
}
</script>
</head>
<body>
<div class="message" id="tst" ></div>
<script type="text/javascript">
/*<![CDATA[*/
var box=new zxcAnimateII('background-Color','tst')
box.animate('#FFCC66','#FFFFCC',1000);
/*]]>*/
</script>
</body>
</html>
Just to elaborate on what I need to achieve, the problem with timeouts is although I can create a delay until the cycle on one level has finished before executing code to change the layer above it (as Vic's code does), this isn't enough.
Vic's example allows me to execute an entire cycle of change one one level after another level has finished. For my project each level of loop has to increment just a single step after the layer below it completes a cycle.
So I think I need some hybrid between nested for loops and timeouts (if such a beast exists).
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title></title>
<style type="text/css">
/*<![CDATA[*/
.message {
width:100px;height:100px;background-Color:#FFCC66;
}
/*]]>*/
</style>
<script type="text/javascript">
// AnimateII (08-October-2010)
// by Vic Phillips http://www.vicsjavascripts.org.uk
// To progressively change the
// left, top, width, height, color, background-Color, Opacity, border-Width, border-Color, clip
// style of an element over a specified period of time.
// **** Application Notes
// **** The HTML Code
//
// when moving an element the inline or class rule style position of the element should be assigned as
// 'position:relative;' or 'position:absolute;'
// If not assigned the style position of the element will be assigned as 'position:relative;' by the script.
//
// The element would normally be assigned a unique ID name.
//
// **** Initialising the Script.
//
// The script is initialised by assigning an instance of the script to a variable.
// e.g A = new zxcAnimateII('left','id1')
// where:
// A = a global variable (variable)
// parameter 0 = the mode(see Note 1). (string)
// parameter 1 = the unique ID name or element object. (string or element object)
// parameter 2 = the initial value. (digits, default = 0)
//
// **** Executing the Effect
//
// The effect is executed by an event call to function 'A.animate(10,800 ,5000,[10,800]);'
// where:
// A = the global referencing the script instance. (variable)
// parameter 0 = the start value. (digits, for opacity minimum 0, maximum 100)
// parameter 1 = the finish value. (digits, for opacity minimum 0, maximum 100)
// parameter 2 = period of time between the start and finish of the effect in milliseconds. (digits or defaults to previous or 0(on first call) milliSeconds)
// parameter 3 = (optional) the type of progression, 'sin', 'cos' or 'liner'. (string, default = 'liner')
// 'sin' progression starts fast and ends slow.
// 'cos' progression starts slow and ends fast.
// **** Notes
//
// Note 1: Examples modes: 'left', 'top', 'width', 'height', 'opacity', clip, 'color', 'background-Color'.
//
// Note 2: The default units(excepting opacity) are 'px'.
// For hyphenated modes, the first character after the hyphen must be upper case, all others lower case.
//
// Note 3: To animate colors('color' or 'background-Color') the start and finish values may be.
// HEX('#FF0000') or RGB('rgb(255,0,0)'), abbreviated HEX or named colors are not allowed.
//
// Note 4: To animate 'clip' the start and finish values may be
// arrays of the top, right, bottom and left values([0,100,20,0])
// or the standard clip format 'rect(0px,100px,20px,0px)'.
//
// Note 5: It may be required to access the current value of the effect.
// An array storing the current, start and finish values of the element effect may be accessed
// from the element effect.data as fields 0, 1 and 2 respectively && each field is an array.
// For color each field is an array storing the R, G, B values.
// The current effect value is recorded in A.data[0].
//
// Note 6: A function may be called on completion of the effect by assigning the function
// to the animator instance property .Complete.
// e.g. [instance].Complete=function(){ alert(this.data[0]); };
//
// **** Functional Code(2.61K) - NO NEED to Change
function zxcAnimateII(mde,obj,srt){
this.obj=(typeof(obj)=='string')?document.getElementById(obj):obj;
this.mde=mde.replace(/[-#]/g,'');
this.data=[srt?this.convert(srt,this.mde):0];
this.to=null;
}
zxcAnimateII.prototype={
animate:function(srt,fin,ms,c){
this.std=!(this.mde.indexOf('olor')>0||this.mde=='clip'||this.mde=='opacity');
srt=this.convert(srt,this.mde);
fin=this.convert(fin,this.mde);
this.pos=true;
for (var z0=0;z0<srt.length;z0++){
if (srt[z0]<0||fin[z0]<0){
this.pos=false;
}
}
c=c||'';
this.c=c.charAt(0).toLowerCase();
this.mS=ms||2000;
clearTimeout(this.to);
this.srttime=new Date().getTime();
this.inc=Math.PI/(2*this.mS);
this.data=[[],srt,fin];
this.cng();
},
cng:function(){
var ms=new Date().getTime()-this.srttime;
for (var z0=0;z0<this.data[2].length;z0++){
this.data[0][z0]=Math.floor(this.c=='s'?(this.data[2][z0]-this.data[1][z0])*Math.sin(this.inc*ms)+this.data[1][z0]:this.c=='c'?(this.data[2][0])-(this.data[2][z0]-this.data[1][z0])*Math.cos(this.inc*ms):(this.data[2][z0]-this.data[1][z0])/this.mS*ms+this.data[1][z0]);
if (this.pos&&this.data[0][z0]<0){
this.data[0][z0]=0;
}
}
this.cngstyle();
if (ms<this.mS){
this.to=setTimeout(function(oop){ return function(){oop.cng(); } }(this), 10);
}
else {
this.data[0]=this.data[2];
this.cngstyle(this.data[0]);
if (this.Complete){
this.Complete(this);
}
}
},
cngstyle:function(){
var v=this.data[0];
if (this.std){
this.obj.style[this.mde]=v[0]+'px';
}
else if (this.mde.indexOf('olor')>0){
this.obj.style[this.mde]='rgb('+v[0]+','+v[1]+','+v[2]+')';
}
else if (this.mde=='clip'){
this.obj.style[this.mde]='rect('+v[0]+'px,'+v[1]+'px,'+v[2]+'px,'+v[3]+'px)';
}
else if (this.mde=='opacity'){
zxcOpacity(this.obj,v[0]);
}
},
convert:function(col,mde){
if (typeof(col)=='object'){
return col;
}
else if (mde=='clip'){
col=col.replace(/[rect()px]/g,'').split(',');
return [col[0]*1,col[1]*1,col[2]*1,col[3]*1];
}
else if (!col.toString().match('#')){
return [parseInt(col)];
}
else {
col=parseInt(col.substring(1,3),16)+','+parseInt(col.substring(3,5),16)+','+parseInt(col.substring(5,7),16);
col=col.replace(/[rgb()\s]/g,'').split(',');
return [parseInt(col[0]),parseInt(col[1]),parseInt(col[2])];
}
}
}
function zxcOpacity(obj,opc){
if (opc<0||opc>100) return;
obj.style.filter='alpha(opacity='+opc+')';
obj.style.opacity=obj.style.MozOpacity=obj.style.WebkitOpacity=obj.style.KhtmlOpacity=opc/100-.001;
}
</script>
</head>
<body>
<div class="message" id="tst0" ></div>
<div class="message" id="tst1" ></div>
<div class="message" id="tst2" ></div>
<script type="text/javascript">
/*<![CDATA[*/
var box0=new zxcAnimateII('background-Color','tst0');
var box1=new zxcAnimateII('background-Color','tst1');
box1.Complete=function(){
box0.animate('#FFCC66','#FFFFCC',1000);
}
var box2=new zxcAnimateII('background-Color','tst2');
box2.Complete=function(){
box1.animate('#FFCC66','#FFFFCC',1000);
}
box2.animate('#FFCC66','#FFFFCC',1000);
/*]]>*/
</script>
</body>
</html>
Sorry, Vic, maybe I didn't explain quite clearly enough - I don't need the 3 boxes to change sequentially. I need them to change in a similar way to a digital counter.
That is, once the bottom box has completed a full cycle of change from black to white (for the sake of example let's say it takes 128 steps), then the middle box will increment by just 1 step. Once the bottom box has completed its second full 128-step cycle, the middle box will increment another single step.
In this way it will take 128 black-->white cycles of the bottom box until the middle box completes a single cycle.
And at this point the top box will increment the first step of its 128-step cycle.
So over time, for each cycle of the top box the middle box will cycle 128 times and the bottom box will complete 128x128=16,384 cycles.
It's this conditionality which has me thinking in terms of nested loops, but if I use the zxcAnimateII() function within another function within another function then how can the code know how to just increment a single step and how to suspend processing until the box underneath it has completed a full cycle?
Of course I could set the period of time of the function to increasingly high powers of the innermost loop, but very soon (as I add a few more boxes) the numbers exceed the allowable maximum.
Received a suggestion from elsewhere which helps incredibly and should be all I need to continue from here.
I've copied it below in case anyone else is interested.
Thanks again Vic for your comments and advice, much appreciated, and I can see some very interesting ways the script you shared might help in future online artworks.
Code:
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>loopandShow</title>
<style type="text/css">
#box1, #box2, #box3 {
float: left; margin: 20px;
width: 200px; height : 200px;
background: black;
}
</style>
</head>
<body>
<div id = 'box1'></div>
<div id = 'box2'></div>
<div id = 'box3'></div>
<script type="text/javascript">
// From: http://www.webdeveloper.com/forum/showthread.php?t=239101
function loopandShow(els){
// define local variables
var elNodesStyles = [];
var timer = null;
var elColor = [];
// Store nodes in an array.
// Therefore avoiding having to search the DOM repeatedly in colourInc.
for (var i = 0, len = els.length; i < len; i+=1){
elNodesStyles.push(document.getElementById(els[i]).style);
elColor.push(0);
}
var colourInc = function(){
// if we've reached 255 in 3rd box, clear the timer and return
if (elColor[2] > 255) { // optional setting all box colors to 255 here, if desired
clearInterval(timer); timer = null; return;
}
// loop through our nodes, and set property
elNodesStyles[0].background = "rgb(" + elColor[0] + "," + elColor[0] + "," + elColor[0] + ")";
elNodesStyles[1].background = "rgb(" + elColor[1] + "," + elColor[1] + "," + elColor[1] + ")";
elNodesStyles[2].background = "rgb(" + elColor[2] + "," + elColor[2] + "," + elColor[2] + ")";
// adjust speed of changes with different increment assignments below
elColor[0]+=8;;
if (elColor[0] > 255) { elColor[1]+=16; elColor[0] = 0; }
if (elColor[1] > 255) { elColor[2]+=32; elColor[1] = 0; }
};
timer = setInterval(colourInc, 5);
}
// an array of elements
var elems = ['box1', 'box2', 'box3'];
loopandShow(elems);
</script>
</body>