Writing a colorful sine wave animation in 34 lines of JS.

Seems like people really liked both of my tutorials on how to make a nice animation in a very small amount of JavaScript. Looks like a signal from above that I need to write more of these.

This is what we are going to do today:

Small little disclaimer over here

If you have done a Sine wave animation before, this tutorial won't tell you anything new, other than how to do it in a small amount of JS on a HTML5 Canvas.


if you haven't done any sine wave animations yet, but wanted to, it means that you don't fully understand how Trigonometry or Animations work, at least one of them. So let's review that:

  • How animations work:
    • You draw a particle on a screen. To make it move you have to update the position of the particle and draw it again.
  • How trigonometry works:
    • The Sine is basically a function, that takes an angle as an argument and returns a Y coordinate.
Using this information

We will draw multiple particles on a screen, each with already familiar for us values:

  • Horizontal position
  • Angle
and color. Its not necessary if you want it to be white.

Let's get to code.

Let's quickly slide through the little setup we have here:

  <canvas id="C"></canvas>

  canvas {
    position: absolute; /*removing the ugly scrollbars */
    top: 0; /* by making the body element have the width */
    left: 0; /* of 0, because of position: absolute; */

    let $ = c.getContext('2d'),
        w = c.width = window.innerWidth,
        h = c.height = window.innerHeight,
        opts = {}, //object with preferences
        arr; //we will get to this one in a minute

    function loop(){
        $.fillStyle="#222"; //drawing a black background
        $.fillRect(0,0,w,h); //as a large full-screen square


Simple little setup with a function that will loop. In the first few lines we assign:

  • our canvas' context to a $ variable
  • window.innerWidth and innerHeight to our canvas' c.width and c.height and then to our little single-symbol variables w and h.
  • opts as an object that will store some values that you can tweak to your preference later.
  • arr is undefined. Yet.

What is arr?

It will be the array that will store our particles, that we can loop through, update, and draw each particle. Let's define it.

We will use the same strategy as I used in two previous tutorials: initialising the array and defining it's contents dynamically with .map() at the same time.

  let arr = new Array(opts.amount /* 20 */ ).fill().map((el,ind)=>{
    return {
        a: Math.PI*2/opts.amount * ind, //angle
        x: (opts.radius*2 /* 10*2 */ + opts.distance /* 10 */)*ind, //horizontal position
        c: "hsl(th, 75%, 55%)" //color

There is kind of a lot of stuff going on here, so let's get through it step by step.


As you see the opts object references in the code, please go to the opts object and put in the value that you want/see here. Coming back and going over each value that we mention defined here is pointless.

Array processing
  1. We create an array. The array is empty (!) even though it has the length of opts.amount. The map() function maps through each object in the array and returns a new one if return is specified. We can't run the function map() yet because, as I mentioned, the arr is empty, hence there is nothing to map through.
  2. We fill() the array with undefined elements. Even though each element in the array is still undefined, it is there and the array is not empty anymore.
  3. We map() through each undefined object in the array and return a new object that we are going to discuss right now.
Particle values
  • Angle is a value between 0 and Math.PI*2, with a step multiplied by the ind (id) of the particle, according to the arr.
  • Position will be a value of distance and radius*2 of particle
  • Color just an hsl color value that I liked. We will later replace the th (theta), hue angle, with the angle of the particle, to make the hue rotate (a bit of a spoiler)

Let's update and render

It is going to bit all fit into one forEach function to update and render our particles. The background rendering have been defined in the initial setup, so we will just get to the particles

    //step #1
    el.a+= Math.PI/180*4;
    //step #2
    $.arc(el.x, Math.sin(el.a)*opts.height, opts.radius, 0, Math.PI*2);
    //step #3
    $.fillStyle=el.c.replace("th", el.a*20);

I have defined some steps in the code comments so you know which line to look at.

Step #1

el.a += Math.PI/180*4;. Looks a bit confusing if you haven't done that yet. We are taking an angle of the element and incrementing it by 4 degrees. To get degrees here, we convert radians into one degree and then multiply by 4. You can tweak the division part, but visually, it is easier for me to understand degrees over radians.

Step #2

This one is not complicated either. As I explained previously, we are using Math.sin as a function that will return a Y coordinate. The reason why we are multiplying that coordinate by opts.height, is because Math.sin returns a value between -1 and 1. If we multiply it by the height it will go height px up and height px below the middle line. So, when you define opts.height consider the fact that the actual animation will be twice as tall.

Step #3

This step is used for filling our shape with the dynamic color. The color is defined by the hue angle, which is our element's angle. But because our angle is in Radians, the hue will transition too slowly to notice the "hue wave". To fix that we multiply the angle by a certain number. The bigger the number - the faster the hue rotation. I picked 20 as a multiplier.

But wait, I just saved my file and I don't see my particles in the center.

Yes, particles are located on top left part of the screen. They aren't rendered as showed on the example. And the reason is because we haven't centered them yet. Doing that will be easy. The center of the screen is usually X: width/2, Y: height/2 coordinate. Our window's Height and Width values are stored in w and h variables. So to render our animation in the middle we will add the center values to our X and Y of the particle in the rendering step like this:

    /* X: */ el.x + w/2, 
    /* Y: */ Math.sin(el.a)*opts.height + h/2, 
    opts.radius, 0, Math.PI*2);

It is still not in the center tho...

It is shifted to the right of the screen, because the width of our animation incremenets with the amount of particles, but the incremention starts from the middle of the screen and goes far and beyond (depending on the amount of particles you have).

To fix that we have to get the width of the whole block of our particles. To do that we can just add the width+distance of each article together by multiplying the radius, distance and amount of them together.

  let width = opts.amount*( opts.distance + opts.radius*2 );

now to make it work we just have to divide it by 2 and subtract that amount from the center point:

    el.x + w/2 - width/2, /* the rest of the code */

And at this moment we should be done :)

This is our result:

3,031 0 40