Responsive <canvas> elements

By | 20 February, 2016

tl;dr – If you want responsive canvases to be a part of your RWD website, use scrawl-canvas.

The Responsive Web Design (RWD) Wikipedia article defines RWD as: … an approach to web design aimed at crafting sites to provide an optimal viewing and interaction experience – easy reading and navigation with a minimum of resizing, panning, and scrolling – across a wide range of devices […] A site designed with RWD adapts the layout to the viewing environment by using fluid, proportion-based grids, flexible images, and CSS3 media queries.

The key phrase for this discussion is “flexible images”. Translated, this means that:

  1. images should display cleanly, within the bounds set for them, and scale to fit into the browser (or grid) view appropriately; and
  2. image filesize should be appropriate for the device consuming them – smaller files should be served to handheld devices, compared to the image files required to display on large screens.

HTML5 introduced the new <picture> element to meet the requirements of RWD flexible images. However this new element doesn’t (yet) solve the problem for images used as part of a <canvas> element’s display; by definition, canvas elements can only source their image inputs from <img>, <video> and other <canvas> elements.

A further problem is that CSS treats <canvas> elements in much the same way as it treats <img> elements – setting the width and height of a canvas via CSS can distort the canvas’s output. Canvas dimensions are set directly as attributes on the element (measured in px), and are not the same as CSS block dimensions which, for the purposes of RWD, should ideally be set in proportional units.

Finally, programming the interactions with a <canvas> element – by mouse or by touch – can be difficult at the best of times. But when trying to do this with a “flexible canvas” the task of converting mouse/touch coordinates (measured in px) into the correct position on a canvas can quickly turn into a nightmare.

Implementing a responsive canvas in a web page

If we want to implement a responsive canvas in a page layout we will have to tackle all the above issues, which we can break down as follows:

  1. Make the <canvas> element “flexible”
  2. Keep the <canvas> dimensions in proportion to prevent display distortions
  3. Use “flexible images” to reduce page weight and download times
  4. Accurately position graphics and text on the canvas, whatever its current dimensions
  5. Interact with the canvas, using both mouse and touch, accounting for its current dimensions

Make the <canvas> element “flexible”

There’s no getting away from the fact that if we want to resize the canvas, then we have to do it programmatically. Setting the canvas width and height values via CSS will change its visible size, but not its drawing dimensions. The output will rarely be crisp, especially if a canvas with small drawing dimensions is forced to cover a much larger visual area.

Instead, we have to use JavaScript to set the canvas drawing dimensions to match the visual display. One simple approach is to put the canvas inside a container which can be resized using CSS. Then each time a resize action takes place (for instance a mobile view changes from portrait to landscape) a JavaScript resize event listener – appropriately debounced – can interrogate the container to extract its new computed dimensions and apply those values to the <canvas> element’s width and height (not style.width or style.height) attributes.

This approach has the added benefit of being able to integrate the canvas with CSS media queries. However care should be taken when applying padding, border and margin values to the container: ideally these should all be set to zero, to minimize confusion when extracting its current dimensions. Such styling could instead be applied to an outer container.

One thing we must remember is that each time we change a canvas’s width or height, the browser will automatically clear the canvas display. For animated canvases (using a Request Animation Frame loop) this will not be an issue as the canvas will be redrawn up to 60 times each second. For static displays, though, we will need to redraw the canvas ourselves. If the canvas drawing routines are kept in a JavaScript function, we can make our work a lot simpler.

Keep the <canvas> dimensions in proportion

Scaling a canvas in line with its container is all well and good, but what happens if the container width halves while its height trebles? Most likely we will end up with a poorly positioned, or distorted, canvas scene. This could occur if the canvas is part of a fluid grid system where each cell in a row has equal height.

Solving this problem is a 2-step process. First, instead of getting the height value directly from the container (in the JavaScript resize event listener), we calculate it as a proportion of the width value. Designing the canvas scene so that its ratio of height to width never changes will keep the calculations simple.

Second, we can use CSS to position the container within the grid or page. For instance, the CSS object-fit property could do the job (with an appropriate polyfill for IE, of course).

Use “flexible images” to reduce download times

If we are going to use images in our canvas, then we need to use them “flexibly”. But as we saw above, we cannot (yet) make use of the inherently flexible <picture> element as our image source.

Fortunately most modern browsers now allow <img> elements to use the picture element’s srcset and sizes attributes. This allows us to specify a range of images which the browser can choose from, and outline parameters to help the browser choose the most appropriate image for its current display. Finally the src attribute can be used to specify the default image for browsers that do not support the new attributes.

The 2d Canvas API is, for once, kind to us when dealing with flexible images. The API doesn’t care about choosing an appropriate file to download. It cares only that the <img> element downloads a file, and uses that data as its image source.

There is one gotcha to watch out for with this approach. Certain browsers (Chrome, I’m looking at you!) have taken the functionality further: if the environment in which the image displays changes significantly, then the browser will dynamically download and display a more appropriate image file. This can have unexpected consequences for an unwary canvas implementation.

Accurately position graphics and text on the canvas

On the face of it, maintaining an accurate representation of a canvas display would seem to be one of the most difficult issues to solve. Canvas composition relies on lines, shapes, texts and images being absolutely positioned using pixel coordinates – and pixel coordinates, by definition, don’t scale.

In fact the solution is simple: use a second canvas. We can create a canvas element dynamically with JavaScript and add it to a document fragment which, unlike the DOM, is never displayed in the browser. By setting the dimensions of this hidden canvas to static values which match the proportions of the visible <canvas> element in the DOM, and making sure the invisible canvas is larger than the visible <canvas> is likely to be on desktop monitors, we can give ourselves a stable environment for composing our canvas output.

Canvas drawing now become another 2-step operation. First, draw everything onto the hidden canvas. Then, when this has completed, copy the invisible canvas display over to the visible <canvas>. Job done!

Interact with the canvas, using both mouse and touch

RWD assumes nothing about the device on which a web page will be displayed, thus it is up to us to add functionality for handling user interaction with the <canvas> element from both mouse and touch events. In an ideal world every browser would understand and use pointer events. We do not live in an ideal world; it is our job to make sure our responsive canvas can handle input from a variety of sources.

Just as important – possibly more important: don’t forget accessibility issues. Can a user interact with your canvas using the keyboard? Have you included descriptions alongside the canvas scene to explain its purpose and usability to those not using visual browser technology? Web designers and developers have a legal duty to make their sites accessible!

Whichever method we use to gather information on the coordinates of a user click or touch, this information will only relate to the visible <canvas> element. If (as recommended) we are using a hidden fragment canvas for most of our scene composition and interaction then those coordinates will need to be converted. For x coordinates, divide the value by the visible canvas width and multiply it by the hidden canvas width; use height values to get the y coordinate.

One last issue: scaling a canvas is not enough when it comes to RWD interactivity. We also need to make sure that the interactive hit zones on the canvas remain large enough for people to see, and use, on small mobile screens. This means we will have to add some checking functionality to our JavaScript canvas drawing functions which will size and position buttons and labels (small text is not legible text on a canvas) appropriately for the visible <canvas> element’s current dimensions.

Using Scrawl-canvas to generate responsive canvases

The tour page on this site meets the requirements for RWD: it uses a fluid grid for positioning components, and media queries to change the layout according to the environment in which the page displays. All images used on the site use srcset and sizes attributes in the <img> elements.

The main part of the page comprises twelve responsive canvases which all use the Scrawl-canvas JavaScript library to handle most of the issues raised above. In particular:

  • The tour page uses scrawl stacks to act as the canvas containers.
  • A JavaScript resize event listener updates the stack <div> dimensions – resizing the visible <canvas> is handled automatically by scrawl.
  • Scrawl treats each visible <canvas> as a Pad object, which creates a hidden canvas attached to a DOM fragment.
  • These base cell canvases are given static dimensions – for the tour page, we use 800px width and 400px height as we designed the tour page fluid grid columns to never exceed a width of 800px.
  • When resizing occurs scrawl automatically handles any outfall from a browser downloading a more appropriately sized image file.
  • We define and position scrawl entitys using relative (%-based) coordinates, which means we don’t have to worry about pixel calculations.
  • The scrawl display cycle handles updating the hidden canvas and copying the hidden canvas over to the visible canvas.
  • We use scrawl event listeners to monitor mouse, touch and pointer interactions with the canvas for each canvas; these event listeners automatically handle coordinate calculations for us.
  • We check the browser environment using the scrawl.device object, which allows us to tailor each canvas scene to match the browser’s, and device’s, capabilities.
  • We use the scrawl animation functionality to drive canvas animations and interactions; to minimise the tour page’s impact on the browser only one canvas is animated at any one time.
  • Many of the animation routines use scrawl tweens and timelines, which have no problem using relative (%-based) positions for their calculations.
Click click ...Tweet about this on TwitterShare on Google+Share on FacebookShare on TumblrShare on LinkedIn

Leave a Reply

Your email address will not be published. Required fields are marked *