Lenscape, a canvas interaction tutorial
Recently I started playing with the HTML5 <CANVAS> object sometimes having fun and sometimes feeling pain. I would like to share here my pains with your all
I approached html drawing far in the past when svg was a weak light in the darkness of browser graphics. Mainly due to the lack of IE support for svg at the time I chose to create my own graphic library emulating pixel drawing with thousands of lilliputian <div>.
In the following years I had only sparse contacts with html graphics but always having in mind the svg approach, where a line, a circle or whatever is a solid xml tag.
e.g: <ellipse cx=”300″ cy=”150″ rx=”200″ ry=”80″ style=”fill:rgb(200,100,50);”/>
this is really “natural” for me, html-writer: every single element has its own tag, and we are all happy!
Last month here, in Open Lab, we decided to start developing our first game (first for the company but not for the people working in it) and we decided to develop it using HTML5!
It’s time for me to resurrect dormant skills, and have a look at what is happening in the world of html gaming.
Surprise! Nobody is using svg for gaming, the main road is deeply tracked through <canvas> fields; so then I started studying it.
First of all I got a punch in the stomach learning that a canvas is, as the word says, only a place where to “draw” something, not a place where to “store” something. That means that once you have put something in a canvas, there is no way to recall it, or even worst to detect its presence.
This implies that if you want, for instance, to do something when clicking on a “circle” you cannot “bind” an event on that circle, simply because that “circle” doesn’t exist. So if you need to interact with objects in the scene you need to work around it.
Actually this tutorial will explain how to approach canvas interaction, so this will result as a shortcut for avoiding the pain and getting only the fun part!
Disclaimer: I know there are lots of html gaming engines as Aves recently acquired by Zinga, CAAT by HyperAndroid, Strikedisplay by Josh Strike or Impact by Dominic Szablewski (this list is far from complete). So if you are not interested in going in depth in canvas technicalities, this is a good point where to stop reading.
If you are interested in going deeper, first of all I suggest you to have a look at what is happening in the canvas world: canvasdemos is a great source of inspiration. Then have a look to the canvas element reference, just to taste the complexity…
I chose as final destination of this short trip in the canvas world the construction of a simple application,that emulates a lens moving on a picture. In the meantime I’d like to lay the foundations of my own canvas animation(?) library.
The lens can be moved over the image, to get a detailed view, using drag&drop.
The first step is to have a large image, larger than the screen (e.g.: 4000 x 2000) with full resolution, and use it as canvas background.
In order to have a canvas background you can use at least two different approaches:
1) using CSS background-image. This is an interesting approach as the background is not in the canvas itself, so you do not need to redraw it once you clear the canvas. In my case this solution doesn’t fit as I need to stretch the image, and moreover this feature is not supported by all browsers, actually even canvas is not supported….
2) coping “bits” from an existing image to the canvas. I choose this approach even if this requires to redraw the background after each “clear” action, as this approach is supported by all browser canvas implementations.
First two rows of code: a general style for canvas and the “large” image
As the image is “large” everything starts at the “onload” event on the image, that may happen seconds after the page is loaded.
Just a couple of global variables to refer to the image, the stage, and the context.
img variable is used only for shortening code and is a jQuery proxy for the <img>. I used jQuery only because I’m used to use it, but it isn’t a requirement.
ctx is the canvas context and is the main entry point for canvas operation (have a look at the complete reference of 2d context); here context is reached via a global variable only for shortening code but the right place (“right” in terms of object modeling) should be in the “stage” object.
stage is the “object model” of our environment, this supplies the lack of state of canvas itself, it holds all the objects of our “world” in an array.
First setup fills variables and then initializes the stage:
We are creating a canvas with the same aspect ratio of the original image. The Canvas object is our model for html canvas, it exposes two methods: init and draw.
Init method is responsible for canvas creation, context acquisition, and events binding:
Draw method is called every time you need to refresh the scene (really often during interactions) and, in our example, is here only to repaint the background:
so, just clear and use the drawImage function to perform a bulk copy from our “large” off-screen image to the canvas. This is a sort of powerful bitBlit operation that supports crop, translate and resize operations.
Then the Lens object: it is responsible for holding the lens position and magnification. It exposes three methods: draw, moveTo, setMagnification.
The Lens constructor:
A little complication here is the fact that we are zooming over an image off-screen while we are moving over the canvas, so we need some scaling factor to calculate the position of the underlying point in the “large” image.
Then lens drawing.
The lens effect is rendered by copying a part of the “large” image, eventually magnified, by cropping the image in a round shape, and applying a radial gradient on an alpha channel.
Note once again the usage of drawImage method to copy and enlarge the image and the clip method for creating a circular clipping region. Then we will apply the radial gradient using color with an alpha channel.
The lens modeled using a js object makes it comfortable to have one or more instances of the same object:
Here you see two lenses with different sizes and different magnification factors.
Now everything is working but there is no interaction with the user.
Lets try to add drag&drop capability.
I have to cite html5canvastutorials that helped me understand the first steps in managing interactions.
In order to start dragging we have to intercept the mouseDown event on the lens, but as I told you before, canvas do not retain the state of the object you’ve drawn on, so you cannot bind any event on the circle containing the lens: you can only bind events on the whole canvas, so if the user clicks the mouse on the lens you will receive the event on the canvas, not on the circle.
How to determine if there is your lens under the mouse? A simple, mathematical solution is to calculate the distance between the mouse and the lens center (you have all you need, mouse x-y coordinates from the js event, and the lens center from our Lens object), then if distance is lower than the lens radius the click was inside, out otherwise.
This approach will work fine mainly for rectangles and circles, but how to manage a complex shape like the one in the picture? (yes, I know that this sucks!)
In this case the “math” approach is definitively more complex.
Hopefully canvas can answer the question “is this point in path?”. The “sole” limitation is that you can ask this question “only while” your are drawing, that in terms of canvas context is only between context.beginPath() and context.closePath() calls; so we have to store somewhere the mouse coordinates, then ask isPointInPath(mouseX, mouseY) and store the result on the Lens object.
Summing up: all events are binded to the canvas object, the stage redrawing is called on mouse move, and the “over” state is recorded on the Lens object. Mousedown, mouseup, and mouseleave only perform simple operations.
Stage holds the object currently in drag status.
Note that we move the object that is first dragged on top of the stage.stageObjects list; this will act as a z-order operation, bringing the object to the front.
All the material referred in this post is in my Licorize booklet for canvas related material: http://licorize.com/projects/rbicchierai/blogmaterial/canvas
I took the photos used as examples in my travels.