Wednesday, February 25, 2009

New Logo using JavaScript and Canvas


Been working on a new logo for my nemesisstar.com website. Well not changing the logo really, just adding a bit of flair to it without using flash. If you want to take a look at it you can find it here. Again, don't bother trying to look at it using IE, as the internet explorer browser does not yet support canvas, and no official work that i know of that IE8 will support it either. 

Break down of how the script is working ...

Ok, for speed reasons I'm using 3 canvas's, not just one. All the base objects are wrapped in a div tag, the first 'img' tag inside the div will be used for those browsers that do not support the canvas object. For non-game stuff I have not reason not to provide some sort of backwards-compatibility support ... at very least so the search engines can still browse the site. the very last. I will throw everything into  a nice style sheet and make it pretty a bit later.


Setup of the cavas objects ...

    var logo = document.getElementById('myLogo');
    var logoBG = document.getElementById('myLogoBG');
    var logoBG2 = document.getElementById('myLogoBG2');
    var ctx = logo.getContext('2d');
    var ctxBG = logoBG.getContext('2d');
    var ctxBG2 = logoBG2.getContext('2d');
    ctxWidth = parseInt(document.getElementById('myLogo').offsetWidth);
    ctxHeight = parseInt(document.getElementById('myLogo').offsetHeight);
    document.getElementById('myLogo').width = ctxWidth;
    document.getElementById('myLogo').height = ctxHeight;
    document.getElementById('myLogoBG').width = ctxWidth;
    document.getElementById('myLogoBG').height = ctxHeight;
    document.getElementById('myLogoBG2').width = ctxWidth;
    document.getElementById('myLogoBG2').height = ctxHeight;
    ctx.globalCompositeOperation = 'source-over';
    ctxBG.globalCompositeOperation = 'source-over';
    ctxBG2.globalCompositeOperation = 'source-over';

Because I'm fitting the canvas objects to the screen width and height i need to adjust these values so that they are transformed to their appropriate 000px values. Otherwise, on some browsers at least, everything will have to pass through the scale routines and for some reason things really start to slow down.

Preloading the Graphics ...

Any graphics you use need to be preloaded, or if you are loading them as you need them in your script you need to verify that they are actually loaded before you use them ...

    var imgLogoA = new Image();
    var imgLogoB = new Image();
    var imgLogoC = new Image();
    imgLogoA.src = "assets/images/logo1a2.png";
    imgLogoB.src = "assets/images/logo1d.png";
    imgLogoA.onload = function() { loadCntr++; chkLoad(); }
    imgLogoB.onload = function() { loadCntr++; chkLoad(); }
    function chkLoad()
    {
      if (loadCntr==loadTotal)
      {
        renderLogo();
      }
    }

Very simple if you've done it before, no reason at all to make anything more complicated than you require. If you don't preload then you will get some very funky errors ... but inside the error text it will say 'no data' which is the key point to the error. The browser has 'no data' to work with .. as in, the image has not loaded.

The animation script ...

The script itself is pretty simple if you just walk through it line-by-line. Which I'm not going to do here. First everything needs to be set up and initialized ...

    function renderLogo()
    {
      ctxBG.drawImage(imgLogoA, centerX-203, centerY-203);
      window.setTimeout(doRotate, 100);
    }

The ctxBG is the middle layer which is holding the static logo. It is not moving at all in this portion of the script so their is no reason to keep redrawing it. Plus by sandwiching it between the three layers we can simulate the 3d effect.

angCos[curAng] and angSin[curAng] ...

Very simple simple if you have worked on any sort of 3d application. It takes more time to access Math.sin than it does an array. So we pre-calculate any sin or cos value that we might need and put them into these two arrays. As for the rest of the formula it is a very simple circle drawing routine from basic math class ...

        x = (250 * angCos[curAng]+centerX);
        y = (200 * angSin[curAng]+centerY);
        rad = curAng*0.01745 + offAng;

That tells us exactly where to draw all of the little spikes. The next line of code after the x,y are calculated converts the degree's to radians which is required by the canvas.rotate() function.  After that we actually have to do the drawing but there are two different buffers to draw too.

          if (fbuff==false)

The fbuff variable simply stands for "flip buffer" which tells the screen whether to draw to the foreground or the background based on the 'y' (vertical) value.  After that we do the actual drawing ...

            ctxBG2.save();
            ctxBG2.translate(x,y);
            ctxBG2.rotate(rad);
            ctxBG2.drawImage(imgLogoB, 0, 0);
            ctxBG2.restore();
            curAng += 45;

The canvas.save() and canvas.restore() do exactly like the sound, they save the current state of the canvas, allow us to do our manipulations, then restore it. canvas.translate(x,y) moves the origin point so that when we call the canvas.rotate(radian) function it rotates around the correct point and not the top of the screen (0,0).  canvas.drawImage(img,x,y) does exactly like it sounds ... draw's the little spike to the screen .. finally.

      ctx.translate(0,ctxHeight*((1-scaleY)/2));
      ctxBG2.translate(0,ctxHeight*((1-scaleY)/2));
      ctx.scale(1,scaleY);
      ctxBG2.scale(1,scaleY);

Above all that in the code is this piece which simply adjusts the vertcale scale value and re-centers the origin. The coupled with the buffer flipping is what gives us our pseudo-3d look without having to bother with full 3d code which is slow at best with the current canvas object. Full 3d support is to be added to the canvas object later, but they will have to get past the arguments about the standards first. Myself if i wanted to add full 3D i would just use Unity 3D even though I don't like plug-ins.

Speed of the canvas rendering ...

I've done everything that I've been able to think of to make this run as fast as possible and have a great more that I do wish to add. So far Chrome seems to run it the fastest for me by far and the few people I've had test it have said that a higher end video card does help in the rendering speed. The only thing I don't like is with the black on white it seems a bit shaky in the rendering. I could probably add in 2 more buffers and then flip the visibility after they are rendered to remove that but I'm not positive how that will work out.

Future additions ...
  1. Add interactivity so the user can play with the logo
    * double-click top and it will spin top-to-bottom
    * double-click side and it will spin left-to-right
    * drag the spikes to increase or decrease the spinning speed.
    * be able to click and select a spike
    * add a highlight-glow to currently selected spike/icon
  2. Add icons to the spikes
  3. Make the center image change according to the icon clicked on.
  4. See if i can get more speed by removing the canvas.scale() and converting the spikes to vector drawings instead of images.
  5. Convert the entire script into an animation object/class.
    Will be using this same type of animation in the nemesis-star game so that i can add space-station and such around planets. When being used on a smaller scale like that the animation speed should drastically increase.

No comments:

Post a Comment