Last week I released a two part screencast where I built a simple drawing application in Ionic live on screen. Since I’m just making things up as I go along in these style of videos, the end result usually works but is lacking in polish.
As such, I like to do a follow-up blog post where I spend some time finishing up the application a bit and then writing a more detailed tutorial on creating the application. In this tutorial, we will walk through building a simple drawing application in Ionic using an HTML5 canvas. Users will have the ability to draw with a variety of different brush sizes and colours, they will be able to erase parts of the drawing, or delete the entire canvas and start again.
If you are unfamiliar with the <canvas>
element, it basically provides an area on the screen that we can “draw” on through interacting with a Javascript API. By using methods like lineTo
, stroke
, and fill
we can create various lines and shapes in a variety of colours on the canvas.
This is what it will look like when we are finished:
Before We Get Started
Last updated for Ionic 3.1.0
Before you go through this tutorial, you should have at least a basic understanding of Ionic concepts. You must also already have Ionic set up on your machine.
If you’re not familiar with Ionic already, I’d recommend reading my Ionic Beginners Guide or watching my beginners series first to get up and running and understand the basic concepts. If you want a much more detailed guide for learning Ionic, then take a look at Building Mobile Apps with Ionic.
1. Generate a New Ionic Application
We’re going to start by generating a new blank Ionic application, to do that just run the following command:
ionic start ionic-drawing blank
Once that has finished generating, you should make it your working directory by running the following command:
cd ionic-drawing
We will be implementing the functionality for the canvas through a component. Rather than hard coding this into one of our pages, by creating a custom component that implements the functionality we would be able to easily drop this canvas in wherever we want. Let’s create the custom component for that now.
Run the following command to generate the CanvasDraw component:
ionic g component CanvasDraw
In order to be able to use this component throughout the application, we will also need to add it to our app.module.ts file.
Modify src/app/app.module.ts to reflect the following:
import { BrowserModule } from '@angular/platform-browser';
import { ErrorHandler, NgModule } from '@angular/core';
import { IonicApp, IonicErrorHandler, IonicModule } from 'ionic-angular';
import { SplashScreen } from '@ionic-native/splash-screen';
import { StatusBar } from '@ionic-native/status-bar';
import { MyApp } from './app.component';
import { HomePage } from '../pages/home/home';
import { CanvasDraw } from '../components/canvas-draw/canvas-draw';
@NgModule({
declarations: [MyApp, HomePage, CanvasDraw],
imports: [BrowserModule, IonicModule.forRoot(MyApp)],
bootstrap: [IonicApp],
entryComponents: [MyApp, HomePage],
providers: [
StatusBar,
SplashScreen,
{ provide: ErrorHandler, useClass: IonicErrorHandler },
],
})
export class AppModule {}
2. Create the Basic Drawing Functionality
To start off with, we are just going add a basic implementation of the canvas. We will add the canvas to our template, and set up some basic functionality that will handle drawing a simple line onto the canvas (wherever the user moves their finger).
Modify src/components/canvas-draw/canvas-draw.html to reflect the following:
<ion-toolbar id="top-toolbar"> </ion-toolbar>
<canvas
#myCanvas
(touchstart)="handleStart($event)"
(touchmove)="handleMove($event)"
></canvas>
<ion-toolbar id="bottom-toolbar"> </ion-toolbar>
In the code above, we have added the <canvas>
element to the template for the component, as well has some toolbars that we will make use of later. We add a template variable of #myCanvas
so that we can easily get a reference to this element in a moment, and we also set up event bindings for touchstart
and touchmove
. These event bindings are what we will hook into to draw onto the canvas using the Javascript API.
So that the canvas displays correctly, we will also need to add a few styles to the component.
Modify src/components/canvas-draw/canvas-draw.scss to reflect the following:
canvas-draw {
height: 100%;
width: 100%;
display: block;
#top-toolbar {
position: absolute;
top: 0;
}
#bottom-toolbar {
position: absolute;
bottom: 0;
}
}
Now let’s get into implementing the TypeScript file for the component, which will contain most of the interesting stuff.
Modify src/components/canvas-draw/canvas-draw.ts to reflect the following:
import { Component, ViewChild, Renderer } from '@angular/core';
import { Platform } from 'ionic-angular';
@Component({
selector: 'canvas-draw',
templateUrl: 'canvas-draw.html'
})
export class CanvasDraw {
@ViewChild('myCanvas') canvas: any;
canvasElement: any;
lastX: number;
lastY: number;
currentColour: string = '#1abc9c';
brushSize: number = 10;
constructor(public platform: Platform, public renderer: Renderer) {
console.log('Hello CanvasDraw Component');
}
ngAfterViewInit(){
this.canvasElement = this.canvas.nativeElement;
this.renderer.setElementAttribute(this.canvasElement, 'width', this.platform.width() + '');
this.renderer.setElementAttribute(this.canvasElement, 'height', this.platform.height() + '');
}
handleStart(ev){
this.lastX = ev.touches[0].pageX;
this.lastY = ev.touches[0].pageY;
}
handleMove(ev){
let ctx = this.canvasElement.getContext('2d');
let currentX = ev.touches[0].pageX;
let currentY = ev.touches[0].pageY;
ctx.beginPath();
ctx.lineJoin = "round";
ctx.moveTo(this.lastX, this.lastY);
ctx.lineTo(currentX, currentY);
ctx.closePath();
ctx.strokeStyle = this.currentColour;
ctx.lineWidth = this.brushSize;
ctx.stroke();
this.lastX = currentX;
this.lastY = currentY;
}
}
This implements the basic functionality for our canvas and will give the user the ability to draw a simple line. We’ve set up a few member variables, so let’s talk through those first:
@ViewChild('myCanvas') canvas: any;
canvasElement: any;
lastX: number;
lastY: number;
currentColour: string = '#1abc9c';
brushSize: number = 10;
We grab a reference to the canvas element by using @ViewChild
and supplying it with the template variable we added earlier. We have a canvasElement
member that will hold a reference to the actual DOM element for the canvas, a lastX
and lastY that will store the coordinates of the users last touch on the screen, and a
currentColourand
brushSize` for determining the size and colour of the brush strokes we will be drawing on the canvas.
In the ngAfterViewInit
function, we create a reference to the nativeElement
for the canvas (which is the actual DOM element that we want to work with, not just the object that @ViewChild
returns), and we set the width
and height
of the canvas to equal whatever the current width and height of the device is.
The handleStart
function handles recording where the user has tapped on the screen. This is important because in order to draw a line we need to know the x
and y
coordinates of where we want to draw the line from and where we want to draw the line to.
The handleMove
function is what handles drawing the line itself. We get the new x
and y
coordinates, and then we interact with the canvas by using its context
. This allows us to call the various methods of the Canvas API. In our case, we define a path from the last coordinates to the new coordinates, and then we stroke
that path (which basically means “draw a line over it”) with the current brush colour and with a thickness of whatever the current brush size is. After that, we set the last coordinates to be the coordinates we just used so that next time the function is triggered that will become the beginning of the path.
3. Add the Component to the Home Page
At this point, although we have not finished implementing all the functionality of the component, it should be in working order. So, we are going to add this component to our home page now and then continue adding some functionality to it.
Modify src/pages/home/home.html to relect the following:
<ion-content no-bounce>
<canvas-draw></canvas-draw>
</ion-content>
If you were to fire up the application in your browser now, you should be able to draw onto the canvas. Since we are listening specifically for touch events, you will have to make sure that you use the Chrome DevTools emulator to use the application or run it on a device (otherwise touch events will not be triggered).
4. Add Brush Sizes and Colours
Now we are just going to make some slight modifications to allow the user to change the colour and size of the brush, as well as adding the ability to erase or delete their drawing.
Modify src/components/canvas-draw/canvas-draw.html to reflect the following:
<ion-toolbar id="top-toolbar">
<ion-buttons left>
<button
*ngFor="let colour of availableColours"
icon-only
ion-button
(click)="changeColour(colour)"
>
<ion-icon [style.color]="colour" name="brush"></ion-icon>
</button>
</ion-buttons>
<ion-buttons right>
<button
style="border: 1px solid #cecece;"
ion-button
icon-only
style.color="#fff"
(click)="changeColour('#fff')"
>
<ion-icon style="color: #fff;" name="square"></ion-icon>
</button>
</ion-buttons>
</ion-toolbar>
<canvas
#myCanvas
(touchstart)="handleStart($event)"
(touchmove)="handleMove($event)"
></canvas>
<ion-toolbar id="bottom-toolbar">
<ion-buttons left>
<button color="dark" ion-button icon-only (click)="clearCanvas()">
<ion-icon name="trash"></ion-icon>
</button>
</ion-buttons>
<ion-buttons right>
<button color="dark" ion-button icon-only (click)="changeSize(5)">
<ion-icon style="font-size: 0.25em;" name="radio-button-on"></ion-icon>
</button>
<button color="dark" ion-button icon-only (click)="changeSize(10)">
<ion-icon style="font-size: 0.5em;" name="radio-button-on"></ion-icon>
</button>
<button color="dark" ion-button icon-only (click)="changeSize(20)">
<ion-icon style="font-size: 1em;" name="radio-button-on"></ion-icon>
</button>
<button color="dark" ion-button icon-only (click)="changeSize(50)">
<ion-icon style="font-size: 2em;" name="radio-button-on"></ion-icon>
</button>
<button color="dark" ion-button icon-only (click)="changeSize(200)">
<ion-icon style="font-size: 3em;" name="radio-button-on"></ion-icon>
</button>
</ion-buttons>
</ion-toolbar>
We have added some buttons to the toolbars now. In the top toolbar we loop over an array of availableColours
, which we will create shortly, and we create a button for each of those colours. These buttons will trigger a changeColour
function and then that colour
will be passed into the function.
We take a similar approach with the erase button that is also in the top toolbar, except this one will sit off by itself so we just define it manually and pass in #fff
to the function ourselves.
We have taken a similar approach for the bottom toolbar, except these buttons call a different function called changeSize
. This will, of course, allow us to change the size of the brush.
Let’s implement these functions now.
Modify src/components/canvas-draw/canvas-draw.ts to reflect the following:
import { Component, ViewChild, Renderer } from '@angular/core';
import { Platform } from 'ionic-angular';
@Component({
selector: 'canvas-draw',
templateUrl: 'canvas-draw.html'
})
export class CanvasDraw {
@ViewChild('myCanvas') canvas: any;
canvasElement: any;
lastX: number;
lastY: number;
currentColour: string = '#1abc9c';
availableColours: any;
brushSize: number = 10;
constructor(public platform: Platform, public renderer: Renderer) {
console.log('Hello CanvasDraw Component');
this.availableColours = [
'#1abc9c',
'#3498db',
'#9b59b6',
'#e67e22',
'#e74c3c'
];
}
ngAfterViewInit(){
this.canvasElement = this.canvas.nativeElement;
this.renderer.setElementAttribute(this.canvasElement, 'width', this.platform.width() + '');
this.renderer.setElementAttribute(this.canvasElement, 'height', this.platform.height() + '');
}
changeColour(colour){
this.currentColour = colour;
}
changeSize(size){
this.brushSize = size;
}
handleStart(ev){
this.lastX = ev.touches[0].pageX;
this.lastY = ev.touches[0].pageY;
}
handleMove(ev){
let ctx = this.canvasElement.getContext('2d');
let currentX = ev.touches[0].pageX;
let currentY = ev.touches[0].pageY;
ctx.beginPath();
ctx.lineJoin = "round";
ctx.moveTo(this.lastX, this.lastY);
ctx.lineTo(currentX, currentY);
ctx.closePath();
ctx.strokeStyle = this.currentColour;
ctx.lineWidth = this.brushSize;
ctx.stroke();
this.lastX = currentX;
this.lastY = currentY;
}
clearCanvas(){
let ctx = this.canvasElement.getContext('2d');
ctx.clearRect(0, 0, this.canvasElement.width, this.canvasElement.height);
}
}
We’ve added our array of availableColours
now, you could extend this to include as many colours as you would like. We have also defined the additional functions for changing the brushSize
and currentColour
as well as a function that will clear the entire canvas.
If you load up the application now, you should see the finished product:
Summary
This is a very basic introduction to the powers of the <canvas>
element, but hopefully, it should highlight how you might go about integrating the canvas into an Ionic application.