Current update

requestAnimationFrame lets you animate objects with a powerful and direct mechanism, a function which is repeated at the browser’s own convenience.

const animate = () => {
	// ...something animated
	requestAnimationFrame(animate);
};

Call the function “calling” itself through the requesting method and you are able to update the page in some shape or form, over and over. You might have glimpsed the process already, perhaps in an exploration of the JavaScript library Zdog, discovering a couple of ways to update the pseudo-3D drawings. But I find it helpful to discuss the topic anew, to visualize the change and look for another helpful technique.

The most clear, and shallow, approach is to update a value directly, changing a variable by an arbitrary amount.

let x = 0;

const animate = () => {
	x += 0.167;

	requestAnimationFrame(animate);
};

Here you have a magic number, and a small number at that, but there might be some logic in picking the value. You will soon discover that fractional increments work best, and this is because of the high refresh rate of your powerful system. Say the browser requests the function 60 times per second. This means that in a single second the increment compounds for as many times. If you have a measure in mind for the actual change you want on the page, you can find the number with a quick computation.

The logic works, but is undoubtedly linear. With every iteration the variable is updated in the same exact manner.

To work around this, you can set up a more elaborate script. One counter variable incremented by 1 in the animating function, and one threshold variable, describing the upper limit.

let counter = 0;
const threshold = 300;

const animate = () => {
	counter++;

	requestAnimationFrame(animate);
};

Divide the two and you have a value in the (0, 1) range. You may want to reset the counter variable to repeat the exercise, but let’s stick to one iteration.

const t = counter / threshold;

Think of the 0 to 1 value as a measure of progress, of time. With this decimal number you can then update the shapes any way you so choose.

Of course the value is small, but works as a reference. If you wanted to rotate a shape around the center, you would multiply time by a fitting number of degrees, or the necessary number of radians. If you wanted to move a shape in one direction, you would use the offset as a proper weight.

const a = t * (Math.PI * 2);
const x = t * 50;

Again the result is linear, but it doesn’t have to be for much longer. Thanks to the measure of time and thanks to different easing functions. These are functions with a solitary purpose: map a value to a different range.

const quadInOut = (t) => {
	t /= 0.5;
	if (t < 1) return 0.5 * t * t;
	t--;
	return -0.5 * (t * (t - 2) - 1);
};

Feed the variable to the easing function and the change assumes a different, smoother pace.

const ease = quadInOut(t);

The change is subtle, and for some updates, it’s all that you need.

const a = ease * (Math.PI * 2);
const x = ease * 50;

But the change is still limiting in its direction. As the progress goes from 0 to 1, the shapes are updated in one way only.

Linear increments Linear Time 0 1 Value 1 Easing increments Easing Time 0 1 Value 1

In the context of a full rotation, that is far from a problem — by the end of the cycle, you would find yourself in the same position —, but think of the translation. What if you wanted to revert the change, to move in the opposite direction and back to the beginning?

One way is to add a separate variable, detailing the direction with a positive or negative number.

let direction = 1;
let counter = 0;
const cycle = 300;

With this reference, the idea is to update the counter variable to grow and then shrink, flipping the direction every time you reach the upper threshold. But also, every time you go back to 0.

counter = counter + direction;

if (counter > cycle) {
	direction = -1;
} else if (counter < 0) {
	direction = 1;
}

But the concept of rotations, and the concept of radians in particular, offers a different way. This time, with the contribution of the sine function. This is a function which conveniently repeats every time you cross the value of twice the constant PI, also known as TAU.

Sine function TAU x y

Even more conveniently, it reaches the same value with which you start as soon as you reach half the measure. Or, put it simply, PI.

At long last we can get around our looping needs with the trigonometric function and the counter variable. You need to multiply the value, always in the 0-1 range, with the celebrated constant.

const loop = Math.sin(t * Math.PI);

But you have found it, a third, different way to animate shapes. From one value to another and back.

Looping increments Looping Time 0 1 Value 1

Ultimately, you pick the approach which works best for what you need. In some settings, a constant, linear change is more than enough. But you have certainly options to soften the pace, and even ways to create looping animations. Nothing stops you from even mixing an easing with the sine function, and dip your toe into a new animations.