I am initializing classes like so (Character is a class in my program):
character = new Character();
I want two instances of this class/object, so I tried this:
character2 = new Character();
Yet character2 simply replaces character; therefore, there is only one object. Is it possible to create another instance, or would I need to make another Character class (a lot of code duplication!).
I tried adding a second draw function (named draw2, for the second object), but that didn't help.
You need to move the definition of the Character.prototype.draw method out of the constructor function. Otherwise, each time you create a new Character(), you also override the Character.prototype.draw method.
You also need to replace references to local constructor function variables within the Character.prototype.draw method such as x, y or size with object properties such as this.X, this.Y and this.Size.
Also, you need to make the img a (possibly static) property of your Character.
Object creation in modern JS
Modern JS includes some shortcuts when defining objects. There is no real need to use prototype unless you are creating many instances of the Object. Accessing the prototype adds some overhead when using objects
You can create the object inside the creating function that allows you to define private properties via closure.
Closure creates private properties
function Character() {
var x = 0;
var y = 0;
var size = 25;
var vx = 4;
var vy = 4;
var width = 45;
var height = 45;
var img = new Image();
img.src = 'character.jpg';
var pattern;
// using API = {rather than return { allows you to access the instance of the
// inside this scope without having to use the `this` token
const API = {
get x() { return x },
get y() { return y },
get vx() { return vx },
get vy() { return vy },
get size() { return size },
get width() { return width },
get height() { return height },
set x(v) { x = v },
set y(v) { y = v },
set vx(v) { vx = v },
set vy(v) { vy = v },
set size(v) { size = v },
set width(v) { width = v },
set height(v) { height = v },
draw(ctx) {
ctx.save();
ctx.translate(x, y);
ctx.lineWidth = 2;
ctx.fillStyle = pattern ? pattern = ctx.createPattern(img, "no-repeat") : pattern;
ctx.beginPath();
ctx.moveTo(-size, -size);
ctx.lineTo(-size, size);
ctx.lineTo(size, size);
ctx.lineTo(size, -size);
ctx.closePath();
ctx.fill();
ctx.stroke();
ctx.restore();
},
}
return API;
}
usage
var char = Character();
var char1 = Character();
// or
var char = new Character();
var char1 = new Character();
Performance considerations
If the object is required in performance code you may want to create more performant setters and getters, or avoid the getter setter overhead and include the properties in the object itself.
function Character() {
var img = new Image();
img.src = 'character.jpg';
var pattern;
// using API = {rather than return { allows you to access the instance of the
// inside this scope without having to use the `this` token
const API = {
x : 0,
y : 0,
size : 25,
vx : 4,
vy : 4,
width : 45,
height : 45,
draw(ctx) {
const size = API.size;
ctx.save();
ctx.translate(API.x, API.y); // Note that I use API.x rather than this.x
ctx.lineWidth = 2;
ctx.fillStyle = pattern ? pattern = ctx.createPattern(img, "no-repeat") : pattern;
ctx.beginPath();
ctx.moveTo(-size, -size);
ctx.lineTo(-size, size);
ctx.lineTo(size, size);
ctx.lineTo(size, -size);
ctx.closePath();
ctx.fill();
ctx.stroke();
ctx.restore();
},
}
return API;
}
Notes.
I noticed you have way too many comments.
eg
//public property for VX
Object.defineProperty(this, 'width',
and
//function public draw method
Character.prototype.draw = function (ctx) {
//save the ctx
ctx.save();
//set x and y
ctx.translate(x, y);
//set the line width
ctx.lineWidth = 2;
You are stating the obvious in a comment, no machine will read it, no human needs to read it, so why is it there. Comments add noise, and source code noise is dangerous, avoid all unnecessary noise in your code.
You create the pattern each time the draw function is called, this is unneeded overhead. Create it only once.
There is a time when calling the draw function may not work as the image will not have loaded yet. Maybe you should manage images outside the object where you can ensure that media objects are loaded and ready to be used befor you try to use them.
Each instance of Character will load the image, create 100 of them and there will be 100 copies of the same image. This will affect performance and memory negatively.
Related
When I set the src of an image object, it will trigger an onload function. How can I add parameters to it?
x = 1;
y = 2;
imageObj = new Image();
imageObj.src = ".....";
imageObj.onload = function() {
context.drawImage(imageObj, x, y);
};
x = 3;
y = 4;
In here, I want to use the x and y values that were set at the time I set the src of the image (i.e. 1 and 2). In the code above, by the time the onload function would finish, x and y could be 3 and 4.
Is there a way I can pass values into the onload function, or will it automatically use 1, and 2?
Thanks
All the other answers are some version of "make a closure". OK, that works. I think closures are cool, and languages that support them are cool...
However: there is a much cleaner way to do this, IMO. Simply use the image object to store what you need, and access it in the load handler via "this":
imageObj = new Image();
imageObj.x = 1;
imageObj.y = 2;
imageObj.onload = function() {
context.drawImage(this, this.x, this.y);
};
imageObj.src = ".....";
This is a very general technique, and I use it all the time in many objects in the DOM. (I especially use it when I have, say, four buttons and I want them to all share an "onclick" handler; I have the handler pull a bit of custom data out of the button to do THAT button's particular action.)
One warning: you have to be careful not to use a property of the object that the object class itself has a special meaning or use. (For example: you can't use imageObj.src for any old custom use; you have to leave it for the source URL.) But, in the general case, how are you to know how a given object uses all its properties? Strictly speaking, you can't. So to make this approach as safe as possible:
Wrap up all your custom data in a single object
Assign that object to a property that is unusual/unlikely to be used by the object itself.
In that regard, using "x" and "y" are a little risky as some Javascript implementation in some browser may use those properties when dealing with the Image object. But this is probably safe:
imageObj = new Image();
imageObj.myCustomData = {x: 1, y: 2};
imageObj.onload = function() {
context.drawImage(this, this.myCustomData.x, this.myCustomData.y);
};
imageObj.src = ".....";
Another advantage to this approach: it can save a lot of memory if you are creating a lot of a given object -- because you can now share a single instance of the onload handler. Consider this, using closures:
// closure based solution -- creates 1000 anonymous functions for "onload"
for (var i=0; i<1000; i++) {
var imageObj = new Image();
var x = i*20;
var y = i*10;
imageObj.onload = function() {
context.drawImage(imageObj, x, y);
};
imageObj.src = ".....";
}
Compare to shared-onload function, with your custom data tucked away in the Image object:
// custom data in the object -- creates A SINGLE "onload" function
function myImageOnload () {
context.drawImage(this, this.myCustomData.x, this.myCustomData.y);
}
for (var i=0; i<1000; i++) {
imageObj = new Image();
imageObj.myCustomData = {x: i*20, y: i*10};
imageObj.onload = myImageOnload;
imageObj.src = ".....";
}
Much memory saved and may run a skosh faster since you aren't creating all those anonymous functions. (In this example, the onload function is a one-liner.... but I've had 100-line onload functions, and a 1000 of them would surely be considered spending a lot of memory for no good reason.)
UPDATE: See use of 'data-*' attribute for a standard (and "standards approved") way to do this, in lieu of my ad-hoc suggestion to use myCustomData.
Make a private scope closure that will store x & y values:
imageObj.onload = (function(x,y){
return function() {
context.drawImage(imageObj, x, y);
};
})(x,y);
Make a small function that handles it. Local variables will hold the correct scope.
function loadImage( src, x, y) {
var imageObj = new Image();
imageObj.src = src;
imageObj.onload = function() {
context.drawImage(imageObj, x, y);
};
}
var x = 1,
y = 2;
loadImage("foo.png", x, y);
x = 3;
y = 4;
You could use an anonymous function
x = 1;
y = 2;
(function(xValue, yValue){
imageObj = new Image();
imageObj.src = ".....";
imageObj.onload = function() {
context.drawImage(imageObj, xValue, yValue);
};
})(x,y);
x = 3;
y = 4;
How to simplify this code without repeating the context? Are there any library for canvas api?
I tried using with(context), but it throws an error because I am using 'use strict'. Are there any way around this?
context.save();
context.beginPath();
context.setTransform(1, 0, 0, 1, 0, 0);
context.translate(alien.x, alien.y);
context.rotate(0);
context.moveTo(-15, 0);
context.lineTo(-15, 5);
context.bezierCurveTo(-15, -10, 15, -10, 15, 5);
context.lineTo(15, 0);
context.lineTo(-15, 0);
context.lineTo(-15, 5);
context.lineTo(-20, 5);
context.lineTo(-25, 10);
context.lineTo(25, 10);
context.lineTo(20, 5);
context.lineTo(-20, 5);
context.moveTo(10, 10);
context.lineTo(10, 15);
context.lineTo(15, 15);
context.lineTo(15, 10);
context.moveTo(-10, 10);
context.lineTo(-10, 15);
context.lineTo(-15, 15);
context.lineTo(-15, 10);
context.strokeStyle = '#fff';
context.stroke();
context.closePath();
context.restore();
Write a wrapper object - with this you can also shorten the names as well as extend functionality like setting color and line width at the same time calling stroke and so on.
Update: I also made this library (free/MIT) which do wrapping of context.
Call them what you want - these are just examples of course. Make sure each one returns the this object which is what allows chaining. If you plan only to use a single instance you can put these methods inside the main object instead of using prototypes:
function CTX(ctx) {
this.ctx = ctx;
};
CTX.prototype.move = function(x,y) {this.ctx.moveTo(x, y); return this};
CTX.prototype.line = function(x,y) {this.ctx.lineTo(x, y); return this};
CTX.prototype.bez = function(a,b,c,d,e,f,g) {
this.ctx.bezierTo(a,b,c,d,e,f,g);
return this;
};
// etc.
To use: just wrap your context with this instance:
var ctx = new CTX(context);
ctx .save()
.begin()
.setTrans(1, 0, 0, 1, 0, 0)
.trans(alien.x, alien.y)
.rot(0)
.move(-15, 0)
.line(-15, 5)
.bez(-15, -10, 15, -10, 15, 5)
.line(15, 0)
// etc.
function CTX(ctx) {
this.ctx = ctx;
};
CTX.prototype.begin = function() {
this.ctx.beginPath();
return this;
};
CTX.prototype.move = function(x, y) {
this.ctx.moveTo(x, y);
return this;
};
CTX.prototype.line = function(x, y) {
this.ctx.lineTo(x, y);
return this;
};
CTX.prototype.stroke = function(color) {
if (color) this.ctx.strokeStyle = color;
this.ctx.stroke();
return this;
};
// ... define more here
var ctx = new CTX(canvas.getContext('2d'));
ctx
.begin()
.move(20, 20)
.line(50, 50)
.line(80, 20)
.line(110, 50)
.stroke('#00f');
// etc.
<canvas id=canvas width=500 height=180></canvas>
Here's one way to concisely specify your canvas path data using SVG-like syntax.
I yanked the following from an SVGPath-to-CanvasPath cross-compiler I wrote a while back. The nice thing about SVG path data is that it's very short & concise. It uses a single letter to define drawing command and it combines all the commands into a single string.
// Canvas path drawing commands represented by single command letters
M == moveTo
L == lineTo
Q == quadraticCurveTo
C == bezierCurveTo
R == rect
A == arc
Z == closePath
Y == clip
So your complex canvas path could be represented by a single short string like this:
var data='M-15,0 L-15,5 C-15,-10,15,-10,15,5 L15,0 L-15,0 L-15,5 L-20,5 L-25,10 L25,10 L20,5 L-20,5 M10,10 L10,15 L15,15 L15,10 M-10,10 L-10,15 L-15,15 L-15,10';
Here's example code and a Demo that parses & draws this data string to the Canvas:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var commandExpander={
M: 'moveTo',
L: 'lineTo',
Q: 'quadraticCurveTo',
C: 'bezierCurveTo',
R: 'rect',
A: 'arc',
Z: 'closePath',
Y: 'clip'
}
// your path written as a single string
var data='M-15,0 L-15,5 C-15,-10,15,-10,15,5 L15,0 L-15,0 L-15,5 L-20,5 L-25,10 L25,10 L20,5 L-20,5 M10,10 L10,15 L15,15 L15,10 M-10,10 L-10,15 L-15,15 L-15,10';
// convert the single data string into separate commands
var commands=parseData(data);
// execute the drawing commands
definePath(commands);
// stroke the result
ctx.stroke();
// Take a single string containing path data
// Break it into separate commands and put all commands into an array
// One command consists of a letter and the command arguments
function parseData(data){
var commands=[];
var cmdSegments = data.match(/[a-z][^a-z]*/ig);
for(var i=0;i<cmdSegments.length;i++){
var segment=cmdSegments[i];
var command={letter:null,args:null};
commands.push(command);
command.letter=segment.slice(0,1);
command.args=segment
.slice(1)
.trim()
.replace(/-+/g,' -')
.trim()
.replace(/\s+/g,',')
.replace(/,+/g,',')
.split(",")
.filter(function(e){return e!==''});
for(var a=0;a<command.args.length;a++){
command.args[a]=Number(command.args[a]);
}
}
return(commands);
}
// execute all the commands in the cmds[] array
function definePath(cmds){
ctx.beginPath();
for(var i=0;i<cmds.length;i++){
var command=commandExpander[cmds[i].letter];
var args=cmds[i].args;
ctx[command].apply(ctx,args);
}
}
body{ background-color: ivory; padding:10px; }
#canvas{border:1px solid red;}
<canvas id="canvas" width=300 height=300></canvas>
Javascript does not have a default option for method chaining. Jquery does have this option, but not with the default Canvas methods (although there is a jCanvas plugin).
I think your best option is to write a method that executed a method with a set of arguments, which returns the context, so you can simulate chaining:
//Call on the canvas
//methodname is a string with the methodname;
//arguments is a list of arguments
canvas.prototype.applyMethodToContext = function(methodname, arguments){
this[methodname].apply(this, arguments);
return context;
}
This method is COMPLETELY untested and probably requires some changes to completely work. If you prefer to use a 3rd party solution which has been tested (and doesn't require entering the method as a string), someone has written a small javascript file (80 lines) that allows canvas chaining: https://github.com/millermedeiros/CanvasContext2DWrapper
Have a look at these site it just create a simple canvas for you http://www.w3resource.com/html5/canvas-element.php
I'm trying to construct a base class Shape using Crockford's inheritance pattern. Using this base Shape, I'm trying to draw a circle, a rectangle and a triangle. I'm kinda stuck. I didn't know how to call/modify the base method
function points(x,y) {
x = this.x;
y = this.y;
}
function Shape() {
return {
this.points: [ ],
init : function(){
if(typeof this.context === ‘undefined’){
var canvas = document.getElementById(‘canvas’);
var context = canvas.getContext(‘2d’);
}
},
draw: function(){
var context = this.context;
context.beginPath();
context.moveTo(this.points[0].x, this.points[0].y);
for(var i=1; i< this.parameter.length; i++){
context.lineTo(this.parameter[i].x, this.parameter[i].y);
}
context.closePath();
context.stroke();
}
};
}
function Circle(x, y, r){
var points = Shape();
point.x = x;
points.y = y;
points.r = r;
var baseMethod = that.draw;
that.draw = function(){
/*how to modify the base method to draw circle*/
};
}
function Rectangle(a, b, c, d){
var points = Shape();
point.a = a;
points.b = b;
points.c = c;
points.d = d
var baseMethod = that.draw;
that.draw = function(){
/*how to call base method to draw rectangle*/
};
}
You've got quite a few problems going on with your code. Firstly you need to make sure you've got your basic drawing code working before moving on to more complicated shapes such as circles and rectangles. Start with drawing lines. I've tidied up your code and got it working with drawing straight lines:
//returns basic point object which has
//two properties x & y
function point(x, y) {
return {
x: x,
y: y
}
}
//function that returns a shape object with all the
//mechanisms for drawing lines between points
function Shape(canvasID) {
return {
points: [], //not 'this.points' (which would most likely be window.points)
addPoint: function(x, y) {//adding a point to a shape is an operation of shape
this.points.push(point(x, y))
},
init: function() {
if (typeof this.context === 'undefined') {
var canvas = document.getElementById(canvasID);
var ctx = canvas.getContext('2d');
this.context = ctx; //add the context reference to the current shape object
}
},
draw: function() {
this.init();
var context = this.context;
context.beginPath();
var that = this; //create a local reference to the current 'this' object.
//insures us against any possible 'this' scope problems
context.moveTo(that.points[0].x, that.points[0].y);
for (var i = 1; i < that.points.length; i++) {
context.lineTo(that.points[i].x, this.points[i].y);
}
context.closePath();
context.stroke();
}
};
}
//Simple Line object - good for testing your
//basic drawing functionality
function Line(canvasID, x, y, x2, y2) {
var shape = Shape(canvasID);
shape.addPoint(x, y);
shape.addPoint(x2, y2);
shape.draw();
}
//Execute your drawing functionality after the
//window has loaded to make sure all your objects exist before
//trying to use them
window.onload = function() {
Line('canvas', 100, 100, 200, 200);
}
I'm not necessarily sold on whether this is the best way to approach what you are doing - but DC's basic approach is to create objects without having to use the "new" keyword. So he returns an object from a function call using the JavaScript object notation.
Now that you can draw a line, the next step is to draw a series of connected lines one after the other (a path). After that, create your rectangle. You need some code to tell your code where to start drawing the rectangle (the start x/y coordinate) and then you can have parameters denoting the height and width of the rectangle which will be used to calculate the coordinates of the rectangle's corners and passed to the shape object to be drawn in the same way the series of connected lines were drawn. One caveat, though, is to check if there is some sort of 'createRectangle' function on the context object (and same for circle). I don't actually know myself as I've not done this sort of work in HTML5/canvas - although I have in other environments.
Edit
Forgot to mention that you will need to make sure the doctype declaration of your html is html5. A lot of IDE's will automatically declare your html as html4. Html5 just needs: <!DOCTYPE html>
Also, make sure you declare a canvas element in the html body, something like this:
<canvas id="canvas" width="300" height="150">
</canvas>
I have a piece of js software that is structured like so:
obj = new object[id]();
function wrapperFunction (e) {
var pos = findPos(this);
e._x = e.pageX - pos.x;
e._y = e.pageY - pos.y;
var func = obj[e.type];
if (func) {
func(e);
}
}
__
obj.line = function () {
this.started = false;
this.mousedown = function (e) {
}
this.mousemove = function (e) {
if (this.started) {
}
}
this.mouseup = function (e) {
if (this.started) {
}
}
}
The above code block is duplicated for multiple shapes so there is also a obj.square obj.circle etc...
I also have a shape object that is as follows.
function Shape (type, color, height, width, radius, x, y) {
this.type = type;
this.color = color;
this.h = height;
this.w = width;
this.r = radius;
this.points = ["x","y"];
this.points["x"] = [x];
this.points["y"] = [y];
};
I would like to initiate the shape object on a mousedown for each obj.* and populate the shape object with the propper info.
Now for the issue.
The radius is calcuated on every mousemove as well as height and width but when I add shapes = new Shape(circle, black, 10, 10, null, e._x, e._y) to the mousemove so it looks like...
this.mousemove = function (e) {
if (this.started) {
shapes = new Shape(circle, black, 10, 10, null, e._x, e._y);
}
}
The shape object does not create.
If I create the shape object inside the wrapper function instead of the mousemove then the object initiates but I cannot use radius or height/width.
How can I create an object inside another object inside a wrapper function so I can use calculated terms inside the created object? Is there an alternate route to take besides what I am doing?
Aside from wonkiness in the obj = new object[this.id](); line, I think you're just missing a this keyword:
this.mousemove = function (e) {
if (this.started) {
this.shapes = new Shape(circle, black, 10, 10, null, e._x, e._y);
}
}
Edit just noticed more wonkiness in your code (yes, that's a technical term :). I think you want to change these lines in the constructor:
this.points = ["x","y"]; // creates an array, which is indexed by numbers
this.points["x"] = [x]; // tacks on some ad-hoc properties to the array, which
this.points["y"] = [y]; // doesn't really make sense
to this:
this.points = {x: x, // I think this is what you actually mean to do.
y: y};
I'm trying to update the interval value x but not succeeding. I hope to eventually have different pawn objects with internal values I can update when keypress up/down/left/right to redraw the canvas.
Code Update: Able to update x, y values now, but not sure about creating seperate objects using modular JavaScript pattern.
JavaScript using jQuery 1.5.1:
//Constructors
var pawn = (function() {
var x = 25;
var y = 25;
var getX = function() {
return x;
};
var getY = function() {
return y;
};
function _drawPawn(x,y) {
var x = x || 25;
var y = y || 25;
var canvas = document.getElementById("canvas");
if (canvas.getContext) {
var b = $('body');
var winH = b.height();
var winW = b.width();
$(canvas).attr('height',winH).attr('width',winW);
var ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.lineWidth="3";
ctx.arc(x, y, 10, 0, Math.PI * 2, true); // circle
ctx.stroke();
}
}
function left() {
x = 100;
y = 100;
}
return {
getX:getX,
getY:getY,
draw: function drawPawn(x,y) {
_drawPawn(x,y);
},
left:left
}
})();
//Init
$(function() {
var b = pawn;
b.left();
alert(b.getX());
var a = pawn;
alert(a.getX());
//b.draw();
});
and the html:
<canvas id="canvas" height="800px" width="600px">
Download a modern browser like Internet Explorer 9, Firefox, Safari or Chome to view this.
</canvas>
As it is currently written, your getX() function will be automatically invoked with the supplied (empty) parameter list, and return x, which is then equivalent to:
var getX = x;
which will give getX the value of x at the time it was declared.
To fix, removed the parentheses:
var getX = function() {
return x;
};
You also need to fix this function in your returned object:
draw: function drawPawn(x, y) {
_drawPawn(x, y);
}
Since the apparent intent is to call draw without parameters and use the currently bound x and y values, it should be:
draw: function drawPawn() {
_drawPawn(x, y);
}
First like alnitak said remove paranthese from getX, getY.
If u want different pawn objects you should use var b = new pawn()