HTML5 Canvas application with Undo and Redo functionality

There is not much we have to do add undo and redo functionality to a Canvas application. Here is simple application to demo how we can achieve that by saving the state after each event in an array and then add that to another array after undo so that we can redo. These are simple JavaScript arrays where we are storing the images using push() and pop() methods. The canvas image is retrieved using the context.toDataURL("image/png") method and when we undo or redo we take that image and paint it again on the canvas after clearing the current canvas. Just to complete the function we have added the clear button also to reset the canvas.


HTML5 Canvas application with Undo and Redo functionality

Application starting point - index.html

<!DOCTYPE html>
<html>
<head>
   
    <title>HTML5 canvas - Undo and Redo buttons</title>
    <link rel="stylesheet" type="text/css" href="extjs/resources/css/ext-all.css">
    <script type="text/javascript" src="extjs/ext-all-debug.js"></script>
    <script type="text/javascript" src="app.js"></script>
   
</head>
<body>
</body>
</html>

Application JavaScript file - app.js

Ext.Loader.setConfig({ 
    enabled: true 
});

Ext.application({

    name: 'MyApp',
    appFolder: 'app',
    controllers: [
                  'UndoRedo'
                  ],

                  launch: function() {
                      Ext.create('Ext.container.Viewport', {
                          margin: 10, 
                          defaults: {
                              margin: 10,
                          }, 
                          items: [{
                              xtype: 'label',
                              html: '<b>HTML5 canvas with Undo and Redo functions</b>'
                          },
                          {
                              xtype: 'myCanvas',
                          },
                          {
                              xtype: 'button',
                              text: 'Clear Canvas',
                              id: 'clear'
                          },
                          {
                              xtype: 'button',
                              text: 'Undo Action',
                              id: 'undo',
                              disabled: true,
                          },
                          {
                              xtype: 'button',
                              text: 'Redo Action',
                              id: 'redo',
                              disabled: true,
                          }]
                      });
                  }

});

HTML5 canvas view panel - MyCanvas.js

Ext.define('MyApp.view.MyCanvas', {
    extend: 'Ext.Container',
    alias : 'widget.myCanvas',
    
    layout: {
        type: 'vbox',
        align: 'stretch'
    },
    width: 600,
    height: 300,
    border: 1,
    style: {borderColor:'#000000', borderStyle:'solid', borderWidth:'1px'},
    
    items: [
        {
            xtype: 'panel',
            id: 'myCanvasPanel',
            html: '<canvas id="html5Canvas" width="600" height="300">no canvas support</canvas>'
        }]
  
});


HTML5 Canvas application with Undo and Redo functionality

Application controller file - UndoRedo.js

Ext.define('MyApp.controller.UndoRedo', {
    extend : 'Ext.app.Controller',
   
    //define the views
    views : ['MyCanvas'],
    //define refs
    refs: [{
        ref: 'myCanvasPanel', 
        selector: 'panel[id="myCanvasPanel"]'
    }], 
   
    init : function() {
        this.control({
            'myCanvas' : {
                afterrender : this.onPanelRendered
            },
            'viewport button[id=clear]' : {
                click : this.onClearCanvas   
            },
            'viewport button[id=undo]' : {
                click : this.onUndoCanvas   
            },
            'viewport button[id=redo]' : {
                click : this.onRedoCanvas   
            }
        });
    },

    //clear the canvas
    onClearCanvas: function(button) {
        console.log('Clear CANVAS button clicked!');
        canvasPad.width = canvasPad.width;
        canvasPadContext.lineWidth = 3;
        //disable the undo and redo buttons
        undoButton.disable();
        redoButton.disable();
        //clear the arrays 
        savedImages = [];
        removedImages = [];
    },
   
    onUndoCanvas: function(button) {
        console.log('Undo CANVAS button clicked!');
       
        //save the current canvas in redo array
        this.removeImage();
        canvasPad.width = canvasPad.width;
        canvasPadContext.lineWidth = 3;
       
        //create an image object and paint 
        var imageObj = new Image();
        imageObj.onload = function(){
            canvasPadContext.drawImage(imageObj, 0, 0);
        };
        //get from array the source for the image object 
        imageObj.src = savedImages.pop();
        //if the stack is empty then disable the undo button
        if (savedImages.length === 0) {
            undoButton.disable();
        }
    },
   
    onRedoCanvas: function(button) {
        console.log('Redo CANVAS button clicked!');
       
        //save the current canvas in undo array
        this.saveImage();
        //clear the canvas
        canvasPad.width = canvasPad.width;
        canvasPadContext.lineWidth = 3;
       
        //create an image object and paint 
        var imageObj = new Image();
        imageObj.onload = function(){
            canvasPadContext.drawImage(imageObj, 0, 0);
        };
        //get from array the source for the image object 
        imageObj.src = removedImages.pop();
        //if the stack is empty then disable the redo button
        if (removedImages.length === 0) {
            redoButton.disable();
        }
    },
   
    onPanelRendered: function(panel) {
        console.log('HTML5 canvas rendered, get ready to Sign!');
       
        var view = panel.up('viewport');
        undoButton = view.down('button[id=undo]');
        redoButton = view.down('button[id=redo]');
        savedImages = [];
        removedImages = [];
       
        //get reference to the canvas element and its 2d context
        canvasPad = Ext.getDom("html5Canvas");
        if (canvasPad && canvasPad.getContext) {
            canvasPadContext = canvasPad.getContext('2d');
        }

        if (!canvasPad || !canvasPadContext) {
            alert('Error creating canvas pad.');
            return;
        }
       
        canvasPad.width = this.getMyCanvasPanel().getWidth();
        canvasPad.height = this.getMyCanvasPanel().getHeight();
        canvasPadContext.lineWidth = 3;
       
        //add listener to the mousedown event
        Ext.get("html5Canvas").addListener('mousedown',this.onMouseDown, this);
       
    },
   
    onMouseDown: function(event) {
       
        this.saveImage();
        //get the mouse click coordinates
        var mySign = Ext.get("myCanvasPanel");
        event._x = event.getX() - mySign.getX();
        event._y = event.getY() - mySign.getY();
       
        //create a circle
        canvasPadContext.beginPath();
        canvasPadContext.arc(event._x, event._y,10,0*Math.PI,2*Math.PI);
        canvasPadContext.stroke();
   
    },
   
    saveImage: function(){
       
        //save the canvas image to undo array 
        var imgSrc = canvasPad.toDataURL("image/png");
        savedImages.push(imgSrc);
        undoButton.enable();
       
    },
   
    removeImage: function(){
       
        //save the canvas image to redo array
        var imgSrc = canvasPad.toDataURL("image/png");
        removedImages.push(imgSrc);
        redoButton.enable();
       
    }
           
});

No comments:

Post a Comment

NO JUNK, Please try to keep this clean and related to the topic at hand.
Comments are for users to ask questions, collaborate or improve on existing.