Subtitel

A blog by Juri Urbainczyk | Juri on Google+ | Juri on Twitter | Juri on Xing

Tuesday, June 25, 2013

An HTML5 canvas component for ExtJS 4

As I described in another post, ExtJS 4 features components for graphics ("Ext.draw.Component") which are based on the standard HTML SVG tag. But what if we liked to use a <canvas> tag? ExtJS does not bring one of those - thus we have to create one.

We also need a nice little showcase. For that, I implemented a small application, which simulates a solar system, with a configurable number of planets, moons and stars. The application calculates the gravity which affects the objects and then determines their new positions. This is done repeatedly, leading to a simulated continuous motion. The positions (and trajectories) of all objects are displayed in the canvas element. Picture 1 shows the solar system simulator in action.
Picture 1: Solar system simulator written with the canvas component
I put the canvas component in a separate file (canvasPanelClass.js), thus it should be easily reusable. The component utilizes the powerful object-oriented features of ExtJS. It extends Ext.Panel and creates the canvas element in the constructor method. In the afterrender listener (when the component already is complete) it stores a reference to the canvas in a private variable (this.canvas = this.items.items[0].el.dom;).

After that, the canvas is ready to be used. The component already brings some important methods, e.g. drawCircle( 50, 50, 10, "red" ) which draws a red circle with a radius of 10 pixels around the point 50, 50.

And now, the most interesting part of the source code:

        Ext.define('CanvasPanelClass', {
            extend: 'Ext.Panel',
           
            gridColor: '',
            ctx: null,        // is set when rendered
            canvas: null,    // is set when rendered                       
            bodyStyle: { background: '#000' },
            frame: false,
            margin: '2 2 2 2',
           
            listeners: {
                afterrender: {
                    fn: function(){                       
                        this.canvas = this.items.items[0].el.dom;           
                        this.ctx = this.canvas.getContext("2d");                                       this.clear();                       
                    }
                }       
            },           
           
            constructor: function(config) {
               
                //define this here because we can set width and height
                this.items = {
                    xtype: 'box',
                    autoEl:{
                        tag: 'canvas',
                        height: config.height,
                        width: config.width
                    }
                };
               
                this.tempCanvas = document.createElement("canvas");
               
                CanvasPanelClass.superclass.constructor.call(this, config);
            },
                    
            drawRect: function( x, y, width, height, lineWidth, color ) {
                this.ctx.strokeStyle = color;
                this.ctx.lineWidth = lineWidth;
                this.ctx.strokeRect(x,y,width,height);               
            },
           
            fillRect: function( x, y, width, height, color ) {
                this.ctx.fillStyle = color;
                this.ctx.fillRect(x,y,width,height);               
            },       
           
            drawCircle : function( x, y, radius, color ) {
                this.ctx.beginPath();
                this.ctx.strokeStyle = color;
                this.ctx.fillStyle = color;
                this.ctx.arc( x, y, radius, 0, Math.PI*2, true );
                this.ctx.closePath();
                this.ctx.fill();       
            },           
           
            putPixel: function( x, y, size, color ) {
                this.ctx.fillStyle = color;
                this.ctx.fillRect(x,y,size,size);           
            },

                  
            clear: function() {
           
                // Store the current transformation matrix
                this.ctx.save();

                // Use the identity matrix while clearing the canvas
                this.ctx.setTransform(1, 0, 0, 1, 0, 0);
                this.ctx.clearRect ( 0, 0, this.getWidth(), this.getHeight() );

                // Restore the transform
                this.ctx.restore();                                      
            }


        }); // define CanvasPanelClass

Then, the canvas component can be used like in the following code snippet. (There is an array of canvasPanels because you might want to create more than one canvas.) The mousedown handler shows how an application can react on mouse clicks into the canvas.

      canvasPanel[0] = new CanvasPanelClass({
            height: 500,
            width: 500
      });              

      canvasPanel[0].on({
            mousedown: function(DOMevent) {

                             var canvasPanelX = this.getPosition()[0];
                             var canvasPanelY = this.getPosition()[1]
                             var point = DOMevent.getPoint();

                             var eventX = point.x-canvasPanelX;
                             var eventY = point.y-canvasPanelY;

                              // do something …

                             canvasPanel[0].clear();
                             canvasPanel[0].renderGrid(25*50/ZOOM,GRIDCOLOR);
            },
            element: 'body',
            scope: canvasPanel[i] //Ensure "this" is correct during handler execution
      });

      tablePanel.add(canvasPanel[0]); 
  
      canvasPanel[0].clear();    

      canvasPanel[0].putPixel( 50, 50, 1, "green" );

No comments:

Post a Comment