Wait/Loading Animation Shading 




Tutorial:



The last tutorial covered the basics of drawing on a Browser Canvas. Now that the drawing surface is available, the first part of the magic can be introduced, Unsigned Unit Intervals: variables which only vary between zero and one inclusive (written [0,1]), so 0.5 is midrange like 50%.
The gradient in the previous tutorial was drawn with a single Unsigned Unit Interval: the variable
t
in the source code which will be zero (black) wheny
is zero, and one (white) wheny
isheight
. This is simply a division:t=y/c.height
; check it out because this is the heart of everything that follows: wheny
is zero,t
is zero, wheny
isheight
,t
is one and there is no danger of division by zero as long asheight
is not zero. Since the pixel values vary in the range [0,255],t
is finally scaled up simply by multiplying by 255 but this is not done until the pixel data is written because Unit Intervals are more valuable for mixing and filtering.Here's the code again in case you didn't already paste it into a file called shade.htm for editing and viewing in your web browser to see your changes:
<!DOCTYPE html> <html> <body> <canvas id="Drawing" width="50" height="50">This browser doesn't support the canvas element :(</canvas> <script type="text/javascript"> var c=document.getElementById("Drawing"); var ctx=c.getContext("2d"); var imgData=ctx.getImageData(0,0,c.width,c.height); for(var y=c.height; y;) { var t=y/c.height; // t varies in the range [0,1]. 0 will be black, 1 will be white. for(var x=c.width; x;) { var i=4*(c.width*y+x); // locate the point (x,y) in the imgData array imgData.data[i+0]=255*t; // Red scaled up to byte size varying between [0,255] imgData.data[i+1]=255*t; // Green imgData.data[i+2]=255*t; // Blue imgData.data[i+3]=255; // Alpha (Opaque) = Overwrite the screen data } } ctx.putImageData(imgData,0,0); </script> </body> </html>You can change the code to shade overx
instead ofy
; edit three lines of the source to look like this by moving thet
declaration into the inner loop and changingy
tox
andheight
towidth
:for(var y=c.height; y;) { for(var x=c.width; x;) { var t=x/c.width; // t varies in the range [0,1]. 0 will be black, 1 will be white.Save your changes and open the file in your browser to see the result (or Refresh if it's already open  usually by pressing F5 at the top of your keyboard or clicking a circular arrow icon).Now it's time to start playing with Unit Intervals by mixing the x shading with the y shading. Make both shading declarations and give them separate names  I've used dx and dy which culturally mean a change in x and a change in y, then create t by mixing (multiplying) them:
for(var y=c.height; y;) { var dy=y/c.height; // [0,1] for(var x=c.width; x;) { var dx=x/c.width; // [0,1] var t=dx*dy; // t varies in the range [0,1]. 0 will be black, 1 will be white.This will give black top and left edges where either x or y are zero and a white bottom right corner where both x and y are one, with rounded rectangular shading of greys inbetween. You can see the rounded rectangle by adding the next few lines of code immediately after the previous lines:
if(t>0.1) t=1; else t*=10;That was a filter, filtering out values greater than 0.1 and spreading out what was left to cover the full range of shades. That technique is used a lot in the Button and Wall Procedural Textures on this site (in the gold menu bar at the top of this page). Here are a couple of functions that will be used later that spread out either the upper or lower fraction of the Unsigned Unit Interval's range:function Lower(n,t) {return t>1/n ? 1 : t*n;} // if n=5, returns the lower fifth spread out to [0,1] function Upper(n,t) {return t<(n1)/n ? 0 : (t(n1)/n)*n;} // if n=5, returns the upper fifth spread out to [0,1]You can see that the filter used could be written asLower(10,t)
which spreads out the lower tenth of the scale. It's like zooming in to the Lower tenth (throwing away the rest) and bringing that area's detail out to full range.Multiplying Unit Intervals is a safe thing to do because the result is always a another Unit Interval. Look at the minimum: zero, multiplied by anything is always zero  the result can't get smaller (negative); and the maximum? Multiply anything by one and the result can't be bigger than the number you started with  so if that is an Unsigned Unit Interval [0,1] it won't exceed 1: it's still a Unit Interval. Signed Unit Intervals [1,1] behave similarly: when multiplied, the result will always be another Signed Unit Interval. It's also worth noting that there is a very useful operation
(1t)
that can be thought of as the 'opposite' of t. You can uset=(1t)
to invert the shading in your existing code, for example.The last thing to play with is the RGB values (Red, Green, Blue) in the following three lines:
imgData.data[i+0]=255*t; // Red scaled up to byte size varying between [0,255] imgData.data[i+1]=255*t; // Green imgData.data[i+2]=255*t; // BlueTry replacing thet
on one or more lines with zero or one or 0.5 or(1t)
. This is where you can learn to control what you create.
A gradient going from black to red would be (t,0,0) going down thet
column:↓ imgData.data[i+0]=255*t; // Red scaled up to byte size varying between [0,255] imgData.data[i+1]=255*0; // Green imgData.data[i+2]=255*0; // Bluegoing from red to black would be ((1t),0,0):imgData.data[i+0]=255*(1t); // Red scaled up to byte size varying between [0,255] imgData.data[i+1]=255*0; // Green imgData.data[i+2]=255*0; // BlueTry getting from sky blue to sun yellow (yellow is full red and full green):This is one solution (as you'll see if you look at this web page's HTML and javascript source code):
imgData.data[i+0]=255*(t/2+0.5); // Red scaled up to byte size varying between [0,255] imgData.data[i+1]=255*(t/4+0.75); // Green imgData.data[i+2]=255*(1t); // BlueJust like theUpper
andLower
functions mentioned previously, two functions could be used to squeeze a parameter so that it varies in a limited range:function SqueezeAbove(n,t) {return t/n+(11/n);} // if n=5, returns the range mapped to [1/5,1] function SqueezeBelow(n,t) {return t<(1/n) ? t/n : 1;} // if n=5, returns the range mapped to [0,1/5]The sky blue to sun yellow fade would then be written as follows:imgData.data[i+0]=255*SqueezeAbove(t,2); // Red scaled up to byte size varying between [0,255] imgData.data[i+1]=255*SqueezeAbove(t,4); // Green imgData.data[i+2]=255*(1t); // BlueNice as these shadings are, they aren't what is needed for a Wait/Loading animation. A circular shading is developed in the next tutorial.