Using HTML5 Canvas with Ionic

May 2, 2017, 3:14 pm Categories:

Categories

The Canvas API brings to HTML what was previously only possible with Flash subsequently allowing developers to tap into a wide range of possible options for further interactivity and aesthetics.

From drawing geometric shapes and manipulating image pixel data to creating advanced animations the HTML5 Canvas API opens up a world of creative possibilities for web and mobile applications.

In this tutorial we'll be looking at how to enable this API within Ionic/Angular and then use that to create a very basic project.

Before we do though let's take a moment to consider support and some of the potential issues that come with using this language feature.

Look before you leap

As with any 'cutting-edge' technology we begin by asking if we can realistically use this in our projects.

Thankfully the API has fairly widespread support as evidenced by the following minimum browser requirements:

  • IE9+
  • Edge 12+
  • Firefox 3.6+
  • Chrome 4+
  • Safari 4+
  • Opera 10.1+
  • iOS 3.2+
  • Android 3+

If browser versions prior to the above require support then the developer could implement the Canvas polyfill or look into some graceful degradation/fallback option such as using images, video or text for example.

Fortunately, as there's excellent support across web browsers and iOS & Android for the Canvas API, this isn't something we need to be concerned with - which means we can pretty much jump straight in!

Before we do however there are, depending on your perspective/particular needs, some potential pitfalls with this technology that you need to be aware of:

  • API can be complex and potentially daunting for new developers to get to grips with
  • Potentially slow if redrawing large numbers of pixels
  • No inherent state management (the developer needs to create their own logic to track and manage elements rendered with the API)

These are worth taking into consideration for larger, more complex projects (where system resources might be taxed more easily by use of the API) but for the purposes of this tutorial we can move safely forwards.

What we'll be building

Over the course of this article I'm going to walk you through enabling the Canvas API in Ionic/Angular to create a very simply geometric shape switcher like so:

This will simply rely on the API drawing methods to individually generate a circle, square or triangle (depending on which button component was activated) and render that particular shape to the HTML5 Canvas element in the template view.

Nothing overly complicated but enough to demonstrate how the Canvas API can be leveraged within an Ionic project.

Ready to jump in?

Let's start developing then!

Getting started

Open up your command line and generate a basic Ionic project named ion-canvas with the following commands:

ionic start ion-canvas blank

Other than running ionic serve this is all the command line work that we'll need for this project as we'll be relying on the Canvas API to perform the vast majority of the required work.

Now open the ion-canvas/src/pages/home/home.html file and replace its contents with the following mark-up:

<ion-header>
  <ion-navbar>
    <ion-title>
      Ionic Canvas
    </ion-title>
  </ion-navbar>
</ion-header>

<ion-content padding>
  

  <div class="ion-canvas">
      <canvas #canvas></canvas>
  </div>



   <button 
      ion-button 
      color="primary" 
      (click)="drawCircle()">
      Draw a circle
   </button>



   <button 
      ion-button 
      color="primary" 
      (click)="drawSquare()">
      Draw a Square
   </button>



   <button 
      ion-button 
      color="primary" 
      (click)="drawTriangle()">
      Draw a Triangle
   </button>



</ion-content>

This is fairly straightforward HTML that makes use of the following:

  • The canvas DOM element (wrapped inside a <div> tag) which implements a template reference variable of #canvas to allow Angular's ViewChild class to be able to access the Canvas API and its native methods
  • Button element with a click event that fires off the drawCircle method
  • Button element with a click event that fires off the drawSquare method
  • Button element with a click event that fires off the drawTriangle method

As you've probably guessed when each button's click event is fired off the associated method will be triggered and subsequently 'draw' the shape for that method to the canvas element.

Let's now take a look at the logic underlying and enabling this interaction with the canvas.

Managing the canvas

In the ion-canvas/src/pages/home/home.ts class we start by importing Angular's ElementRef and ViewChild modules which will allow us, shortly, to gain access to the Canvas DOM element:

import { Component, 
         ElementRef, 
         ViewChild } from '@angular/core';
import { NavController } from 'ionic-angular';

Within the HomePage class the following properties are defined which enable the Canvas DOM element to be managed/interacted with programatically:

  • _CANVAS - the class object which maps to the canvas DOM element
  • _CONTEXT - the class object for retrieving and working with the Canvas API drawing context

Notice how the @ViewChild decorator references the canvas template reference variable?

This is then assigned to a local property of canvasEl which means, thanks to Angular's ViewChild class, we can now plug directly into the Canvas API methods.

/**
  * 'plug into' DOM canvas element using @ViewChild 
  */
  @ViewChild('canvas') canvasEl : ElementRef;



/**
  * Reference Canvas object 
  */
  private _CANVAS  : any;



/**
  * Reference the context for the Canvas element 
  */
   private _CONTEXT : any;

Following from this we then use Ionic's ionViewDidLoad navigation lifecycle event to trigger the initialisation of the Canvas object (so we can connect with the actual canvas DOM element in the page template) and set the dimensions for the canvas before executing the drawCircle method to render a circle shape to the canvas HTML element.

Notice how the _CANVAS class object is able to connect with the canvas DOM element through use of nativeElement supplied by the ElementRef class?

FYI: Use of the ElementRef class to directly interact with the DOM is generally frowned upon due to the potential for introducing Cross-Site Scripting vulnerabilities and, as a result, is seen as a security risk.

In the context of this tutorial though I feel that potential is low enough to make this approach justified.

Just be aware of this security risk though when using Angular to manage the DOM in your own projects.

ionViewDidLoad() 
{
   this._CANVAS 	    = this.canvasEl.nativeElement;
   this._CANVAS.width  	= 500;
   this._CANVAS.height 	= 500;

   this.initialiseCanvas();
   this.drawCircle();
}

The initialiseCanvas method simply determines whether the Canvas drawing context (which provides methods and properties for interacting with the Canvas object) is able to be recognised by the browser.

If it is then we know this object is supported by the browser and the conditional check subsequently allows the application to execute the setupCanvas method.

If not supported by the browser then a fallback option could be displayed/used instead (in this tutorial I've simply omitted to use any fallback option but in a real world project it would be wise to handle this scenario - even as unlikely as it might be to occur in the context of a mobile application).

initialiseCanvas()
{
   if(this._CANVAS.getContext)
   {
      this.setupCanvas();
   }
}

The setupCanvas method defines a 2D drawing context which defines the API to be used for drawing to the canvas and manipulating its content.

The available API drawing contexts are as follows:

  • 2d
  • webgl (3d)

In order to draw geometric shapes and/or manipulate text and images using the Canvas API we'll be making use of the 2d drawing context.

We also set the background colour which is then assigned to the HTML Canvas element through the fillRect method (starting at x and y coordinates of 0, 0 and filling a width and height of 500 pixels):

setupCanvas()
{
   this._CONTEXT = this._CANVAS.getContext('2d');
   this._CONTEXT.fillStyle = "#3e3e3e";
   this._CONTEXT.fillRect(0, 0, 500, 500);
}

We also define a clearCanvas method which, as the names implies, is used to clear the canvas of its contents, essentially resetting that to a blank state before reinitialising for further usage:

clearCanvas()
{
   this._CONTEXT.clearRect(0, 0, this._CANVAS.width, this._CANVAS.height);
   this.setupCanvas();
}

Drawing API methods

With the configuration logic in place we now move onto defining the following 3 methods which allows us to draw individual geometric shapes to the canvas HTML element:

  • drawCircle
  • drawSquare
  • drawTriangle

Each of these will make use of different aspects of the Canvas drawing API which include:

  • beginPath - Creates a new drawing path
  • arc - Adds an arc to the drawing path
  • moveTo - Moves the starting point for the path to the specified x and y coordinates
  • lineTo - Connects the previous x and y coordinates to the currently specified x and y coordinates with a straight line (but doesn't actually draw the line)
  • lineWidth - Sets the thickness of the line
  • strokeStyle - Specifies the colour or style to be used for lines around shapes
  • stroke - Sets the stroke for the current path

Drawing a circle

Let's see how some of these drawing API properties and methods are implemented by starting with the drawCircle method:

drawCircle()
{
   this.clearCanvas();
   this._CONTEXT.beginPath();

   // x, y, radius, startAngle, endAngle
   this._CONTEXT.arc(this._CANVAS.width/2, this._CANVAS.height/2, 80, 0, 2 * Math.PI);      
   this._CONTEXT.lineWidth = 1;
   this._CONTEXT.strokeStyle = '#ffffff';
   this._CONTEXT.stroke();
}

You can see that a clearCanvas method is called first which simply 'resets' the canvas DOM element to a blank state allowing us to subsequently draw new objects onto there.

This is followed by using the drawing context object to implement the different Canvas API methods.

Out of these available methods and properties a circular shape is defined through use of the arc method like so:

this._CONTEXT.arc(this._CANVAS.width/2, this._CANVAS.height/2, 80, 0, 2 * Math.PI);

This helps create a circular shape through use of the following parameters:

  • x axis position - centre point of the canvas using the following math: this._CANVAS.width/2
  • y axis position - centre point of the canvas using the following math: this._CANVAS.height/2
  • radius - 80
  • startAngle - 0
  • endAngle - Calculated with the following math: 2 * Math.PI

As you can see generating a circle is made easy thanks to the arc method of the Canvas API. If we had to construct this with raw TypeScript the logic would become, in comparison, a lot more involved and challenging.

Drawing a square

This is much simpler than generating a circle as we only need to supply the x, y coordinates followed by the height and width values to the rect method of the drawing API:

drawSquare()
{
   this.clearCanvas();
   this._CONTEXT.beginPath();
   this._CONTEXT.rect(this._CANVAS.width/2 - 100, this._CANVAS.height/2 - 100, 200, 200);
   this._CONTEXT.lineWidth = 1;
   this._CONTEXT.strokeStyle = '#ffffff';
   this._CONTEXT.stroke();
}

If we wanted to generate a rectangle we would simply make the height and width values for the rect method differ to one another.

Can't get much simpler than that!

Drawing a triangle

This is a little trickier than generating a square or rectangle but simply involves repeated use of the Canvas lineTo method to define where the x,y coordinates will 'draw' the line to:

drawTriangle()
{
   this.clearCanvas();
   this._CONTEXT.beginPath();
   this._CONTEXT.moveTo(this._CANVAS.width/2 - 100, this._CANVAS.height/2 + 100);
   this._CONTEXT.lineTo(this._CANVAS.width/2 + 100, this._CANVAS.height/2 + 100);
   this._CONTEXT.lineTo(this._CANVAS.width/2, this._CANVAS.height/2);
   this._CONTEXT.lineTo(this._CANVAS.width/2 -100, this._CANVAS.height/2 + 100);
   this._CONTEXT.lineWidth = 1;
   this._CONTEXT.strokeStyle = '#ffffff';
   this._CONTEXT.stroke();
}

As with previous methods I rely on the use of math generated via the this._CANVAS.width and this._CANVAS.height values to manipulate where the triangle is drawn to.

The ion-canvas project

With the logic for managing the Canvas object now in place your ion-canvas/src/pages/home/home.ts file should resemble the following:

import { Component, 
         ElementRef, 
         Input, 
         ViewChild } from '@angular/core';
import { NavController } from 'ionic-angular';

@Component({
  selector: 'page-home',
  templateUrl: 'home.html'
})
export class HomePage {


   /**
     * 'plug into' DOM canvas element using @ViewChild 
     */
   @ViewChild('canvas') canvasEl : ElementRef;



   /**
     * Reference Canvas object 
     */
   private _CANVAS  : any;



   /**
     * Reference the context for the Canvas element 
     */
   private _CONTEXT : any;
   


   constructor(public navCtrl: NavController) 
   {

   }



   /**
     * Implement functionality as soon as the template view has loaded
     * 
     * @public
     * @method ionViewDidLoad
     * @return {none}
     */
   ionViewDidLoad() : void 
   {
      this._CANVAS 		    = this.canvasEl.nativeElement;
      this._CANVAS.width  	= 500;
      this._CANVAS.height 	= 500;

      this.initialiseCanvas();
      this.drawCircle();
   }



   /**
     * Detect if HTML5 Canvas is supported and, if so, configure the 
     * canvas element accordingly
     * 
     * @public
     * @method initialiseCanvas
     * @return {none}
     */
   initialiseCanvas() : void
   {
      if(this._CANVAS.getContext)
      {
         this.setupCanvas();
      }
   }




   /**
     * Create a circle using canvas drawing API
     * 
     * @public
     * @method drawCircle
     * @return {none}
     */
   drawCircle() : void
   {
      this.clearCanvas();
      this._CONTEXT.beginPath();

      // x, y, radius, startAngle, endAngle
      this._CONTEXT.arc(this._CANVAS.width/2, this._CANVAS.height/2, 80, 0, 2 * Math.PI);      
      this._CONTEXT.lineWidth   = 1;
      this._CONTEXT.strokeStyle = '#ffffff';
      this._CONTEXT.stroke();
   }




   /**
     * Create a square using canvas drawing API
     * 
     * @public
     * @method drawSquare
     * @return {none}
     */
   drawSquare() : void
   {
      this.clearCanvas();
      this._CONTEXT.beginPath();
      this._CONTEXT.rect(this._CANVAS.width/2 - 100, this._CANVAS.height/2 - 100, 200, 200);
      this._CONTEXT.lineWidth   = 1;
      this._CONTEXT.strokeStyle = '#ffffff';
      this._CONTEXT.stroke();
   }




   /**
     * Create a triangle using canvas drawing API
     * 
     * @public
     * @method drawTriangle
     * @return {none}
     */
   drawTriangle() : void
   {
      this.clearCanvas();
      this._CONTEXT.beginPath();
      this._CONTEXT.moveTo(this._CANVAS.width/2 - 100, this._CANVAS.height/2 + 100);
      this._CONTEXT.lineTo(this._CANVAS.width/2 + 100, this._CANVAS.height/2 + 100);
      this._CONTEXT.lineTo(this._CANVAS.width/2, this._CANVAS.height/2);
      this._CONTEXT.lineTo(this._CANVAS.width/2 -100, this._CANVAS.height/2 + 100);
      this._CONTEXT.lineWidth   = 1;
      this._CONTEXT.strokeStyle = '#ffffff';
      this._CONTEXT.stroke();
   }




   /**
     * Configure the Canvas element
     * 
     * @public
     * @method setupCanvas
     * @return {none}
     */
   setupCanvas() : void
   {
      this._CONTEXT = this._CANVAS.getContext('2d');
      this._CONTEXT.fillStyle = "#3e3e3e";
      this._CONTEXT.fillRect(0, 0, 500, 500);
   }




   /**
     * Reset the Canvas element/clear previous content
     * 
     * @public
     * @method clearCanvas
     * @return {none}
     */
   clearCanvas() : void
   {
      this._CONTEXT.clearRect(0, 0, this._CANVAS.width, this._CANVAS.height);
      this.setupCanvas();
   }


}

This along with the HTML amendments you made earlier should, after launching the ionic serve utility, result in the following screen captures when interacting with the application:

If you've got to this stage then congratulations you've successfully implemented the Canvas API within Ionic!

In Summary

Using HTML5's Canvas API within Ionic is relatively straightforward thanks to Angular's ViewChild and ElementRef classes.

In this tutorial we've created a very simply geometric shape renderer which makes heavy use of Canvas's drawing API methods.

There are many ways in which we could extend this tutorial such as adding form controls which allow the values for each geometric shape to be custom entered and manipulated or through adding the ability to render multiple canvases within the same drawing space.

I'll touch on further uses of the Canvas API over future tutorials but I hope, for now at least, you've found this introduction to implementing the Canvas API in Ionic useful.

If you enjoyed what you've read here then please sign up to my mailing list and, if you haven't done so already, take a look at my e-book: Mastering Ionic for information about working with different aspects of the Ionic Framework including plugins and UI components.

Tags

Categories

Post a comment

All comments are welcome and the rules are simple - be nice and do NOT engage in trolling, spamming, abusiveness or illegal behaviour. If you fail to observe these rules you will be permanently banned from being able to comment.

Top