DEV Community

Brian Montana
Brian Montana

Posted on • Edited on

Canvas 101: Rotating Shape

Never built anything in canvas before?! This is perfect for ya!

Let's start with a few concepts of setting up and using the Canvas API. We can think of Canvas as a programmable etch a sketch that you erase and draw every time at a fraction of a second.

We can setup the first few lines of code in HTML, CSS, and JavaScript. We'll use CodePen because it's an easy way to jump in and start building with Canvas. You'll create a new pen, set CSS to normalize, add <canvas id="canvas"></canvas> to the HTML, and add html { overflow: hidden; } to CSS.

Now we can get into building with Canvas, breaking down each step.

const canvas = document.querySelector("#canvas");
const context = canvas.getContext("2d");
let canvasWidth = canvas.width = window.innerWidth;
let canvasHeight = canvas.height = window.innerHeight;

We'll store a reference to the HTMLCanvasElement with const canvas = document.querySelector("#canvas"); this will let us access properties and methods to start drawing. const context = canvas.getContext("2d"); reaches into the canvas HTMLCanvasElement to return the context of the canvas to draw. canvasWidth and canvasHeight are using the properties of the window to apply the width and height to the canvas.

First let's build the render function to set the color for the context then draw a shape fillRect() as the background.

function render(){
  context.fillStyle = 'lightsalmon';
  context.fillRect(0, 0, canvasWidth, canvasHeight);
}

Next we can construct a class for a shape, let's make it a Square ◻️ and give it these properties in the constructor { width, height, rotate, xPosition, yPosition }. We'll deconstruct the incoming object in the constructor and set default values. After making the Square class let's make an instance of Square and set some properties.

class Square {
  constructor({
    width,
    height,
    rotate = 0,
    xPosition = canvasWidth / 2,
    yPosition = canvasHeight / 2
  }) {
    this.width = width;
    this.height = height;
    this.rotate = rotate;
    this.xPosition = xPosition;
    this.yPosition = yPosition;
  }
}

const square = new Square({width: 50, height: 50});

Now that we have the Square class and an instance of it created. We can start to add it to the render method. So let's step back into it and do a few important steps. The context.save() method will allow us to save the transformation, specific attributes, clipping, etc. Allowing you to place multiple shapes in the drawing context of canvas and context.restore() will reinstate the state from context.save().

function render() {
  context.fillStyle = "lightsalmon";
  context.fillRect(0, 0, canvasWidth, canvasHeight);
  context.save();
  // Add styling for square in the context here!
  context.restore();
}

Nothing is going to change when setting this up but it will allow us to start building in the shapes and styles in the commented section! So let's add a darker color to next draw context context.fillStyle, set the context transformation matrix with context.translate(), context.rotate(), then draw in the context with context.fillRect().

function render() {
  context.fillStyle = "lightsalmon";
  context.fillRect(0, 0, canvasWidth, canvasHeight);
  // animation method
  context.save();
  context.fillStyle = "salmon";
  context.translate(square.xPosition, square.yPosition);
  context.rotate(square.rotate);
  context.fillRect(-square.width/2, -square.height/2, square.width, square.height);
  context.restore();
  // requestAnimationFrame
}

Awesome! You drew a shape in the canvas... now let's animate it! We'll create a movement method to increment the rotation and position of the square. The transformation matrix property rotate is a value of 0 to 1; 1 represents 360 degrees. Where we place the movement method to manipulate the square properties will be very important.

function movement(shape) {
  shape.rotate += 0.01;
  shape.xPosition += 0.1;
}

Since we have the movement method, let's start by building it into the render method. The most important thing is we need to make sure we're not constantly updating the context transformation matrix every time movement method occurs. So context.save makes sure that doesn't happen and context.restore applies the initial state again. The last thing we'll do it use the requestAnimationFrame method so we're only calling the render method every 60 frames a second :D

function render() {
  context.fillStyle = "lightsalmon";
  context.fillRect(0, 0, canvasWidth, canvasHeight);
  movement(square);
  context.save();
  context.fillStyle = "salmon";
  context.translate(square.xPosition, square.yPosition);
  context.rotate(square.rotate);
  context.fillRect(-square.width/2, -square.height/2, square.width, square.height);
  context.restore();
  window.requestAnimationFrame(render);
}

window.requestAnimationFrame(render);

There we go! The square is slowly rotating and flying off the canvas! You can check out the final version on CodePen :D

Top comments (0)