ikerhurtado.com
You're in
Iker Hurtado's pro blog
Developer | Entrepreneur | Investor
Software engineer (entrepreneur and investor at times). These days doing performant frontend and graphics on the web platform at Barcelona Supercomputing Center

Notes on HTML5 Canvas

21 Apr 2016   |   iker hurtado  
Share on Twitter Share on Google+ Share on Facebook

I start quoting a good introduction to canvas (by comparing it with DOM work) from Kirupa:

Once the pixels get placed on the canvas, they lose any individuality and become just another brick in the wall. This is one of the things that makes drawing on the canvas more difficult compared to DOM elements.

We can't fundamentally alter how the canvas works. What we can do is come up with ways to track and manipulate the things we draw on the canvas using good old JavaScript Objects.

Size of the Canvas Element

Other important idea on the canvas size issue :

When you specify the size of the canvas's width and height attributes, you are specifying the size of the canvas's rendered area. When the size is specified via CSS, you scale the rendered area to match the defined CSS size.

It's not recommended using CSS for specifying the canvas’s size. Instead, we can set the width and height canvas attributes directly -in HTML and programmatically (dynamically):

var myCanvas = document.querySelector("#myCanvas");
myCanvas.width = 350;
myCanvas.height = 250;

We can use the resize event if necessary:

window.addEventListener("resize", resizeCanvas, false);

More: kirupa.com - Resizing the HTML Canvas Element

Drawing commands

Two clarifying paragraphs about the canvas API drawing commands, some of the are quite confusing:

(...) the stroke() method ensures all of the stroke-related commands such as drawing our line, adjusting our line thickness, and setting the line color are all pushed live. (...) The fill() method ensures that anything related to the fill of the shape are pushed live as well.

The thing that confused me when working with the canvas is that beginPath is the only thing you need to signal your intent to draw a new shape. For every new shape you want to draw, just call beginPath. Any stroke, fill, or draw-related properties you set earlier stay with the earlier shape. Nothing gets carried over to your new shape. This confusion was compounded every time I saw the closePath method. As we saw in the previous tutorial, all closePath does is draw a line from where you are now to your shape's starting point. You don't have to specify it if you are going to manually close the shape using lineTo, and you certainly don't need to pair it with beginPath to signal the closing of your shape.

Getting the Exact Mouse Position in Canvas

There are a lot properties to get the mouse coordinates from the MouseEvent: clientX, pageX, x, offsetX, etc.. Some of them are not very good supported for browsers.
Maybe the method based in Element.getBoundingClientRect() is better. Check it out.

The clientX and clientY properties store the mouse position from the top-left corner of the browser window. They don't take into account where the canvas element is located on the page. We need to know canvas exact position in order to get the mouse coordinates on the canvas; this a complete function to get it (it takes into account the scroll):

// Helper function to get an element's exact position
function getPosition(el) {
  var xPos = 0;
  var yPos = 0;
 
  while (el) {
    if (el.tagName == "BODY") {
      // deal with browser quirks with body/window/document and page scroll
      var xScroll = el.scrollLeft || document.documentElement.scrollLeft;
      var yScroll = el.scrollTop || document.documentElement.scrollTop;
 
      xPos += (el.offsetLeft - xScroll + el.clientLeft);
      yPos += (el.offsetTop - yScroll + el.clientTop);
    } else {
      // for all other non-BODY elements
      xPos += (el.offsetLeft - el.scrollLeft + el.clientLeft);
      yPos += (el.offsetTop - el.scrollTop + el.clientTop);
    }
 
    el = el.offsetParent;
  }
  return {
    x: xPos,
    y: yPos
  };
}

We have to use this function smartly: when the canvas element gets moved in the browser client area. Dealing with this is easy thanks to the scroll and resize events. Use example:

window.addEventListener("scroll", updatePosition, false);
window.addEventListener("resize", updatePosition, false);
 
function updatePosition() {
  canvasPos = getPosition(canvas);
}   


// In other function we get the exact mouse X and Y coordinates
var mouseX = e.clientX - canvasPosition.x;
var mouseY = e.clientY - canvasPosition.y;

More on the topic: kirupa.com - Working with the Mouse

Canvas transformations

I extract a good explanation on this from the book online Eloquent Javascript:

There are several other methods besides scale that influence the coordinate system for a canvas. You can rotate subsequently drawn shapes with the rotate method and move them with the translate method. The interesting —and confusing— thing is that these transformations stack, meaning that each one happens relative to the previous transformations.

(...)

It is possible to save the current transformation, do some drawing and transforming, and then restore the old transformation. This is usually the proper thing to do for a function that needs to temporarily transform the coordinate system. First, we save whatever transformation the code that called the function was using. Then, the function does its thing (on top of the existing transformation), possibly adding more transformations. And finally, we revert to the transformation that we started with.

The save and restore methods on the 2D canvas context perform this kind of transformation management. They conceptually keep a stack of transformation states. When you call save, the current state is pushed onto the stack, and when you call restore, the state on top of the stack is taken off and used as the context’s current transformation.


Very good info about canvas: kirupa.com - Working with the Canvas

Other good introductory resource: Canvas - Eloquent Javascript

POST A COMMENT: