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/
Related
I've created a Javascript object via prototyping. I'm trying to render a table dynamically. While the rendering part is simple and works fine, I also need to handle certain client side events for the dynamically rendered table. That, also is easy. Where I'm having issues is with the "this" reference inside of the function that handles the event. Instead of "this" references the object, it's referencing the element that raised the event.
See code. The problematic area is in ticketTable.prototype.handleCellClick = function():
function ticketTable(ticks)
{
// tickets is an array
this.tickets = ticks;
}
ticketTable.prototype.render = function(element)
{
var tbl = document.createElement("table");
for ( var i = 0; i < this.tickets.length; i++ )
{
// create row and cells
var row = document.createElement("tr");
var cell1 = document.createElement("td");
var cell2 = document.createElement("td");
// add text to the cells
cell1.appendChild(document.createTextNode(i));
cell2.appendChild(document.createTextNode(this.tickets[i]));
// handle clicks to the first cell.
// FYI, this only works in FF, need a little more code for IE
cell1.addEventListener("click", this.handleCellClick, false);
// add cells to row
row.appendChild(cell1);
row.appendChild(cell2);
// add row to table
tbl.appendChild(row);
}
// Add table to the page
element.appendChild(tbl);
}
ticketTable.prototype.handleCellClick = function()
{
// PROBLEM!!! in the context of this function,
// when used to handle an event,
// "this" is the element that triggered the event.
// this works fine
alert(this.innerHTML);
// this does not. I can't seem to figure out the syntax to access the array in the object.
alert(this.tickets.length);
}
You can use bind which lets you specify the value that should be used as this for all calls to a given function.
var Something = function(element) {
this.name = 'Something Good';
this.onclick1 = function(event) {
console.log(this.name); // undefined, as this is the element
};
this.onclick2 = function(event) {
console.log(this.name); // 'Something Good', as this is the binded Something object
};
element.addEventListener('click', this.onclick1, false);
element.addEventListener('click', this.onclick2.bind(this), false); // Trick
}
A problem in the example above is that you cannot remove the listener with bind. Another solution is using a special function called handleEvent to catch any events:
var Something = function(element) {
this.name = 'Something Good';
this.handleEvent = function(event) {
console.log(this.name); // 'Something Good', as this is the Something object
switch(event.type) {
case 'click':
// some code here...
break;
case 'dblclick':
// some code here...
break;
}
};
// Note that the listeners in this case are this, not this.handleEvent
element.addEventListener('click', this, false);
element.addEventListener('dblclick', this, false);
// You can properly remove the listners
element.removeEventListener('click', this, false);
element.removeEventListener('dblclick', this, false);
}
Like always mdn is the best :). I just copy pasted the part than answer this question.
You need to "bind" handler to your instance.
var _this = this;
function onClickBound(e) {
_this.handleCellClick.call(cell1, e || window.event);
}
if (cell1.addEventListener) {
cell1.addEventListener("click", onClickBound, false);
}
else if (cell1.attachEvent) {
cell1.attachEvent("onclick", onClickBound);
}
Note that event handler here normalizes event object (passed as a first argument) and invokes handleCellClick in a proper context (i.e. referring to an element that was attached event listener to).
Also note that context normalization here (i.e. setting proper this in event handler) creates a circular reference between function used as event handler (onClickBound) and an element object (cell1). In some versions of IE (6 and 7) this can, and probably will, result in a memory leak. This leak in essence is browser failing to release memory on page refresh due to circular reference existing between native and host object.
To circumvent it, you would need to either a) drop this normalization; b) employ alternative (and more complex) normalization strategy; c) "clean up" existing event listeners on page unload, i.e. by using removeEventListener, detachEvent and elements nulling (which unfortunately would render browsers' fast history navigation useless).
You could also find a JS library that takes care of this. Most of them (e.g.: jQuery, Prototype.js, YUI, etc.) usually handle cleanups as described in (c).
Also, one more way is to use the EventListener Interface (from DOM2 !! Wondering why no one mentioned it, considering it is the neatest way and meant for just such a situation.)
I.e, instead of a passing a callback function, You pass an object which implements EventListener Interface. Simply put, it just means you should have a property in the object called "handleEvent" , which points to the event handler function. The main difference here is, inside the function, this will refer to the object passed to the addEventListener. That is, this.theTicketTable will be the object instance in the belowCode. To understand what I mean, look at the modified code carefully:
ticketTable.prototype.render = function(element) {
...
var self = this;
/*
* Notice that Instead of a function, we pass an object.
* It has "handleEvent" property/key. You can add other
* objects inside the object. The whole object will become
* "this" when the function gets called.
*/
cell1.addEventListener('click', {
handleEvent:this.handleCellClick,
theTicketTable:this
}, false);
...
};
// note the "event" parameter added.
ticketTable.prototype.handleCellClick = function(event)
{
/*
* "this" does not always refer to the event target element.
* It is a bad practice to use 'this' to refer to event targets
* inside event handlers. Always use event.target or some property
* from 'event' object passed as parameter by the DOM engine.
*/
alert(event.target.innerHTML);
// "this" now points to the object we passed to addEventListener. So:
alert(this.theTicketTable.tickets.length);
}
This arrow syntax works for me:
document.addEventListener('click', (event) => {
// do stuff with event
// do stuff with this
});
this will be the parent context and not the document context.
With ES6, you can use an arrow function as that will use lexical scoping[0] which allows you to avoid having to use bind or self = this:
var something = function(element) {
this.name = 'Something Good';
this.onclick1 = function(event) {
console.log(this.name); // 'Something Good'
};
element.addEventListener('click', () => this.onclick1());
}
[0] https://medium.freecodecamp.org/learn-es6-the-dope-way-part-ii-arrow-functions-and-the-this-keyword-381ac7a32881
According to https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener ,
my_element.addEventListener('click', (e) => {
console.log(this.className) // WARNING: `this` is not `my_element`
console.log(e.currentTarget === this) // logs `false`
})
so if you use the arrow functions you can go safe beacause they do not have their own this context.
I know this is an older post, but you can also simply assign the context to a variable self, throw your function in an anonymous function that invokes your function with .call(self) and passes in the context.
ticketTable.prototype.render = function(element) {
...
var self = this;
cell1.addEventListener('click', function(evt) { self.handleCellClick.call(self, evt) }, false);
...
};
This works better than the "accepted answer" because the context doesn't need to be assigned a variable for the entire class or global, rather it's neatly tucked away within the same method that listens for the event.
What about
...
cell1.addEventListener("click", this.handleCellClick.bind(this));
...
ticketTable.prototype.handleCellClick = function(e)
{
alert(e.currentTarget.innerHTML);
alert(this.tickets.length);
}
e.currentTarget points to the target which is bound to the "click event" (to the element that raised the event) while
bind(this) preserves the outerscope value of this inside the click event function.
If you want to get an exact target clicked, use e.target instead.
Heavily influenced by kamathln and gagarine's answer I thought I might tackle this.
I was thinking you could probably gain a bit more freedom if you put handeCellClick in a callback list and use an object using the EventListener interface on the event to trigger the callback list methods with the correct this.
function ticketTable(ticks)
{
// tickets is an array
this.tickets = ticks;
// the callback array of methods to be run when
// event is triggered
this._callbacks = {handleCellClick:[this._handleCellClick]};
// assigned eventListenerInterface to one of this
// objects properties
this.handleCellClick = new eventListenerInterface(this,'handleCellClick');
}
//set when eventListenerInterface is instantiated
function eventListenerInterface(parent, callback_type)
{
this.parent = parent;
this.callback_type = callback_type;
}
//run when event is triggered
eventListenerInterface.prototype.handleEvent(evt)
{
for ( var i = 0; i < this.parent._callbacks[this.callback_type].length; i++ ) {
//run the callback method here, with this.parent as
//this and evt as the first argument to the method
this.parent._callbacks[this.callback_type][i].call(this.parent, evt);
}
}
ticketTable.prototype.render = function(element)
{
/* your code*/
{
/* your code*/
//the way the event is attached looks the same
cell1.addEventListener("click", this.handleCellClick, false);
/* your code*/
}
/* your code*/
}
//handleCellClick renamed to _handleCellClick
//and added evt attribute
ticketTable.prototype._handleCellClick = function(evt)
{
// this shouldn't work
alert(this.innerHTML);
// this however might work
alert(evt.target.innerHTML);
// this should work
alert(this.tickets.length);
}
The MDN explanation gives what to me is a neater solution further down.
In this example you store the result of the bind() call, which you can then use to unregister the handler later.
const Something = function(element) {
// |this| is a newly created object
this.name = 'Something Good';
this.onclick1 = function(event) {
console.log(this.name); // undefined, as |this| is the element
};
this.onclick2 = function(event) {
console.log(this.name); // 'Something Good', as |this| is bound to newly created object
};
// bind causes a fixed `this` context to be assigned to onclick2
this.onclick2 = this.onclick2.bind(this);
element.addEventListener('click', this.onclick1, false);
element.addEventListener('click', this.onclick2, false); // Trick
}
const s = new Something(document.body);
In the posters example you would want to bind the handler function in the constructor:
function ticketTable(ticks)
{
// tickets is an array
this.tickets = ticks;
this.handleCellClick = this.handleCellClick.bind(this); // Note, this means that our handleCellClick is specific to our instance, we aren't directly referencing the prototype any more.
}
ticketTable.prototype.render = function(element)
{
var tbl = document.createElement("table");
for ( var i = 0; i < this.tickets.length; i++ )
{
// create row and cells
var row = document.createElement("tr");
var cell1 = document.createElement("td");
var cell2 = document.createElement("td");
// add text to the cells
cell1.appendChild(document.createTextNode(i));
cell2.appendChild(document.createTextNode(this.tickets[i]));
// handle clicks to the first cell.
// FYI, this only works in FF, need a little more code for IE
this.handleCellClick = this.handleCellClick.bind(this); // Note, this means that our handleCellClick is specific to our instance, we aren't directly referencing the prototype any more.
cell1.addEventListener("click", this.handleCellClick, false);
// We could now unregister ourselves at some point in the future with:
cell1.removeEventListener("click", this.handleCellClick);
// add cells to row
row.appendChild(cell1);
row.appendChild(cell2);
// add row to table
tbl.appendChild(row);
}
// Add table to the page
element.appendChild(tbl);
}
ticketTable.prototype.handleCellClick = function()
{
// PROBLEM!!! in the context of this function,
// when used to handle an event,
// "this" is the element that triggered the event.
// this works fine
alert(this.innerHTML);
// this does not. I can't seem to figure out the syntax to access the array in the object.
alert(this.tickets.length);
}
I am creating a game engine for HTML5 Canvas with javascript for personal use, however I am running into a problem. I have created a entities class with a super constructor and a few functions (like remove and add new entities) as well as a update and init function within the class. However when I run the main init at the end of the code, with entities.init(); it reports an error and says it is not a function, even though I'm sure I made it public. Here is the code
function entities(){
//Entities class holds all objects that: take damage, move,and do things that a static object could not.//
//A list of all current entities in game//
var entitiesList = new Array();
//Allows removal of an entitiy from the game, and the current list of entities//
function removeEntity( id){
//snip!//
}
//entity superclass//
function entity( name, spriteName, HP){
//snip!//
var updateEntity = new function(){
console.log("UPDATING Entities")
//drawSprite(sprite, posX, posY);
if(this.timer > 0){
this.timer = this.timer - 1;
}else{
removeEntity(this.entityID);
delete this;
}
if(this.health == 0){
removeEntity(this.entityID);
delete this;
}
}
}
//Method to create a new entity//
function createNewEntity( entName, sprite, posX, posY, HP){
//snip!//
}
var damageField = new function(radius, power, posX, posY) {
//Damage any entities within a "square" radius of an entity. I plan to add radial version later//
//snip!//
}
this.init = function(){
console.log("INIATING ENTS");
createNewEntity("NUGGET", "chaingun_impact.png", 250, 250);
}
//update function for superclass update function to call//
this.update = function(){
entity.updateEntity();
}
}
The main init function
function init(){
pushToSheetList();
jsonParser();
entities.init();
}
Also I am 99.99% sure that the update function is not called either it is the same code pretty much just update() instead.
I am really not sure what to do, unless I want to make it so every sprite on the screen is hard coded manually, and no one wants that for a reusable engine.
You need to create instance of your entities class.
var oEntity=new entities();
oEntity.init();//call init method.
var en = new entities();
en.init();
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().
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.
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.