A simple drawing app using JavaScript

The HTML Canvas element was introduced by Apple in 2004. It was originally created to power the Dashboard concept introduced in OS X 10.4. The Canvas element was a part of Apple WebKit from 2004, in 2005 it became a part of Mozilla browsers and then Opera. Today, all browsers worth using sports support for the Canvas element. In this article I'll investigate demo involving a simple line drawing on a Canvas element.

To be a proper red line drawing application today there are several considerations to be taken into account.

  • You must support both touch devices and dusty old mouse enabled devices

  • You must take Hi-res or retina devices into consideration. You wouldn't want blurry red lines now would you.

  • The thing needs to be performant and cool..and red

Take a look at the full CoffeeScript code for the demo. It has a bunch of comments.

Supporting touch and mouse events

if 'ontouchstart' of window
# .. then switch to touch mode
@mode = 'touch'

From line 8 in the code. Here we check if the event «ontouchstart» exists in the window object. If it does we know that this device supports touch events. We then keep this result in the «mode» property for use later on. Easy peasy. Let's add som listeners.

if @mode is 'touch'
canvas.addEventListener 'touchstart', @, false
canvas.addEventListener 'touchmove', @, false
canvas.addEventListener 'touchcancel', @, false
else
canvas.addEventListener 'mousedown', @, false
canvas.addEventListener 'mouseup', @, false

We use the «mode» property and add touch and mouse listeners. Note that we do not add any listeners for when the mouse is moving, this we do in the mouse up and down events. See the section at the end of the article as an explanation as to why.

So, what the hell does it mean to add a listener to "@"? In CoffeeScrip the "@" is the same as "this" in JavaScript. By adding event listeners to "this" we can implement the half-magical «handleEvent» method. This method will not only handle all events on "this", but it will also maintain scope, removing the need for «Function.bind». If you didn't know that, you mind has just been blown - right?! Moving on.

Supporting retina quality graphics

This is a tiny bit trickier, but not much. What we need is to know the scale factor of the display of the device. We can then use this number to calculate the size of the canvas itself and how lines are drawn.

Let's start with finding the scale factor.

@scaleFactor = window.devicePixelRatio || 1;

If there is a property on window called «devicePixelRatio» we use that value, if not we default to 1, which basically means that it's a "normal" display or a piece of shit old browser. The «devicePixelRatio» will output a multiplier, like 2 for the iPad Mini Retina. This number gives us the value on which to multiply.

Scaling the canvas element correctly

Calculating the size of the actual canvas element looks something like this (if you want the canvas to span the entire browser window).

canvas.width = window.innerWidth * @scaleFactor
canvas.height = window.innerHeight * @scaleFactor

On a "normal" display the width of the canvas would be equal to the inner width of the window. On an iPad Mini Retina it would be «innerWidth x 2», because the «scaleFactor» of the iPad Mini Retina is 2.

It's important to note that the canvas element is now twice or more the size of what you intend to display it at. You fix that by setting the style width and height to the intended display size, which in the case of an iPad Mini Retina will be half the size of the canvas backing store (the actual drawing size).

Scaling the drawing context

@ctx = canvas.getContext '2d'
@ctx.strokeStyle = 'rgba(255,0,0,1)'
@ctx.lineWidth = 5 * @scaleFactor

The «ctx» property holds the 2d drawing context of the canvas. On the second line we tell the context that any lines drawn, should be red - of course. On the third line the thickness of the line is set and like we did with the canvas backing store we multiply with the scale factor. Remember that a canvas on the iPad Mini Retina is twice the size of what it's displayed as (yep, yep). If we didn't multiply the line thickness if would actually display at 2.5..and that would be stupid because we want it to look the same everywhere, except it will look sharper on retina displays.

@ctx.moveTo e.touches[0].pageX * @scaleFactor, e.touches[0].pageY * @scaleFactor
#...
@ctx.lineTo e.touches[0].pageX * @scaleFactor, e.touches[0].pageY * @scaleFactor

The same multiplication also needs to happen every time we either move the drawing pointer or draw a line. If we didn't do this the line would be drawn with an offset of the finger or the cursor - again, we don't want that. And that's it, ha!

Other stuff

For mouse mode, it's also a good idea to remove the «mosemove» listener when the move isn't pressed. This way you won't get a bunch of events firing all over the place when you really don't need them.

That's all folks.

Retina images with «img srcset»

retina.png

Handling High-DPI or Retina™ images is probably one of the more discussed topics in the front-end community today. The reason for this might be that front-end developers are working with technologies that hasn't quite caught up with the progress in this particular part of device technologies.

To give you an idea of what I'm talking about; A lot of mobile devices have High-DPI displays, first and most notably was the iPhone 4 which Apple used to coin the term Retina™. This kind of display have a 2x pixel density (vertically and horizontally), which means that there are 4 pixels for every one point.

The challenge

For developers and designers this pixel doubling means that artwork like images will need to be double the size it should be displayed at (horizontally and vertically). This in by itself is quite easy to fix, you just set the width and the heigh of the image in the «img» tag and then refer to an image twice the area of the defined width and height. The problem that brings is that non-retina devices, which often has less available memory will need to download a file which is double of the needed size and hence consumes much more memory and bandwidth than a normal image would. The real solution is to serve high-DPI images only to those device which can support them.

My approach

For my demo-site I'm using the W3C srcset attribute to handle High-DPI images. This allows you to set multiple sources for one image and at the same time maintain backwards compatibility with older browsers since older browsers will simply ignore the «srcset» attribute. You define it like so:

img src="normal-image.jpg" srcset="retina-image.jpg 2x"

Notice the "2x" part of the «srcset» URL. This is where you define the resolution of the image. The «srcset» attribute can take multiple comma separated image values with different parameters. If you need to display different images for different screen sizes and so on you could set multiple images here. For my demo-site I'm only using the 2x option and specifying only one alternative image.

Browser support and polyfilling

At the time of writing this only WebKit Nightly builds and Chromium supports the «srcset» attribute, but the good news is that it is coming. However, we need a solution now, hence enter the polyfill.

There are a several complete polyfills for «srcset», but they implement the whole spesification and I was really just looking for the 2x part, so I decided to create my own simple polyfill.

As you can see this is a tiny script. In short it tests if «srcset» isn't supported and whether the «devicePixelRatio» is more than one. If so, it scoops up all the images with a «srcset» attribute and swaps the image source for the one in the «srcset» attribute.

Note that this script assumes that all you want is 2x images, it is not a complete «srcset» polyfill!

So that is how I handle Hi-DPI or Retina™ images in my test-site, feel free to use it if you need it.