Animate circle using Javascript and HTML5 Canvas. The circle will be drawn at the center of the canvas. The initial radius will be 0 and will keep on increasing till 100 and then decrease till 0 and this cycle will keep repeating.

var cnvs = document.createElement("CANVAS");

cnvs.width = (3 * window.innerWidth)/4;
cnvs.height = (3 * window.innerHeight)/4;

cnvs.style.border = "2px solid black";
cnvs.style.position = "absolute";
cnvs.style.top = "50%";
cnvs.style.left = "50%";
cnvs.style.transform = "translate(-50%, -50%)";


var inc = 1;
var dec = 0;
var inc_dec = inc;
var x_center = cnvs.width/2;
var y_center = cnvs.height/2;
var min_radius = 0;
var max_radius = 100;
var radius = min_radius;

var ctx = cnvs.getContext("2d");

setInterval(animate_circle, 10);

function animate_circle() {

    if (inc_dec == inc) {
        radius = radius + 1;
    } else {
        radius = radius - 1;

    ctx.clearRect(0, 0, cnvs.width, cnvs.height);

    ctx.arc(x_center, y_center, radius, 0, 2 * Math.PI);
    ctx.fillStyle = "green";

    if (radius == max_radius) {
        inc_dec = dec;
    } else if (radius == min_radius) {
        inc_dec = inc;

} // end of animate_circle
<!doctype html>
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Animate Circle</title>
        <script src="animate_circle.js"></script>


2 Answers 2


setInterval will execute the callback no sooner then 10ms after the call. It doesnt mean it will trigger in 10ms exactly. This makes it possible for the animation to appear not smooth - slower at one time and faster at another time - not that i can see the effect now. Or the overall the animation may get slower then anticipated.

But it would be better, instead of modify the radius by +/-1 on every call. To create a getRadius(t,t0) function which will compute the current radius based on start of animation (t0) and current time (t). It would be much more preditactable if you could say radius is zero at t=t0, 100 at t=t0+1000, and zero again at t=t0+2000 ...

EDIT: as KIKO Software pointed out in comments, you can use Window.requestAnimationFrame() function (https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame) to avoid having to decide whether 10ms is the right interval and instead let your browser rendering engine decide the right interval. This is a great suggestion and would be really shame if it was lost when comments are cleaned up.



Design points

  • You can use Object.assign to assign properties to any object including elements and the element's style

  • You don't need the ctx.beginPath call before you clear the canvas. Same with ctx.closePath

    BTW closePath is not related to beginPath. Close path creates a path (line) from the last pointed added to the start of the current path.

    For a 360 (2 PI) ctx.arc the closePath call does nothing apart from use up CPU cycles..

(ctx) refers CanvasRenderingContext2D


setInterval is a dangerous, unreliable, useless excuse for a memory leak. It really should be removed from the language, but its too late we can't break the web can we :(. Don't use it... ever!

Case in point. As you have used it setInterval(animate_circle, 10); you have assigned memory and CPU time that can not be removed as you neglected to store the interval's handle.

The only way to stop the interval and free up the closure it holds is to

  • Refresh the page
  • Or try to guess the handle for it.


To time animations use window.requestAnimationFrame to ensure that the animation is in sync with the display hardware and behaves in a predictable way in regards to power, resource consumption, and presentation.

Example Rewrite

I Have completely rewritten your code.

Key points.

  • Use rAF to time animation frames.

  • Use a CSS rule to style the canvas.

  • Defined an object to hold the circles state.

  • Use a function to render circles if given a circle like object.(Any object with the properties, x, y, r). Also used functions to separate the code into clear and distinct behaviors/roles.

  • Linked the animation to the time as given by the rAF callback. Same as performance.now (time since page load).

    Note that I store the time of the first frame of the animation to make sure the animation starts when the page can render the animation. (eg if the page loads and the page is not visible the animation will start when it becomes visible)

  • Created utility functions to move common code (cyclic waveform and unit scale) out of the main loop

  • Use constants for any value that does not need to change.

  • Put all the code inside a single self evoking function to keep the global scope clean and code state safe from 3rd party content.

  • Used strict mode as part of a good habit and to ensure code runs at optimal speed.

"use strict";
requestAnimationFrame(mainLoop);  // will only start when all code below is ready
const TAU = Math.PI * 2;
const unitScale = (min, max, u) => (max - min) * u + min;
/* waveform sawtooth wave freq 1 amp 0.5 (dc)offset 0.5 min val 0 max val 1 */
const sawtooth = u => (u %= 1, (u < 0.5 ? u : (1 - u))  * 2);
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
Object.assign(canvas, {width: innerWidth - 40, height: innerHeight - 40});
var startTime;
const minSize = Math.min(canvas.width, canvas.height);

const circle = {  // all coords and sizes in pixels.
    x: canvas.width * 0.5, 
    y: canvas.height * 0.5,
    time: 1,    // in seconds.
    min: minSize * 0.5 * 0.1,    
    max: minSize * 0.5 * 0.9,   
    r: 0,      
    col: "black", // CSS color value

function drawCircle(circle) {
    ctx.fillStyle = circle.col ?? ctx.fillStyle;
    ctx.arc(circle.x, circle.y, circle.r, 0, TAU);
function updateAnimation(time) {
    ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
    circle.r = unitScale(
        circle.min, circle.max, sawtooth(time / (circle.time * 1000))

function mainLoop(time) {
    startTime = startTime ?? time;
    updateAnimation(time - startTime);
canvas {
  border: 2px solid black;
  position: absolute;
  top: 0px;
  left: 0px;


Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy