FabricJS ClipTo Issue for multiple objects like group - javascript

My code is
canvas.clipTo = function (ctx) {
ctx.beginPath();
for (var i = 0; i < totalPrintArea; i++) {
ctx.save();
ctx.fillStyle = 'rgba(51,51,51,0)';
ctx.rect(clipLft[i], clipTp[i], clipW[i], clipH[i], 'rgba(51,51,51,1)', clipRtn[i]);
ctx.stroke();
ctx.restore();
}
ctx.closePath();
ctx.clip();
canvas.calcOffset();
};
canvas.renderAll();
I am taking values from the red dotted box and apply to clip where multiple masks are generating.
My issue is its taking all properties but not rotation for all.
I want to rotate all the rectangles.
I just get some code to change the rotation for the clip like ctx.rotate(50); but will not work as I want to make all rotate with their own values
Please guide me for the same.

On the original fabricJS github project I saw the comment: https://github.com/kangax/fabric.js/issues/932#issuecomment-27223912
and decided that I need to prevent making ctx.beginPath all the time:
canvas.clipTo = function(ctx) {
var skip = false;
// Workaround to make possible
// making clipTo with
// fabric.Group
var oldBeginPath = ctx.beginPath;
ctx.beginPath = function() {
if (!skip) {
oldBeginPath.apply(this, arguments);
skip = true;
setTimeout(function() {
skip = false;
}, 0);
}
}
group.render(ctx)
};
You can see my workaround to the problem described:
https://jsfiddle.net/freelast/6o0o07p7/
The workaround is not perfect, but hope it will help somebody.

I have tried using the Andrey's answer, but althouth there some interesting points, it didn't work.
If you try to clip the canvas to a single object (e.g. a circle or a rectangle), you can simply do this:
canvas.clipTo = function(ctx) {
shape.render(ctx); //shape is a circle, for instance
}
However, as explained by Kienz and butch2k in the aforementioned comment on GitHub, the problem is that you cannot use this solution with groups. In particular, if you use the following snippet:
canvas.clipTo = function(ctx) {
group.render(ctx);
}
you will only see one object of the group to be used for clipping.
The issue is due to the render method, which calls the ctx.beginPath() and ctx.closePath() for each object in the group. And because only the last couple of beginPath-closePath calls will affect the clipping, you need some workaround.
So in my solution, I have temporarily redefined the ctx.closePath and ctx.beginPath methods (after storing them in other two temporary variables, named oldBeginPath and oldClosePath) so that they do nothing. Then I call oldBeginPath at the beginning, and after rendering all the objects in the group I call the oldClosePath.
And now, here is the (working) snippet:
canvas.clipTo = function(ctx) {
var oldBeginPath = ctx.beginPath;
var oldClosePath = ctx.closePath;
ctx.beginPath = function() {}
ctx.closePath = function() {}
oldBeginPath.apply(ctx);
group.forEachObject(function(shape){
shape.render(ctx);
});
oldClosePath.apply(ctx);
ctx.beginPath = oldBeginPath;
ctx.closePath = oldClosePath;
};
Hope this will save someone's spare time in the future.

Related

Drawing two images in Canvas at the same time

I try to program an online version of Kara (the little ladybug you can program to find the leafs laying in the grid). For that, I programmed some functions to draw the needed objects into the grid. Those functions are:
Kara.prototype.draw_object = function (x, y, asset) {
pos = this.calc_pos(x,y);
var img = new Image();
img.onload = function() {
c.drawImage(img, pos.x, pos.y, 40, 40);
}
img.src = asset;
}
Kara.prototype.draw_tree = function(x,y) {
this.draw_object(x,y,this.assets.asset_tree);
}
Kara.prototype.draw_leaf = function(x,y) {
this.draw_object(x,y,this.assets.asset_leaf);
}
Kara.prototype.draw_mush = function(x,y) {
this.draw_object(x,y,this.assets.asset_mush);
}
The asset object is defined in the init functions and point to png files like img/tree.png. calc_pos calculates the absolute x and y position depending on the position in the grid.
There is a lot work to do. Because I would like to draw some sample content every time the browser reloads, I wrote a sample function:
Kara.prototype.sample = function() {
kara = this;
kara.draw_kara(1,1); // Not listed above. Draws the little laddybug
setTimeout(function() {
kara.draw_tree(2,1);
}, 4);
setTimeout(function() {
kara.draw_mush(3,1);
}, 8);
setTimeout(function() {
kara.draw_leaf(4,1);
}, 12);
}
Without the timeout everything is drawn to [4][1]. Because it worked when I typed every single command in the Developer console I tried to find the root of the bug by placing some waiting time between the draw commands.
If there are 0.5 seconds between the commands everything works perfect. With 4 miliseconds it could work properly or some objects are placed above each other.
Does anybody know an issue where this occured or can give me a hint for the origion of this bug?

Best approach to deal with instances of rectangles to draw separately on canvas

First thing, excuse my absolute lack of knowledge in JavaScript. I'm looking for the best approach for this problem, but after 3 days I think it may be wrong.
I need to write some code to draw moving rectangles in different rows in a canvas. In the future I will need to detect when 2 rectangles are in the same X coordinate, so it's important to keep track of the X values. Coming from Java I thought the best would be to create some rectangle "objects" and with each instance a draw method.
What is causing me trouble is that I thought about calling the draw function with setInterval(), but it appears that every time the function draw is called, the values are not the same.
This is my definition of the Rectangle class:
function Rectangle(x,y,width,height) {
var x=x;
var y= y;
var width= width;
var height= height;
this.getX = function(){
return x;
}
this.setX = function (value) {
x = value;
}
this.getY = function(){
return y;
}
this.setY = function (value) {
y = value;
}
this.getWidth = function(){
return width;
}
this.setWidth = function (value) {
width = value;
}
this.getHeight = function(){
return height;
}
this.setHeight = function (value) {
height = value;
}
this.draw = function(){
if(this.getX() <=canvas.width){
clearContext(this.getX() - 30,this.getY(),this.getWidth(),this.getHeight());
var temp= this.getX()+1;
this.setX(temp);
ctx.fillRect(temp,this.getY(),this.getWidth(),this.getHeight());
}else{
clearInterval(this.draw(),speed);
}
}
}
Then I have a function formSubmit where I create the Rectangles instances when the button is pressed and call respectively the function draw with setInterval():
function formSubmit(){
number=parseInt(document.getElementById("nummerT").value);
rate=parseInt(document.getElementById("rate").value);
speed=parseInt(document.getElementById("speed").value);
confirm(speed);
myRectangle= new Rectangle(0,0,30,30);
myRectangle2 = new Rectangle(0,60,30,30);
setInterval(myRectangle.draw(),speed);
}
The problem is that setInterval(myRectangle.draw(),speed); doesn't do what you think it does. You are calling draw one time, and then the interval is calling the result of draw. You'll need something like:
interval = setInterval(function() {
myRectangle.draw();
}, speed);
You'll note, I set the return value of setInterval to a variable because that is how you'll clear the interval later. You just call
clearInterval(interval);
I don't know if that's going to solve all your problems, but it should at least you get to something that will give you some more information.
A Demo: http://jsfiddle.net/m1erickson/SdPPa/
Your instinct of creating rectangle objects to define what is drawn on the canvas is indeed the common standard.
Unlike Java, JavaScript does not have true classes, but you can create a pseudo-class as you have done in your question.
At it's simplest a Rectangle "class" needs these properties:
x, y
width, height
If you want to animate those rectangles on the canvas you might add:
velocityX, directionY
velocityY, direction
These new properties allow you to move the rectangles like this:
this.x += this.directionX * this.velocityX;
this.y += this.directionY * this.velocityY;
Hint: Html5 now has an excellent animation handler: requestAnimationFrame. You might want to use this instead of setInterval or setTimeout because it gives better performance by integrating itself with the refresh cycle of the browser.
Hint: JavaScript is a prototypal language so you can extend your "class" with methods. The best way to add methods to a "class" is to add them to the classes prototype. That way the methods are created once and shared by all instances of the class rather than having every method recreated on every instance.
So a method to allow a rectangle instance to draw itself to the canvas might look like this:
// draw this rect on the canvas
Rectangle.prototype.render=function(){
ctx.fillStyle=this.color;
ctx.fillRect(this.x,this.y,this.width,this.height);
return(this);
}
Hint: JavaScript "class" methods can be chained if you always return(this). A good use of chaining might be calling a move method on an instance and then chaining on the render method.
rectangle1.move().render();
There's lots to learn about javascript "classes".
Here's annotated code to start with:
Good luck with your project!
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; }
canvas{border:1px solid red;}
</style>
<script>
$(function(){
// canvas related variables
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
// an array to hold all rectangle objects
var rectangles=[];
// a rectangle pseudo-class (javascript does not have actual classes)
function Rectangle(stdProperties) {
addProperties(this,stdProperties);
this.color=randomColor();
};
//
// Add methods that apply to all instance rectangles
// to Rectangle.prototype so those methods are
// created once for all instances instead of
// repeatedly for every instance.
//
// set x,y,width,height of this rectangle
Rectangle.prototype.init=function(x,y,width,height){
this.x=x;
this.y=y;
this.width=width;
this.height=height;
return(this);
};
// move this rectangle by its preset delta-x and delta-y
Rectangle.prototype.move=function(){
var maxRight=canvas.width-this.width;
var maxBottom=canvas.height-this.height;
this.x+=this.directionX*this.velocityX;
if(this.x<0){ this.x=0; this.directionX*=-1}
if(this.x>maxRight){ this.x=maxRight; this.directionX*=-1}
this.y+=this.directionY*this.velocityY;
if(this.y<0){ this.y=0; this.directionY*=-1}
if(this.y>maxBottom){ this.y=maxBottom; this.directionY*=-1}
return(this);
};
// draw this rect on the canvas
Rectangle.prototype.render=function(){
ctx.fillStyle=this.color;
ctx.fillRect(this.x,this.y,this.width,this.height);
return(this);
}
// create a new rectangle object from the Rectangle "class"
function newRect(x,y,width,height){
// define default properties for Rectangle
var DefaultRectangleProperties={
x:0,y:0,width:10,height:10,
velocityX:1,velocityY:1,directionX:1,directionY:1,
color:"black",
}
// new-up a Rectangle
var rect = new Rectangle(DefaultRectangleProperties);
// set the x,y,width,height & draw it on the canvas
rect.init(x,y,width,height).render();
// return(this) to allow chaining
return(rect);
}
// TESTING
// create 5 rectangles with some randomness
for(var i=0;i<5;i++){
var rect=newRect(Math.random()*200,Math.random()*200,40,50);
rect.velocityX=Math.random()*2;
rect.velocityY=Math.random()*3;
rectangles.push(rect);
}
// animate the rectangles using requestAnimationFrame
animate();
// the animation loop
function animate(t){
// request another animation frame
requestAnimationFrame(animate);
// clear the canvas
// move all the rectangles by their preset distance
// redraw all the rectangles
ctx.clearRect(0,0,canvas.width,canvas.height);
for(var i=0;i<rectangles.length;i++){
rectangles[i].move().render();
}
}
///////////////////////////////////
// Utilities
///////////////////////////////////
// create getters/setters on the specified object
// using the supplied properties object
//
function addProperties(object,properties){
for (var i in properties) {
(function(i) {
Object.defineProperty(object, i, {
get: function(){ return properties[i]; },
set: function(val){ properties[i] = val; }
})
})(i);
}
}
// generate a random color
function randomColor(){
return('#'+Math.floor(Math.random()*16777215).toString(16));
}
}); // end $(function(){});
</script>
</head>
<body>
<canvas id="canvas" width=300 height=300></canvas>
</body>
</html>
I have not so much to add to the other answers (+1 to both) but just a note on this part:
function Rectangle(x,y,width,height) {
var x=x;
var y= y;
var width= width;
var height= height;
...
When you do Rectangle(x, y, ...) the compiler/parser will actually do this for you (or rather, the equivalent of):
var x = arguments[0]; // x declared internally, no need to manually declare it
var y = arguments[1]; // y declared too, etc.
...
so you do not need to declare the variables in the function signature as they are already declared - or just leave the signature without any parameters and do the assigning manually (a tad slower but fully legal).
function Rectangle() {
var x = arguments[0]; // legal but not recommended (in most cases)
var y = arguments[1];
...
So, in conclusion - the recommended approach in this case would be:
function Rectangle(x,y,width,height) {
// no x,y, width and height decl. here - they're declared by signature
this.getX = function(){
return x;
}
...
Second issue: setInterval will need a reference to a function. As it is now the function will invoked due to placing the to parenthesis at the end and the result of that function will be handed as a reference instead.
You can call it like:
setInterval(myRectangle.draw, speed); // only a reference, no parenthesis
But in order to enable cancelling of it you need to store the timer ID:
var timerID; // global scope
...
timerID = setInterval(myRectangle.draw, speed);
Then use that request to cancel it later:
clearInterval(timerID);
I would too recommend using requestAnimationFrame as this is optimized for animation and monitor sync.
Contrary to setInterval you'll need to call it per frame inside your animation loop. You can use a flag/condition to not call it again when you want to end the animation.
You have also a clearContext method in there - I assume you have that defined elsewhere in the code, if not, check out context.clearRect().

Html5 Canvas concept

I'm currently learning the basics of html5 canvas. I just ran into something that needs to be explain. I have this code.
function myApp() {
this.el = document.getElementById('canvas');
this.context = this.el.getContext('2d');
this.colors = ['#fff', '#000', '#f00'];
this.init();
}
myApp.prototype = {
init: function() {
var _this = this;
_this.draw();
},
draw: function() {
var _this = this;
_this.context.fillStyle = _this.colors[2];
_this.context.fillRect(10,10,100,100);
//_this.context.fillStyle = _this.colors[2];
}
}
var startThisBitch = new myApp();
If I try to fillStyle() after I create the Rect(), there's nothing on my canvas. But if I fill the color BEFORE creating the Rect(), it works. I just don't find it logical. Or maybe I'm missing the concept. Do you guys have any explainations?
I just don't find it logical.
Would you find it logical to first paint a wall, and then choose the color of the paint?
Edit: fillStyle() doesn't actually draw anything, it just sets the parameters of subsequent draw calls. fillRect() however does draw pixels on canvas, so it's necessary to set all parameters (color, line width, etc.) before calling fillRect().

test whether shapes were actually drawn on HTML5 canvas [duplicate]

How can I unit-test Javascript that draws on an HTML canvas? Drawing on the canvas should be checked.
I wrote an example for unit-testing canvas and other image-y types with Jasmine and js-imagediff.
Jasmine Canvas Unit Testing
I find this to be better than making sure specific methods on a mock Canvas have been invoked because different series of methods may produce the same method. Typically, I will create a canvas with the expected value or use a known-stable version of the code to test a development version against.
As discussed in the question comments it's important to check that certain functions have been invoked with suitable parameters. pcjuzer proposed the usage of proxy pattern. The following example (RightJS code) shows one way to do this:
var Context = new Class({
initialize: function($canvasElem) {
this._ctx = $canvasElem._.getContext('2d');
this._calls = []; // names/args of recorded calls
this._initMethods();
},
_initMethods: function() {
// define methods to test here
// no way to introspect so we have to do some extra work :(
var methods = {
fill: function() {
this._ctx.fill();
},
lineTo: function(x, y) {
this._ctx.lineTo(x, y);
},
moveTo: function(x, y) {
this._ctx.moveTo(x, y);
},
stroke: function() {
this._ctx.stroke();
}
// and so on
};
// attach methods to the class itself
var scope = this;
var addMethod = function(name, method) {
scope[methodName] = function() {
scope.record(name, arguments);
method.apply(scope, arguments);
};
}
for(var methodName in methods) {
var method = methods[methodName];
addMethod(methodName, method);
}
},
assign: function(k, v) {
this._ctx[k] = v;
},
record: function(methodName, args) {
this._calls.push({name: methodName, args: args});
},
getCalls: function() {
return this._calls;
}
// TODO: expand API as needed
});
// Usage
var ctx = new Context($('myCanvas'));
ctx.moveTo(34, 54);
ctx.lineTo(63, 12);
ctx.assign('strokeStyle', "#FF00FF");
ctx.stroke();
var calls = ctx.getCalls();
console.log(calls);
You can find a functional demo here.
I have used a similar pattern to implement some features missing from the API. You might need to hack it a bit to fit your purposes. Good luck!
I make really simple canvases and test them with mocha. I do it similarly to Juho Vepsäläinen but mine looks a little simpler. I wrote it in ec2015.
CanvasMock class:
import ContextMock from './ContextMock.js'
export default class {
constructor (width, height)
{
this.mock = [];
this.width = width;
this.height = height;
this.context = new ContextMock(this.mock);
}
getContext (string)
{
this.mock.push('[getContext ' + string + ']')
return this.context
}
}
ContextMock class:
export default class {
constructor(mock)
{
this.mock = mock
}
beginPath()
{
this.mock.push('[beginPath]')
}
moveTo(x, y)
{
this.mock.push('[moveTo ' + x + ', ' + y + ']')
}
lineTo(x, y)
{
this.mock.push('[lineTo ' + x + ', ' + y + ']')
}
stroke()
{
this.mock.push('[stroke]')
}
}
some mocha tests that evaluates the functionality of the mock itself:
describe('CanvasMock and ContextMock', ()=> {
it('should be able to return width and height', ()=> {
let canvas = new CanvasMock(500,600)
assert.equal(canvas.width, 500)
assert.equal(canvas.height, 600)
})
it('should be able to update mock for getContext', ()=> {
let canvas = new CanvasMock(500,600)
let ctx = canvas.getContext('2d')
assert.equal(canvas.mock, '[getContext 2d]')
})
})
A mocha tests that evaluates the functionality of a function that returns a canvas:
import Myfunction from 'MyFunction.js'
describe('MyFuntion', ()=> {
it('should be able to return correct canvas', ()=> {
let testCanvas = new CanvasMock(500,600)
let ctx = testCanvas.getContext('2d')
ctx.beginPath()
ctx.moveTo(0,0)
ctx.lineTo(8,8)
ctx.stroke()
assert.deepEqual(MyFunction(new CanvasMock(500,600), 8, 8), canvas.mock, [ '[getContext 2d]', '[beginPath]', '[moveTo 0, 0]', [lineTo 8, 8]', '[stroke]' ])
})
so in this example myfunction takes the canvas you passed in as an argument ( Myfunction(new CanvasMock(500,600), 8, 8) ) and writes a line on it from 0,0 to whatever you pass in as the arguments ( Myfunction(new CanvasMock(500,600),** 8, 8**) ) and then returns the edited canvas.
so when you use the function in real life you can pass in an actual canvas, not a canvas mock and then it will run those same methods but do actual canvas things.
read about mocks here
Since the "shapes" and "lines" drawn on a canvas are not actual objects (it's like ink on paper), it would be very hard (impossible?) to do a normal unit test on that.
The best you can do with standard canvas it analyze the pixel data (from the putImageData/getImageData. Like what bedraw was saying).
Now, I haven't tried this yet, but it might be more what you need. Cake is a library for the canvas. It's using alot of the putImageData/getImageData. This example might help with what you are trying to do with a test.
Hope that helps answer your question.
I've been looking at canvas testing recently and I've now thought about a page that allows comparing the canvas to a "known good" image version of what the canvas should look like. This would make a visual comparison quick and easy.
And maybe have a button that, assuming the output is OK, updates the image version on the server (by sending the toDataUrl() output to it). This new version can then be used for future comparisons.
Not exactly (at all) automated - but it does make comparing the output of your code easy.
Edit:
Now I've made this:
The left chart is the real canvas whilst the right is an image stored in a database of what it should look like (taken from when I know the code is working). There'll be lots of these to test all (eventually) aspects of my code.
From a developer's point of view the canvas is almost write-only because once drawn it's difficult to programmatically get something useful back. Sure one can do a point by point recognition but that's too tedious and such tests are hard to be written and maintained.
It's better to intercept the calls made to a canvas object and investigate those. Here are a few options:
Create a wrapper object that records all the calls. Juho Vepsäläinen posted a such example.
If possible use a library like frabric.js that offers a higher level of abstraction for drawing. The "drawings" are JS objects that can be inspected directly or converted to SVG which is easier to inspect and test.
Use Canteen to intercept all the function calls and attribute changes of a canvas object. This is similar with option 1.
Use Canteen with rabbit which offers you a few Jasmine custom matchers for size and alignment and a function getBBox() that can be used to determine the size and the position of the stuff being drawn on the canvas.

Javascript, MouseEvents and Classes

With all the buzz of HTML5 I've began by investigating the Canvas's capabilities along with interaction from Javascript. Unfortunately things haven't been going well due to idiosyncrasies of Javascript and its OO model.
For instance, I figured I could create a wrapper class for my canvas object and effectively box all appropriate methods and properties into it making the development side of things much easier. Unfortunately I'm struggling with the way the mouse handlers are working. In my case, I have the 'DrawArea' class that adds three mouse handlers for drawing rectangles and a 'Draw' routine titled 'Invalidate'. When the mouse events are fired (mouseMove and mouseUp methods), they fail claiming that the 'Invalidate' function is invalid - almost like it is out of context of the method it is being called within. Code below.
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.js"></script>
<script type="text/javascript">
// Top level variables
var dWrap;
// Point Class
function Point( xPos , yPos ){
this.X = xPos;
this.Y = yPos;
}
// Create wrapper class for the draw area
function DrawArea( da ){
this.SrcArea = da;
// Add mouse handlers
this.SrcArea.addEventListener('mousedown', this.mouseDown, false);
this.SrcArea.addEventListener('mousemove', this.mouseMove, false);
this.SrcArea.addEventListener('mouseup', this.mouseUp, false);
// And draw
// NOTE: this call works!
this.Invalidate();
}
// Properities
DrawArea.prototype.ProposedStartPos = undefined;
DrawArea.prototype.ProposedEndPos = undefined;
DrawArea.prototype.IsDrawing = false;
// Mouse Events
// Handles the mouse down event for new objects
DrawArea.prototype.mouseDown = function(m) {
// Flag as drawing
this.IsDrawing = true;
// Record the start position
this.ProposedStartPos = new Point(m.layerX, m.layerY);
}
// Handles mouse movement when creating a proposed object
DrawArea.prototype.mouseMove = function(m) {
if (this.IsDrawing) {
// Set the current end position
this.ProposedEndPos = new Point(m.layerX, m.layerY);
// NOTE: this call doesn't work!
this.Invalidate();
}
}
// Handles the completion of a proposed object
DrawArea.prototype.mouseUp = function(m) {
if (this.IsDrawing) {
// Set the final end position
if (m.type != 'mouseout') this.ProposedEndPos = new Point(m.layerX, m.layerY);
// NOTE: this call doesn't work!
this.Invalidate();
}
}
// Redraws the source object
DrawArea.prototype.Invalidate = function() {
// Obtain
if (this.SrcArea.getContext) {
var context = this.SrcArea.getContext('2d');
// Clean up
context.clearRect(0, 0, this.SrcArea.width, this.SrcArea.height);
context.save();
// Draw the background
context.strokeStyle = "#000000";
context.fillStyle = "#AAAFFF";
context.beginPath();
context.rect(0, 0, this.SrcArea.width, this.SrcArea.height);
context.closePath();
context.stroke();
context.fill();
// Are we drawing any proposed items
if (this.IsDrawing) {
context.strokeStyle = this.ProposedColorStroke;
context.fillStyle = this.ProposedColorFill;
context.beginPath();
context.rect(this.ProposedStartPos.X, this.ProposedStartPos.Y, this.ProposedEndPos.X - this.ProposedStartPos.X, this.ProposedEndPos.Y - this.ProposedStartPos.Y);
context.closePath();
context.stroke();
context.fill();
}
}
// Flush
context.restore();
}
// Initialise the wrapper class
$(document).ready(function() {
// Obtain the canvas and set
var cWrap = $('#cDrawArea')[0];
dWrap = new DrawArea( cWrap );
});
Html code...
<body>
<div id="DrawContainer">
<canvas id="cDrawArea" width="800" height="600"></canvas>
</div>
</body>
What am I missing here and is this a particular efficient and smart way of handling complex objects that will require a lot of behind the scenes code?
This is a common misunderstanding. JavaScript doesn't have classes, and it doesn't have methods. It has functions. Unlike some other languages (Java, C#, C++), this is determined entirely by how a function is called, not where a function is defined. (This is incredibly powerful, but surprising to someone coming from class-based languages.) So this line of code:
this.SrcArea.addEventListener('mousedown', this.mouseDown, false);
...does hook up the function referenced by the mouseDown property, but does nothing to ensure that when that function is called, this is the value you expect.
If you're really using an ECMAScript5-compliant browser (there are some that have canvas but are not completely ES5-compliant), you can use the new Function#bind feature, but again note that this is only about two years old:
// Create wrapper class for the draw area
function DrawArea( da ){
this.SrcArea = da;
// Add mouse handlers using ECMAScript5's new `Function#bind`
this.SrcArea.addEventListener('mousedown', this.mouseDown.bind(this), false);
this.SrcArea.addEventListener('mousemove', this.mouseMove.bind(this), false);
this.SrcArea.addEventListener('mouseup', this.mouseUp.bind(this), false);
// And draw
// NOTE: this call works!
this.Invalidate();
}
Alternately, you can do pretty much the same thing yourself using closures:
// Create wrapper class for the draw area
function DrawArea( da ){
var self = this; // Set up a variable referencing the instance
this.SrcArea = da;
// Add mouse handlers - these are closures over the context of this
// call to the constructor, and have access to the `self` variable
// above. They just relay the call to the functions on the prototype,
// but in a way that ensures that `this` is what you expect.
this.SrcArea.addEventListener('mousedown', function(event) {
return self.mouseDown(event);
}, false);
this.SrcArea.addEventListener('mousemove', function(event) {
return self.mouseMove(event);
}, false);
this.SrcArea.addEventListener('mouseup', function(event) {
return self.mouseUp(event);
}, false);
// And draw
// NOTE: this call works!
this.Invalidate();
}
More reading:
Mythical methods
You must remember this
Closures are not complicated
Try:
this.SrcArea.addEventListener('mousedown', this.mouseDown.bind(this), false);
this.SrcArea.addEventListener('mousemove', this.mouseMove.bind(this), false);
this.SrcArea.addEventListener('mouseup', this.mouseUp.bind(this), false);
The "bind()" method on the Function prototype (should be in any browser with <canvas> I think) returns a function that will force the this value to be the parameter you pass, in this case your wrapper object instance.
If you don't do something like that, then the handler won't have the this you expect.
The this is not the DrawArea instance in the handler, but the element itself.
You should bind (freeze) the this value with bind. This is the easiest, but is not available in all browsers. There is a shim available, though.
// guarantee the 'this' value inside handler
this.SrcArea.addEventListener('mousedown', this.mouseDown.bind(this), false);
http://jsfiddle.net/KdnZC/

Categories