Tutorial hero
Lesson icon

Using the HTML5 Canvas Element in Sencha Touch: Part 2

Originally published December 18, 2014 Time 15 mins

This is the second part of a tutorial series where we will be building a drawing application using the HTML5 canvas element in Sencha Touch. Click here for the first part.

Last time we talked about what the HTML5 canvas element is and a little bit about how to use it. I mentioned we would be building an application that would allow the following:

1. Users will be able to draw with multiple colours and tools onto a HTML5 canvas element
2. Users will be able to save the image they created to their device.

and also possibly:

3. Users will be able to import a photo from their photo library and edit it.

These requirements are not set in stone and I’m happy to incorporate feedback from readers if there’s something in particular you would like to see.

Building the Canvas Component

In this part of the tutorial, we are going to be building a custom Sencha Touch component to act as our canvas. This will provide us an area to draw on, and some functions to control the canvas such as:

  • Setting the brush colour
  • Setting the brush size
  • Setting the brush type
  • Clearing the canvas

This should act as a pretty good introduction to building Sencha Touch components if you have never built one before – for a far more in depth tutorial though I recommend checking out the Building Sencha Touch Custom Components tutorial on the Sencha Touch blog.

We will be creating a new custom component, but it is based on the Signature Pad Field component available in the Sencha Market. This was created by Jeff Wooden so credit goes to him for a lot of the hard work on this.

1. Create a new component file

First we are going to create a new file in the touch/src/ux folder and name it CanvasDraw.js:

Ext.define('Ext.ux.CanvasDraw', {
  extend: 'Ext.Component',
  xtype: 'canvasdraw',

  template: [
    {
      reference: 'canvas',
      tag: 'canvas',
      classList: [Ext.baseCSSPrefix + 'canvas'],
    },
  ],

  config: {
    /**
     * @cfg brushColor
     * The color that will be used for drawing
     * @accessor
     */
    brushColor: '#000',

    /**
     * @cfg brushSize
     * Used to determine line width, which will
     * control the thickness of the "brush"
     * @accessor
     */
    brushSize: 5,

    /**
     * @cfg brushType
     * The type of brush to be used
     * can be set to 'eraser' or 'normal'
     * @accessor
     */
    brushType: 'normal',
  },
});

We define the template which creates the skeleton of the component – that is the HTML node that will need to be created in the DOM. In this case we want to create a <canvas> element.

The tag is the HTML element that will be added (if you change this to ‘img’ for example and take a peek at the DOM you will see that it creates an tag instead).

The classList defines the class of the element, Ext.baseCSSPrefix will add the CSS prefix which will usually be ‘x’. If you look at other nodes you will notice most things have a class name of ‘x-button’, ‘x-toolbar’ and so on. This will give our element a class of ‘x-canvas’.

The reference allows you to refer to that node inside of your component. If we give it a reference of canvas then we can access it by using this.canvas. Similarly if we decided to name the reference spoon, we could access the same node using this.spoon.

We also add some config options in here including brushColor, brushSize and brushType. This allows us to set some default values to reference when we are using the canvas.

More importantly though it provides us with a way to control the way the canvas is painted from outside of the component. For each config option that is specified a getter and setter function are automatically created. So if we take brushColor as an example, we would automatically be able to call setBrushColor(value) to set a different colour and getBrushColor() to retrieve the current colour.

2. Initialize the canvas

Now we set up our initialize function for the component (this will run when the component is created) and also an initCanvas function:

initialize: function(){

          var me = this;
          me.callParent();

          me.on('painted', 'initCanvas', me);

          me.element.on({
               touchstart: 'onTouchStart',
               touchend: 'onTouchEnd',
               touchmove: 'onTouchMove',
               scope: me
          });

     },

     initCanvas: function(){

          var me = this,
               canvas = me.element.dom.firstChild;

          me.painting = false;
          me.setSize();

          if(canvas){
               me.canvas = canvas;
               canvas.setAttribute("width", me.width);
               canvas.setAttribute("height", me.height);
               me.context = canvas.getContext("2d");
               me.context.fillStyle = me.brushColor;
          }
     },

Once we detect the ‘painted’ event, which fires when the component is rendered on the screen, we call the initCanvas event. This takes care of setting up the canvas (as I mentioned in the last post we have to get the “context” of the canvas before we can start drawing on it) and also sets its size. Setting the size is a bit trickier since it is not always going to be the same size, we want it to expand to take up the full space of its container.

To do this we create a setSize() function, which works out how big its parent is and then sets the canvas size to that (add this function to the code above):

setSize: function(width, height){

          var me = this;

          if(!width){
               var totalOffsetX = 0;
               var totalOffsetY = 0;
               var canvasX = 0;
               var canvasY = 0;
               var currentElement = me.element.dom.firstChild;

               do {
                    totalOffsetX += currentElement.offsetLeft;
                    totalOffsetY += currentElement.offsetTop;
               }
               while(currentElement = currentElement.offsetParent);

               me.width = window.innerWidth - totalOffsetX;
               me.height = window.innerHeight - totalOffsetY;
          }
          else {
               me.width = width;
               me.height = height;
          }
     },

There’s some funny stuff going on here. We’re using the code created by Jeff to determine where a click or touch was on the screen (we will add this soon) but instead we’re using it to determine where the canvas is on the screen. Since we have a toolbar at the top of the page its position is offset a little bit so we can’t just use window.innerWidth and window.innerHeight to determine the size we need.

To finish off the initialisation process we set up some event listeners:

  • touchstart
  • touchend
  • touchmove

We need to capture these events so we know when the user is attempting to draw on the canvas and what they are trying to do. These events will pass the coordinates of the touch onto the functions we will set up to draw on the canvas:

  • onTouchStart
  • onTouchEnd
  • onTouchMove

3. Determining coordinates of touch events

As I mentioned above, Jeff created some code in his own component to determine where a touch event occurred on the screen relative to the canvas. We don’t want to know that a touch occurred 200 pixels from the top of the screen, we want to know how far from the top of the canvas it occurred. So if we started drawing 200 pixels from the top of the screen, and our toolbar is 40 pixels high, then the actual y coordinate we want is 160.

To calculate this, add the following function:

relMouseCoords: function(e){
          var me = this;
          var totalOffsetX = 0;
          var totalOffsetY = 0;
          var canvasX = 0;
          var canvasY = 0;
          var currentElement = me.element.dom.firstChild;

          do {
               totalOffsetX += currentElement.offsetLeft;
               totalOffsetY += currentElement.offsetTop;
          }
          while(currentElement = currentElement.offsetParent);

          canvasX = e.pageX - totalOffsetX;
          canvasY = e.pageY - totalOffsetY;

          return {x:canvasX, y:canvasY};
     },

4. Setting up the touch events

Previously we set up some listeners for some touch events, now we are going to create their corresponding functions:

onTouchStart: function(e) {

         var me = this,
              coords = me.relMouseCoords(e);

         me.painting = true;
         me.iLastX = coords.x;
         me.iLastY = coords.y;

         //Still draw if a user just taps,
         //Move 1 pixel so a line can be drawn
          me.context.lineJoin = "round";
          me.context.beginPath();
          me.context.moveTo(me.iLastX, me.iLastY);
          me.context.lineTo(me.iLastX + 1, me.iLastY);
          me.context.closePath();
          if(me.getBrushType() == 'eraser'){
               me.context.strokeStyle = '#fff';
          }
          else {
               me.context.strokeStyle = me.getBrushColor();
          }
          me.context.lineWidth = me.getBrushSize();
          me.context.stroke();

    },

    onTouchEnd: function(e) {
         var me = this;

         me.paining = false;
         me.iLastX = -1;
         me.iLastY = -1;
    },

    onTouchMove: function(e) {

         var me = this;

         if(me.painting){

              var coords = me.relMouseCoords(e),
                   iX = coords.x,
                   iY = coords.y;

              me.context.lineJoin = "round";
              me.context.beginPath();
              me.context.moveTo(me.iLastX, me.iLastY);
              me.context.lineTo(iX, iY);
              me.context.closePath();
              if(me.getBrushType() == 'eraser'){
                   me.context.strokeStyle = '#fff';
              }
              else {
                   me.context.strokeStyle = me.getBrushColor();
              }
              me.context.lineWidth = me.getBrushSize();
              me.context.stroke();
              me.iLastX = iX;
              me.iLastY = iY;
         }
    },

We’re listening for the touchStart and touchEnd events to determine when we should start and stop painting, and we’re using the touchMove event to determine where to draw our line.

You will notice that we are calling me.getBrushColor() and me.getBrushSize(), this will pull the values specified in the config. This way, we can update the values by calling setBrushColor and setBrushSize to effect what is being drawn on the screen. We also allow the brush type to be set, which can either be set to ‘normal’ or ‘eraser’. If it is set to eraser we are just setting the colour to white – this is a bit pointless since we could have just used setBrushColor() to achieve the same thing, but I’ve done it this way for demonstration purposes. We could also create a different brush type that would draw in a different way, maybe we could create a stamp for example.

5. Clearing the canvas

Finally, we provide a function that will clear the canvas:

resetCanvas: function(){
         var me = this,
              canvas = me.element.dom.firstChild,
              context = canvas.getContext("2d");

         context.clearRect(0,0,canvas.width,canvas.height);
         context.beginPath();
    }

The component is now complete and ready to be used! If you reference the component by using its xtype (canvasdraw) somewhere in your application you will be able to see it in action. In the next part we will continue building our drawing application by creating an interface to interact with this component. This will include buttons to allow the user to change the colour and brush size to whatever they wish.

Click here for Part 3

Learn to build modern Angular apps with my course