51

Is there already a directive to draw/paint things on a canvas? So you can implement something like Paint or even something bigger like Photoshop etc., but a very basic example would suffice.

I haven't found one in my search and if there's already one that is considered best practice I would like to use it. Else I have to implement one myself.

justGoscha
  • 24,085
  • 15
  • 50
  • 61
  • 3
    Angular is aiming at creation of data management (CRUD) applications - for canvas applications you should try something else, for example: http://www.createjs.com/#!/EaselJS – mirrormx May 16 '13 at 12:53

4 Answers4

84

Ok I did one and it is actually pretty easy:

app.directive("drawing", function(){
  return {
    restrict: "A",
    link: function(scope, element){
      var ctx = element[0].getContext('2d');

      // variable that decides if something should be drawn on mousemove
      var drawing = false;

      // the last coordinates before the current move
      var lastX;
      var lastY;

      element.bind('mousedown', function(event){
        if(event.offsetX!==undefined){
          lastX = event.offsetX;
          lastY = event.offsetY;
        } else { // Firefox compatibility
          lastX = event.layerX - event.currentTarget.offsetLeft;
          lastY = event.layerY - event.currentTarget.offsetTop;
        }

        // begins new line
        ctx.beginPath();

        drawing = true;
      });
      element.bind('mousemove', function(event){
        if(drawing){
          // get current mouse position
          if(event.offsetX!==undefined){
            currentX = event.offsetX;
            currentY = event.offsetY;
          } else {
            currentX = event.layerX - event.currentTarget.offsetLeft;
            currentY = event.layerY - event.currentTarget.offsetTop;
          }

          draw(lastX, lastY, currentX, currentY);

          // set current coordinates to last one
          lastX = currentX;
          lastY = currentY;
        }

      });
      element.bind('mouseup', function(event){
        // stop drawing
        drawing = false;
      });

      // canvas reset
      function reset(){
       element[0].width = element[0].width; 
      }

      function draw(lX, lY, cX, cY){
        // line from
        ctx.moveTo(lX,lY);
        // to
        ctx.lineTo(cX,cY);
        // color
        ctx.strokeStyle = "#4bf";
        // draw it
        ctx.stroke();
      }
    }
  };
});

And then you can use it on canvas like this:

<canvas drawing></canvas>

Here's a demo on Plunkr: http://plnkr.co/aG4paH

justGoscha
  • 24,085
  • 15
  • 50
  • 61
  • What is the reset function doing with the assignment element[0].width = element[0].width; ? – Per Quested Aronsson Sep 29 '13 at 08:21
  • Hmm, actually, I forgot, it had something to do with resetting some transform matrix... I don't remember. EDIT: ah here I found it, it's a function to clear the canvas: http://stackoverflow.com/questions/2142535/how-to-clear-the-canvas-for-redrawing – justGoscha Sep 29 '13 at 16:04
  • 2
    I also wrote a test case in JsPerf for the canvas clearing: because I know two ways how you can clean a canvas: http://jsperf.com/canvas-clearing-test – justGoscha Dec 09 '13 at 21:07
  • 2
    I added this to my Angular/Common repo with with some enhancements: http://clouddueling.github.io/angular-common/ – Michael J. Calkins Jan 14 '14 at 00:57
  • updated it above, to work with firefox and also in the plunker – justGoscha Feb 19 '14 at 15:45
  • btw, do you know how to save as an image based on your code? – Justin May 09 '14 at 04:19
  • see here: http://stackoverflow.com/questions/10673122/how-to-save-canvas-as-an-image-with-canvas-todataurl – justGoscha May 12 '14 at 08:06
  • for FF i need to add change the lines to: currentX = event.originalEvent.layerX; currentY = event.originalEvent.layerY; as event.layerX and Y are undefined as well... – Barbarossa May 12 '14 at 11:10
  • This is a great answer, but does not work out-of-the-box for "touch input". In order for touch to work, you'll want to swap out `event.layerX` and `event.layerY` for `event.touches[0].pageX` and `event.touches[0].pageY`, respectively. This is of course only good for a single touch. For multi-touch, you'll need an array of lastX's and lastY's, currentX's and currentY's, populated via a loop over the touches, then perform a loop on the draw calls. – Xaero Degreaz Nov 20 '14 at 05:32
  • You also need to change the event binds from mouse events to touch events: `touchstart`, `touchmove` and `touchend`. – Xaero Degreaz Nov 20 '14 at 06:45
  • @JustGoscha this post was instrumental in helping me to understand directives and you get a gold star * – James Feb 15 '15 at 21:57
  • @JamesGibsonWeber Thanks, I'm flattered. :D – justGoscha Feb 18 '15 at 09:31
  • Maybe you're interested in writing some tests for the directive and publishing it under your name @JustGoscha ? :) – bastianwegge Mar 05 '15 at 18:27
  • @JustGoscha any way to save it as a set of js codes rather than a pic into the back end database? whicever way is easier. – Thinkerer Mar 12 '15 at 16:33
  • @Thinkerer Sure, you just have to capture all the draw events e.g. each mouseMove, mouseDown and mouseUp. And then save it as JSON array to the backend. Then apply all events back to restore. Just iterate through the array and execute all the functions there are for mouseMove/Donw/Up accordingly. This is the straight forward naive approach. Is probably getting more complex with the complexity of your app. – justGoscha Mar 13 '15 at 16:32
  • I realize I'm late to the party, but I'd like to suggest setting the `cursor` property in css... it's weird drawing with an I beam. – Jeff Oct 23 '15 at 13:01
  • can we store the drawing and mutiple user can edit it,is it possible to do that – ashishSober Jun 09 '16 at 16:49
  • if i want to edit image using this canvas directive then where to pass the image in this ? – Gagan Sharma Dec 26 '16 at 21:15
12

Angular is ideally suited for writing applications in declarative style. Once you hit the canvas element you cannot go any further with declarative and you have to switch towards an imperative way of writing mechanism. If the majority of your application is providing UI, which you define in html in the rest of your application I would highly encourage you to use AngularJS. Its an amazing framework for that.

On the other hand if the majority of your code is going to be inside the canvas element, then perhaps AngularJS is not the ideal tool for you. If you really insist on using AngularJS for the majority of your application I would suggest you to consider using something like D3 which is an excellent alternative and uses SVG behind the scenes ( which is declarative in nature and hence a great sidekick for AngularJS ).

ganaraj
  • 26,841
  • 6
  • 63
  • 59
  • I know, I have an application that's mostly declarative. And most things don't need a canvas. But then I have also one feature that needs drawing compatibility on a canvas... I thought maybe there is already something out there that does it in a directive. Or imports some other drawing library in a directive, but if that's not the case I have to figure it out myself. – justGoscha May 16 '13 at 14:06
  • 13
    The above answer is misleading. AngularJS (and declarative programming) does not hinder the use of a canvas. It is all in how you approach the problem. Many of the popular charting and graphing JS libraries are starting to support AngularJS by providing the directives for their components. Thus the imperative code is hidden in the reusable implementation of the directive. This is the beauty of AngularJS, you get to write the imperative code once and use it declaratively as the solution below demonstrates. – Dr. Mike Hopper Feb 16 '14 at 18:39
  • 7
    The answer is not misleading. If you write a bunch of JQuery spaghetti and hide it all inside a directive's constructor function, you have not 'written an Angular Directive'. You've written a bunch of imperative junk and consoled yourself by organizing it in a manner that *resembles* Angular. And that's with code that *could have been* written properly with Angular. But there is no $digest loop that will monitor the state of a canvas, and no such thing as 2-way Angular data-binding to canvas. You can pretend, by wrapping imperative canvas code in a directive. But that won't make it Angular. – XML May 27 '14 at 01:39
  • 2
    There is a nice D3 Angular tutorial here: http://www.ng-newsletter.com/posts/d3-on-angular.html – Tomer Almog Sep 02 '15 at 22:42
  • Why couldn't you use declarative? `` canvas-line is a directive that cool-canvas requires (much like ng-model in many cases) and cool-canvas is a directive (of course) that manages it's children. – SRM Aug 29 '17 at 19:07
5

Some time ago i built a configurable directive for that.

https://github.com/pwambach/angular-canvas-painter

The directive creates the canvas element and adds handlers for mousedown/mousemove/mouseup events (and corresponding touch events) to the element. Mousedown and following mousemove events draw bezier curves with the canvasContext.quadraticCurveTo() method for smoother strokes (instead of just painting circles for every point). For details on the drawing algorithm have a look at this article: http://codetheory.in/html5-canvas-drawing-lines-with-smooth-edges/

Philipp
  • 141
  • 1
  • 5
  • Whilst this may theoretically answer the question, [it would be preferable](http://meta.stackoverflow.com/q/8259) to include the essential parts of the answer here, and provide the link for reference. – Yurii Apr 06 '15 at 07:37
1

That's a really nice implementation! I could add the method if you want to convert the canvas on an image

    function convertCanvasToImage(canvas) {
       var image = new Image();
       image.src = canvas.toDataURL("image/png");
       return image;
    }

This will make you a image tag with the source as base64 element.

Hope it helps you