In here I will document some methods for time-based drawing using simple loops

Before getting into the details, I'll start by going through a basic canvas setup that will give me some of the basics. You can use available frameworks such as p5js as-well, but I prefer this method.

I've put in documentation into the js code of the details, but the important part is that I've created a draw function that executes on every render frame. For now, I've only put in a function that fills the background with a red color, you will see it fade in, as it is using a low opacity.

The basis, at the lowest level, is a variable that goes(increments) from 0 to n then starts over again

The mechanics is that you have a variable val that gets updated every time an update is called. If the value reaches a maximum range value, it starts from beginning again. This is done using a modulo(%) function val = (val + 1) % range

This is a class that will allow a variable to hold some basic values

  // create a class to hold value and have built in incrementing functionality
function Looper (range, start) {
  this.val = start || 0 // set value to start value if defined, or 1
  this.range = range || 100 // set range to passed value or default to 0
  this.norm = this.val / this.range // initialize normalized value (between 0 and 1)
  this.update = function () {
    this.val = (this.val + 1) % this.range // update value
    this.norm = this.val / this.range // update normalize value (between 0 and 1)
  }
}

to show how this works, I created a loop instance, which will go from 0 to 299 var loop1 = new Looper(300, 0)

and then inside the draw loop, I use it to draw a circle (using a custom function that takes x, y, and radius values)

drawCircle(loop1.val - loop1.range / 2, 0, 50)

and, also inside the draw loop, I update the loop and the loop values loop1.update()

what you will see is that the circle will move along the x axis from -150 to 149, then go to -150 again and repeat indefinetly. This is because the value goes from 0 to 299 and is offset by -150.

now instead of using the value val, I switch to using the normalized value norm which will return a value between 0 and 1. The range in this case becomes the number of steps between 0 and 1. node that it will never be 1, because instead of a value of 1 it will skip back to 0

This is the code I'm using to draw using a normalized value. (I offset it by -0.5 because my 0,0 point is in the middle)

  drawCircle((loop1.norm - 0.5) * (canvas.width - 100), 0, 50)

The next step is to add a smoother back-and forth animation.

This is done using some simple trigonometry.

  // in the loop function
this.sin = Math.sin(this.norm * Math.PI * 2)

//in the draw loop
drawCircle((loop1.sin) * (canvas.width/2), 0, 50)

to understand how this works, look at this graph. The x axis, in the ranges from 0 to 2 * PI, completes a full wave. Thy y axis ranges from -1 to +1. So to get a value that goes from 0, up to 1, then down to -1 and back to 0, we map the normalized value norm to 2PI (so instead of ranging from 0 to 1 it ranges from 0 to 2PI)

sine wave - wikipedia source: wikipedia

Now the same thing with normalized the sine, so we have a range from 0 to 1

adding new variable called sinNorm to the loop class

  this.sinNorm = (this.sin + 1) / 2

and updating the draw function to utilize it. drawCircle((loop1.sinNorm - 0.5) * (canvas.width), 0, 50)

Now I'm adding a cos value as-well and applying to y axis

    // in loop class/function
  this.cos = Math.cos(this.norm * Math.PI * 2) // get cosine value from norm normalized to [0, 2PI]
  this.cosNorm = (this.cos + 1) / 2 // normalize cos to [0,1]

  //in draw loop
  var r = 50 // radius of circle (using it below to adjust range along x and y axis)
  drawCircle((loop1.sin) * (canvas.width/2 - r), (loop1.cos) * (canvas.height/2 - r), r)

Now that that works and makes sense, I apply the create multipe particle instances that have multiple loopers mapped to position, size, and color

The magic happens in the Particle class. I've set up 3 particles, and using all of them to update the the x and y position. I'm using the loopers to update the circle radius and color.

  function Particle() {
  // initialize loopers with random trange and offset
  this.loop1 = new Looper(2000 + 200 * Math.random(), 860 * Math.random()) 
  this.loop2 = new Looper(500 + 250 * Math.random(), 220 * Math.random())
  this.loop3 = new Looper(120 + 20 * Math.random(), 140 * Math.random())
  this.offset = Math.random() // some color offset for the color

  this.draw = function (){
    // set x,y, radius, and color params
    var x = (this.loop1.sin) * (canvas.width/4 - 10) + (this.loop2.sin) * (canvas.width/6 - 10) + (this.loop3.sin) * 30
    var y = (this.loop1.cos) * (canvas.height/4 - 10) + (this.loop2.cos) * (canvas.height/6 - 10) + (this.loop3.cos) * 30
    var r = 0.5 + 8 * this.loop3.sinNorm * this.loop3.cosNorm // set the radius
    var c = `hsla(${80 + 50 * (this.loop3.cosNorm + this.offset) * this.loop2.sinNorm}, ${100}%, ${50 + 10 * this.loop3.sin}%, ${0.5})`

    drawCircle(x, y, r, c);  // draw the circle

    this.loop1.update() // update looper
    this.loop2.update() // update looper
    this.loop3.update() // update looper
  }
}


3,794 3 131