So I'm trying to create a method where the you move an image around a canvas element. This is relevant in that in creating many kinds of games, you'd need a background image to move around properly against the canvas and the player's movement. The problem is that you always draw relative to the canvas's (0,0) point in the top left corner. So what I'm going for in a conceptualization where pressing right (for example) would be conceived as moving the CANVAS right, when really you're moving the image left. It could be argued that this is unnecessary, but honestly thinking about it the other way kind of gives me a headache. I think this way of relating everything to a larger absolute field would be easier to program with a large number of objects.
The problem is, I've messed around with my code in Pycharm but I keep getting canvas not defined and similar errors. Please help me fix this up! So without further ado, here's my code! (and any other ways to clean up my code is appreciated, I'm pretty new to JS!)
//Animates a moving black dot on the canvas.
//Variables for parameters
var gameloopId;
var speed=6;
var canvas;
var background;
var circle;
var ctx;
//Wait for document to be ready then start
$(document).ready(function(){
console.log('document is ready');
init();
});
//Holds the relative coordinates.
function Canvas(){
this.x=0;//relative X
this.y=0;//relative Y
//Calulate screen height and width
this.width = parseInt($("#canvas").attr("width"));
this.height = parseInt($("#canvas").attr("height"));
}
canvas=new Canvas();
//Define an object
function Object(){
this.absX=0;
this.absY=0;
this.x=this.absX-canvas.x;
this.y=this.absY-canvas.y;
}
//Circle Object
function Circle(radius){
this.radius=radius;
}
Circle.prototype= new Object(); //Circle is an Object
function drawCircle(){
// Create the circle
ctx.strokeStyle = "#000000";
ctx.fillStyle = "#000000";
ctx.beginPath();
ctx.arc(circle.x,circle.y,circle.radius,0,Math.PI*2,true);
ctx.closePath();
ctx.stroke();
ctx.fill();
}
Background= Image();
Background.prototype=new Object(); //Background is an Object
background= new Background()
function drawBackground(){
//draw the background
ctx.drawImage(background,background.x,background.y);
}
function init(){
console.log('function init()');
initSettings();
//Insert event handler for keyboard movement of circle (space clearInterval)
$(document).keydown(function(e){
if(e.keyCode=='37'){ //Left key
circle.absX+=speed;
canvas.x+=speed;}
if(e.keyCode=='38'){ //Up key
circle.absY-=speed;
canvas.y-=speed;}
if(e.keyCode=='39'){ //Right key
circle.absX+=speed;
canvas.x+=speed;}
if(e.keyCode=='40'){ //Down key
circle.absX+=speed;
canvas.y+=speed;}
if(e.keyCode=='32'){ //Space Bar
console.log('spacebar');
clearInterval(gameloopId);
initSettings();
gameloopId = setInterval(gameLoop,10);
}
});
$(document).keyup(function(e){
if(e.keyCode=='37'){
console.log('left');}//Left key
if(e.keyCode=='38'){
console.log('up');}//Up key
if(e.keyCode=='39'){
console.log('right');}//Right key
if(e.keyCode=='40'){
console.log('down');}//Down key
});
//Initialize loop of "game"
gameloopId = setInterval(gameLoop,10);
}
function initSettings(){
console.log('initSettings');
//Set up canvas
ctx = document.getElementById('canvas').getContext('2d');
//center circle on the horizontal axis
console.log('setting circle coords');
circle = new Circle(15);
circle.x = parseInt(canvas.width/2);
circle.y = canvas.height - 40;
//Put background at (0,0)
background.x=0;
background.y=0;
background.src="http://127.0.0.1:8000/static/back.jpg";
console.log("background width:"+background.width);
console.log("background height:"+background.height);
}
function gameLoop(){
//console.log('function gameLoop()');
//Has it reached far left side?
if(circle.x<circle.radius)
{
circle.x=circle.radius
}
//Has it reached far right side?
if(circle.x>canvas.width - circle.radius)
{
circle.x=canvas.width - circle.radius
}
//Has it reached top?
if(circle.y<circle.radius)
{
circle.y=circle.radius
}
//has it reached bottom?
if(circle.y>canvas.height - circle.radius)
{
circle.y=canvas.height - circle.radius
}
//has background reached left?
if(background.x < canvas.width-background.width)
{
background.x= canvas.width-background.width;
}
//has background reached right?
if(background.x>0)
{
background.x=0;
}
//has background reached top?
if(background.y < canvas.height-background.height)
{
background.y = canvas.height-background.height;
}
//has background reached bottom?
if(background.y>0)
{
background.y=0;
}
//Clear the screen (i.e. a draw a clear rectangle the size of the screen)
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
//draw background
drawBackground();
// draw the circle
drawCircle();
ctx.restore();
}
EDIT:(UPDATED CODE!)
//Animates a moving black dot on the canvas.
//Variables for parameters
var gameloopId;
var speed=6;
var canvas;
var background;
var circle;
var ctx;
//Wait for document to be ready then start
$(document).ready(function(){
console.log('document is ready');
init();
});
//Holds the relative coordinates.
function Canvas(){
this.x=0;//relative X
this.y=0;//relative Y
//Calulate screen height and width
this.width = parseInt($("#canvas").attr("width"));
this.height = parseInt($("#canvas").attr("height"));
}
//Define an object
function MyObject(){
this.absX=0;
this.absY=0;
this.x=this.absX-canvas.x;
this.y=this.absY-canvas.y;
}
//Circle MyObject
function Circle(radius){
this.radius=radius;
}
Circle.prototype= new MyObject(); //Circle is an MyObject
function drawCircle(){
// Create the circle
ctx.strokeStyle = "#000000";
ctx.fillStyle = "#000000";
ctx.beginPath();
ctx.arc(circle.x,circle.y,circle.radius,0,Math.PI*2,true);
ctx.closePath();
ctx.stroke();
ctx.fill();
}
function Background(){
this.img= Image();
}
Background.prototype=new MyObject(); //Background is an MyObject
function drawBackground(){
//draw the background
ctx.drawImage(background,background.x,background.y);
}
function init(){
console.log('function init()');
initSettings();
//Insert event handler for keyboard movement of circle (space clearInterval)
$(document).keydown(function(e){
if(e.keyCode=='37'){ //Left key
circle.absX+=speed;
canvas.x+=speed;}
if(e.keyCode=='38'){ //Up key
circle.absY-=speed;
canvas.y-=speed;}
if(e.keyCode=='39'){ //Right key
circle.absX+=speed;
canvas.x+=speed;}
if(e.keyCode=='40'){ //Down key
circle.absX+=speed;
canvas.y+=speed;}
if(e.keyCode=='32'){ //Space Bar
console.log('spacebar');
clearInterval(gameloopId);
initSettings();
gameloopId = setInterval(gameLoop,10);
}
});
$(document).keyup(function(e){
if(e.keyCode=='37'){
console.log('left');}//Left key
if(e.keyCode=='38'){
console.log('up');}//Up key
if(e.keyCode=='39'){
console.log('right');}//Right key
if(e.keyCode=='40'){
console.log('down');}//Down key
});
//Initialize loop of "game"
gameloopId = setInterval(gameLoop,10);
}
function initSettings(){
console.log('initSettings');
//Set up canvas
canvas=new Canvas();
ctx = document.getElementById('canvas').getContext('2d');
//center circle on the horizontal axis
console.log('setting circle coords');
circle = new Circle(15);
circle.x = parseInt(canvas.width/2);
circle.y = canvas.height - 40;
//Put background at (0,0)
background= new Background();
background.x=0;
background.y=0;
background.img.src="http://127.0.0.1:8000/static/back.jpg";
console.log("background width:"+background.width);
console.log("background height:"+background.height);
}
function gameLoop(){
//console.log('function gameLoop()');
//Has it reached far left side?
if(circle.x<circle.radius)
{
circle.x=circle.radius
}
//Has it reached far right side?
if(circle.x>canvas.width - circle.radius)
{
circle.x=canvas.width - circle.radius
}
//Has it reached top?
if(circle.y<circle.radius)
{
circle.y=circle.radius
}
//has it reached bottom?
if(circle.y>canvas.height - circle.radius)
{
circle.y=canvas.height - circle.radius
}
//has background reached left?
if(background.x < canvas.width-background.width)
{
background.x= canvas.width-background.width;
}
//has background reached right?
if(background.x>0)
{
background.x=0;
}
//has background reached top?
if(background.y < canvas.height-background.height)
{
background.y = canvas.height-background.height;
}
//has background reached bottom?
if(background.y>0)
{
background.y=0;
}
//Clear the screen (i.e. a draw a clear rectangle the size of the screen)
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
//draw background
drawBackground();
// draw the circle
drawCircle();
ctx.restore();
}
enter code here
I don't think you can write your own Object You definitely can't use Object, it's a reserved keyword. Object is the built in javascript object that all objects inherit from. You have basically overwritten it. That could be your problem.
Try calling it myObject to check if this is the problem.
//Define an myObject
function myObject(){
this.absX=0;
this.absY=0;
this.x=this.absX-canvas.x;
this.y=this.absY-canvas.y;
}
Circle.prototype= new myObject(); //Circle is a myObject
Background= Image();
Background.prototype=new Object(); //Background is an Object
background= new Background()
seems suspicious.
edit : Background is an Element. You add a prototype even though it is not a function.
Then you call Background as a constructor but it is not.
So background is likely to be undefined. I am surprised background.x gives you 0.
By the way, you should parseInt(arg, 10) to get your result in decimal and not octal.
I figured it out! I had a lot of stupid stuff in my ode and lots of bugs - for example background.img is an Image, but all over the place I was trying to get background.width instead of background.img.width. I also refactored several functions to make things prettier (to em at least). Thanks to the above for your help! Here's my "final" code, at least as of right now:
//Animates a moving black dot on the canvas.
//Variables for parameters
var gameloopId;
var speed=6;
//var canvas;
var background;
var circle;
var ctx;
//Wait for document to be ready then start
$(document).ready(function(){
console.log('document is ready');
init();
});
//Holds the relative coordinates.
var canvas = new function Canvas(){
this.x=0;//relative X
this.y=0;//relative Y
//Calulate screen height and width
this.width = parseInt($("#canvas").attr("width"));
this.height = parseInt($("#canvas").attr("height"));
};
//Define an object
function MyObject(){
this.absX=0;
this.absY=0;
this.x=this.absX-canvas.x;
this.y=this.absY-canvas.y;
this.updateplace = function (){
this.x=this.absX-canvas.x;
this.y=this.absY-canvas.y;
};
}
//Circle MyObject
function Circle(radius){
this.radius=radius;
this.draw=function(){
// Create the circle
ctx.strokeStyle = "#000000";
ctx.fillStyle = "#000000";
ctx.beginPath();
ctx.arc(circle.x,circle.y,circle.radius,0,Math.PI*2,true);
ctx.closePath();
ctx.stroke();
ctx.fill();
}
}
Circle.prototype= new MyObject(); //Circle is an MyObject
function Background(){
this.img= Image();
this.draw=function(){
ctx.drawImage(background.img,background.x,background.y);
}
}
Background.prototype=new MyObject(); //Background is an MyObject
function init(){
console.log('function init()');
initSettings();
//Insert event handler for keyboard movement of circle (space clearInterval)
$(document).keydown(function(e){
if(e.keyCode=='37'){ //Left key
circle.absX-=speed;
canvas.x-=speed;}
if(e.keyCode=='38'){ //Up key
circle.absY-=speed;
canvas.y-=speed;}
if(e.keyCode=='39'){ //Right key
circle.absX+=speed;
canvas.x+=speed;}
if(e.keyCode=='40'){ //Down key
circle.absY+=speed;
canvas.y+=speed;}
if(e.keyCode=='32'){ //Space Bar
console.log('spacebar');
clearInterval(gameloopId);
initSettings();
gameloopId = setInterval(gameLoop,10);
}
});
$(document).keyup(function(e){
if(e.keyCode=='37'){
console.log('left');}//Left key
if(e.keyCode=='38'){
console.log('up');}//Up key
if(e.keyCode=='39'){
console.log('right');}//Right key
if(e.keyCode=='40'){
console.log('down');}//Down key
});
//Initialize loop of "game"
gameloopId = setInterval(gameLoop,10);
}
function initSettings(){
console.log('initSettings');
//Set up canvas
ctx = document.getElementById('canvas').getContext('2d');
canvas.width = parseInt($("#canvas").attr("width"));
canvas.height = parseInt($("#canvas").attr("height"));
//center circle on the horizontal axis
console.log('setting circle coords');
circle = new Circle(15);
circle.absX = parseInt(canvas.width/2);
circle.absY = canvas.height - 40;
//Put background at (0,0)
background= new Background();
background.x=0;
background.y=0;
background.img.src="http://127.0.0.1:8000/static/back.jpg";
console.log("background width:"+background.img.width);
console.log("background height:"+background.img.height);
console.log("Right Bound:"+(background.img.width- canvas.width))
}
function gameLoop(){
//console.log('function gameLoop()');
//Has it reached far left side?
if(circle.absX<circle.radius)
{
circle.absX=circle.radius
}
//Has it reached far right side?
if(circle.absX>background.img.width - circle.radius)
{
circle.absX=background.img.width - circle.radius
}
//Has it reached top?
if(circle.absY<circle.radius)
{
circle.absY=circle.radius
}
//has it reached bottom?
if(circle.absY>background.img.height - circle.radius)
{
circle.absY=background.img.height - circle.radius
}
//has canvas reached right bound?
if(canvas.x > background.img.width- canvas.width)
{
canvas.x= background.img.width- canvas.width;
}
//has canvas reached left bound?
if(canvas.x<0)
{
canvas.x=0;
}
//has background reached bottom bound?
if(canvas.y > background.img.height - canvas.height)
{
canvas.y = background.img.height - canvas.height;
}
//has background reached top bound?
if(canvas.y<0)
{
canvas.y=0;
}
//Clear the screen (i.e. a draw a clear rectangle the size of the screen)
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
//draw background
background.updateplace();
background.draw();
// draw the circle
circle.updateplace();
circle.draw();
ctx.restore();
}
Related
So I am trying to make a simple space game. You will have a ship that moves left and right, and Asteroids will be generated above the top of the canvas at random X position and size and they will move down towards the ship.
How can I create Asteroid objects in seperate positions? Like having more than one existing in the canvas at once, without creating them as totally seperate objects with seperate variables?
This sets the variables I would like the asteroid to be created on.
var asteroids = {
size: Math.floor((Math.random() * 40) + 15),
startY: 100,
startX: Math.floor((Math.random() * canvas.width-200) + 200),
speed: 1
}
This is what I used to draw the asteroid. (It makes a hexagon shape with random size at a random x coordinate)
function drawasteroid() {
this.x = asteroids.startX;
this.y = 100;
this.size = asteroids.size;
ctx.fillStyle = "#FFFFFF";
ctx.beginPath();
ctx.moveTo(this.x,this.y-this.size*0.5);
ctx.lineTo(this.x+this.size*0.9,this.y);
ctx.lineTo(this.x+this.size*0.9,this.y+this.size*1);
ctx.lineTo(this.x,this.y+this.size*1.5);
ctx.lineTo(this.x-this.size*0.9,this.y+this.size*1);
ctx.lineTo(this.x-this.size*0.9,this.y);
ctx.fill();
}
I included ALL of my code in this snippet. Upon running it, you will see that I currently have a ship that moves and the asteroid is drawn at a random size and random x coordinate. I just need to know about how to go about making the asteroid move down while creating other new asteroids that will also move down.
Thank You for all your help! I am new to javascript.
// JavaScript Document
////// Variables //////
var canvas = {width:300, height:500, fps:30};
var score = 0;
var player = {
x:canvas.width/2,
y:canvas.height-100,
defaultSpeed: 5,
speed: 10
};
var asteroids = {
size: Math.floor((Math.random() * 40) + 15),
startY: 100,
startX: Math.floor((Math.random() * canvas.width-200) + 200),
speed: 1
}
var left = false;
var right = false;
////// Arrow keys //////
function onkeydown(e) {
if(e.keyCode === 37) {
left = true;
}
if(e.keyCode === 39) {
right = true;
}
}
function onkeyup(e) {
if (e.keyCode === 37) {
left = false;
}
if(e.keyCode === 39) {
right = false;
}
}
////// other functions //////
//function to clear canvas
function clearCanvas() {
ctx.clearRect(0,0,canvas.width,canvas.height);
}
// draw the score in the upper left corner
function drawscore(score) {
var score = 0;
ctx.fillStyle = "#FFFFFF";
ctx.fillText(score,50,50);
}
// Draw Player ship.
function ship(x,y) {
var x = player.x;
var y = player.y;
ctx.fillStyle = "#FFFFFF";
ctx.beginPath();
ctx.moveTo(x,y);
ctx.lineTo(x+15,y+50);
ctx.lineTo(x-15,y+50);
ctx.fill();
}
// move player ship.
function moveShip() {
document.onkeydown = onkeydown;
document.onkeyup = onkeyup;
if (left === true && player.x > 50) {
player.x -= player.speed;
}
if (right === true && player.x < canvas.width - 50) {
player.x += player.speed;
}
}
// Draw Asteroid
function drawasteroid() {
this.x = asteroids.startX;
this.y = 100;
this.size = asteroids.size;
ctx.fillStyle = "#FFFFFF";
ctx.beginPath();
ctx.moveTo(this.x,this.y-this.size*0.5);
ctx.lineTo(this.x+this.size*0.9,this.y);
ctx.lineTo(this.x+this.size*0.9,this.y+this.size*1);
ctx.lineTo(this.x,this.y+this.size*1.5);
ctx.lineTo(this.x-this.size*0.9,this.y+this.size*1);
ctx.lineTo(this.x-this.size*0.9,this.y);
ctx.fill();
}
// move Asteroid
function moveAsteroid() {
//don't know how I should go about this.
}
// update
setInterval (update, 1000/canvas.fps);
function update() {
// test collisions and key inputs
moveShip();
// redraw the next frame of the animation
clearCanvas();
drawasteroid();
drawscore();
ship();
}
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>My Game</title>
<script src="game-functions.js"></script>
<!--
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.7/angular.min.js"></script>
-->
</head>
<body>
<canvas id="ctx" width="300" height="500" style="border: thin solid black; background-color: black;"></canvas>
<br>
<script>
////// Canvas setup //////
var ctx = document.getElementById("ctx").getContext("2d");
</script>
</body>
</html>
You want to make the creation of asteroids dynamic...so why not set up a setInterval that gets called at random intervals as below. You don't need a separate declaration for each Asteroids object you create. You can just declare a temporary one in a setInterval function. This will instantiate multiple different objects with the same declaration. Of course you need to store each object somewhere which is precisely what the array is for.
You also have to make sure that asteroids get removed from the array whenever the moveAsteroid function is called if they are off of the canvas. The setInterval function below should be called on window load and exists alongside your main rendering setInterval function.
You are also going to have to change your moveAsteroid function a bit to be able to point to a specific Asteroids object from the array. You can do this by adding the Asteroids object as a parameter of the function or by making the function a property of the Asteroids class and using this. I did the latter in the example below.
var astArray = [];
var manageAsteroidFrequency = 2000;
var Asteroids {
X: //something
Y://something
speed:1
move: function() {
this.X -= speed;
}
}
var mainRenderingFunction = setInterval( function() {
for (var i = astArray.length-1 ; i > -1; i --){
if(astArray[i].Y < 0){
astArray.splice(i, 1)
}else{
astArray[i].move;
}
}
}, 40);
var manageAsteroids = setInterval( function () {
if (astArray.length < 4){
var tmpAst = new Asteroids();
astArray.push(tmpAst);
}
manageAsteroidFrequency = Math.floor(Math.random()*10000);
}, manageAsteroidFrequency);
I have been trying to draw gaussin-like function using bezierCurveTo
find the code below
<canvas id="thisCan" width="0px" height="0px" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.
</canvas>
<script>
(function() {
var
// Obtain a reference to the canvas element
// using its id.
htmlCanvas = document.getElementById('thisCan'),
// Obtain a graphics context on the
// canvas element for drawing.
ctx = htmlCanvas.getContext('2d');
var width = 0;
var height = 0;
// Start listening to resize events and
// draw canvas.
initialize();
function initialize()
{
// Register an event listener to
// call the resizeCanvas() function each time
// the window is resized.
window.addEventListener('resize', resizeCanvas, false);
// Draw canvas border for the first time.
resizeCanvas();
}
// Display custom canvas.
// In this case it's a blue, 5 pixel border that
// resizes along with the browser window.
function redraw()
{
ctx.beginPath();
ctx.moveTo(width/2, 0);
ctx.bezierCurveTo(150, 119, 186, 121, 66, 185);
ctx.moveTo(width/2 + width * 0.08 , 0);
ctx.bezierCurveTo(344, 119, 344, 121, 504, 185);
ctx.stroke();
}
// Runs each time the DOM window resize event fires.
// Resets the canvas dimensions to match window,
// then draws the new borders accordingly.
function resizeCanvas()
{
var contentElement = document.getElementsByClassName("content-box post")[0]
width = htmlCanvas.width = (contentElement.clientWidth * 0.75)
height = htmlCanvas.height = (contentElement.offsetWidth*0.75 * 0.5);
redraw();
}
})();
</script>
I am planning to draw many curves in between as well. But how do I make it parametric, based on width and height variables?
I need to specify the control points using width and height parameters, so that it becomes window-size invariant.
Is there a way?
Don't use Bezier curves if you want an exponent curve, they're different functions. Instead, just plot the function you actually need, with something like http://jsbin.com/nubutodosu/edit?js,output, where you define a Gaussian object:
// Gaussian distribution generator
var Gaussian = function(mean, std) {
this.mean = mean;
this.std = std;
this.a = 1/Math.sqrt(2*Math.PI);
};
Gaussian.prototype = {
addStd: function(v) {
this.std += v;
},
get: function(x) {
var f = this.a / this.std;
var p = -1/2;
var c = (x-this.mean)/this.std;
c *= c;
p *= c;
return f * Math.pow(Math.E, p);
},
generateValues: function(start, end) {
var LUT = [];
var step = (Math.abs(start)+Math.abs(end)) / 100;
for(var i=start; i<end; i+=step) {
LUT.push(this.get(i));
}
return LUT;
}
};
And then you can give that a draw routine so that it can plot itself over the interval that you need:
...
draw: function(ctx) {
var points = this.generateValues(-10,10);
var len = points.length;
ctx.strokeStyle = "black";
ctx.beginPath();
var p0 = points[0];
ctx.moveTo(0, height - (height*p0));
points.forEach(function(p,i) {
if(i===0) {
return;
}
ctx.lineTo(width * i/len, height - (height*p));
p0 = p;
});
ctx.stroke();
}
...
So you build your array of values over the interval, then draw them on the canvas by "connecting the dots".
I managed to resolve it.
To be specific, I was looking out for half-gaussian curve.
I managed to figure out that guassian function has a very special property with respect to bezier curves. It might be elementary, but I could not find it on google. So this might be informative finding.
If each control points of the cubic bezier curve reside on a "line parallel to X axis and passing through start point/end point", the resulting curve will be of a half guassian shape.
e.g.
On top of this finding, I have written the following code:
<canvas id="thisCan" width="0px" height="0px">
Your browser does not support the HTML5 canvas tag.
</canvas>
<script>
(function() {
var
// Obtain a reference to the canvas element
// using its id.
htmlCanvas = document.getElementById('thisCan'),
// Obtain a graphics context on the
// canvas element for drawing.
ctx = htmlCanvas.getContext('2d');
// Start listening to resize events and
// draw canvas.
initialize();
function initialize()
{
// Register an event listener to
// call the resizeCanvas() function each time
// the window is resized.
window.addEventListener('resize', resizeCanvas, false);
// Draw canvas border for the first time.
resizeCanvas();
}
// Display custom canvas.
// In this case it's a blue, 5 pixel border that
// resizes along with the browser window.
function redraw(width, height)
{
var start = width/2;
var margin = width * 0.01;
var height = (width / 3) - (margin * 4);
var end_step = width/4
ctx.beginPath();
ctx.moveTo(start - margin, 0);
ctx.bezierCurveTo((start - height/3), 0 , (start - height/3), height , end_step*1, height);
ctx.moveTo(start + margin, 0);
ctx.bezierCurveTo((start + height/3), 0 , (start + height/3), height , end_step*3, height);
ctx.moveTo(start - margin, 0);
ctx.bezierCurveTo((start - height*0.33), 0 , (start - height*0.16), height , end_step*1.5, height);
ctx.moveTo(start + margin, 0);
ctx.bezierCurveTo((start + height*0.33), 0 , (start + height*0.16), height , end_step*2.5, height);
ctx.moveTo(start, 0);
ctx.bezierCurveTo((start ), 0 , (start ), height , end_step*2, height);
ctx.stroke();
}
// Runs each time the DOM window resize event fires.
// Resets the canvas dimensions to match window,
// then draws the new borders accordingly.
function resizeCanvas()
{
var width = 0;
var height = 0;
var contentElement = document.getElementsByClassName("content-box post")[0]
width = htmlCanvas.width = (contentElement.clientWidth * 0.85)
height = htmlCanvas.height = (contentElement.offsetWidth*0.85 * 0.33);
redraw(width, height);
}
})();
</script>
and the output:
I need to refresh an HTML5 canvas every two or three seconds.
setInterval(writeCanvas, 2000);
This canvas is filled with points and lines. Each abscissa and ordinate is stored in an XML file. So before updating the canvas I do an async request to the file on the server.
The problem is that the canvas blinks. I guess it disappears while the async request is running.
How could I get around this issue?
Here is the code of writeCanvas:
function drawLines(ctx, back, front, width, xArray, yArray) {
ctx.strokeStyle = back;
ctx.fillStyle = front;
ctx.lineWidth = width;
ctx.beginPath();
ctx.moveTo(xArray[0], yArray[0]);
for (var i=1; i<xArray.length; i++) {
ctx.lineTo(xArray[i],yArray[i]);
}
ctx.fill();
ctx.stroke();
ctx.closePath();
}
function drawPoint(ctx, back, front, x, y, radius, startAngle, endAngle) {
ctx.strokeStyle = back;
ctx.fillStyle = front;
ctx.beginPath();
ctx.arc(x,y,radius,startAngle,endAngle,endAngle);
ctx.fill();
ctx.stroke();
ctx.closePath();
}
function writeLabel(ctx, color, font, x, y, text) {
ctx.fillStyle = color;
ctx.font = font;
ctx.beginPath();
if(x < 0) {
x = 0;
}
ctx.fillText(text, x, y);
ctx.fill();
ctx.closePath();
}
function writeCanvas()
{
var elem = document.getElementById('profileCanvas');
if (!elem || !elem.getContext) {
return;
}
var ctx = elem.getContext('2d');
if (!ctx) {
return;
}
// apply the final size to the canvas
elem.setAttribute('width', canvasWidth);
elem.setAttribute('height', canvasHeight);
$.get('profileStatus.xml', function(xml) {
if(xml) {
var testPoints = new Array();
$(xml).find('TP').each(function() {
var selected = $(this).find('SELECTED:first').text();
if(selected == "YES") {
var name = $(this).find('MODULE_NAME:first').text();
var state = $(this).find('STATE:first').text();
var tp = new ProfileTp(name, state, selected);
testPoints.push(tp);
}
});
$.get('profile.xml', function(data) {
if(data) {
profileWidth = parseFloat($(data).find('MAIN > PROFILE > DIM_W').first().text());
profileHeight = parseFloat($(data).find('MAIN > PROFILE > DIM_H').first().text());
var backgroundColor = '#ddd';
var color = '#323232';
ctx.translate(0,canvasHeight);
var xArray = new Array();
var yArray = new Array();
$(data).find('PROFILE > POINT > X').each(function(){
var x=parseFloat($(this).text());
xArray.push(x);
});
$(data).find('PROFILE > POINT > Y').each(function(){
var y=parseFloat($(this).text());
yArray.push(y);
});
drawLines(ctx, backgroundColor, color, 2, xArray, yArray);
var finalArray = new Array();
$(data).find('TESTPOINTS > TP').each(function() {
var labelName = $(this).find('MODULE_NAME:first').text();
var tp = $.grep(testPoints, function(obj){ return obj.NAME == labelName; });
if(tp.length == 1) {
$(this).find('IHM').each(function(){
tp[0].LABEL_X = parseFloat($(this).find('LABEL > X:first').text());
tp[0].LABEL_Y = parseFloat($(this).find('LABEL > Y:first').text());
tp[0].MARKER_X = parseFloat($(this).find('MARKER > X:first').text());
tp[0].MARKER_Y = parseFloat($(this).find('MARKER >Y:first').text());
});
finalArray.push(tp[0]);
}
});
for(var i=0; i<finalArray.length; i++) {
writeLabel(ctx, color, fontSize+"px Arial",(finalArray[i].MARKER_X+finalArray[i].LABEL_X),(finalArray[i].MARKER_Y+finalArray[i].LABEL_Y), finalArray[i].NAME);
drawPoint(ctx, backgroundColor, color, finalArray[i].MARKER_X, finalArray[i].MARKER_Y, 8, 0, 2*Math.PI);
}
} else {
console.error('No XML test points returned');
}
});
}
});
}
There are two XML files. One contains all the points, lines and labels. The second contains only the points and labels that have to be displayed.
Setting a canvas' dimensions clears it entirely, so the lines :
elem.setAttribute('width', canvasWidth);
elem.setAttribute('height', canvasHeight);
are likely to make your canvas 'blink'. GET requests are asynchronous so the canvas is cleared way before points data are computed and drawn.
To fix this, change the dimensions inside your requests callbacks, right before drawing.
Crogo already mention the probable cause in the answer, but as a work around you could do:
if (elem.width !== canvasWidth || elem.height !== canvasHeight) {
// apply the final size to the canvas
elem.setAttribute('width', canvasWidth);
elem.setAttribute('height', canvasHeight);
}
The canvas size is only set if the size change.
You should also try to avoid using setInterval (what if client is on a slow/unstable connection that makes it take longer than 2 second to load data..). If the download is still in progress and setInterval triggers you will initiate another download while the first one is still downloading. You will also risk get "double drawings" to the canvas as these calls stack up in the event queue:
Rather trigger an setTimeout from inside your writeCanvas():
function writeCanvas()
{
//... load and draw
setTimeout(writeCanvas, 1500); //compensate for time
}
Of course, if the data MUST be loaded every two seconds this would be inaccurate (not that setInterval is.. they both give an estimate only).
I am trying to draw over a canvas by clicking and dragging the mouse. My problem is that, apart from the fact that the line has a very poor quality (I want a more pronounced border) it only respects the mouse position when this is at 0,0. As I move the mouse to the lower corner, the line increments its distance from it as much as when I am in the middle of the canvas, the line is already out of it.
I have my code at: http://jsfiddle.net/ajTkP/12/
I will also post it here:
var MDown = false;
var Color = 'blue';
var Canvas = document.getElementById('canvas');
var Context = Canvas.getContext('2d');
Canvas.onselectstart = function() { return false; };
Canvas.unselectable = "on";
Canvas.style.MozUserSelect = "none";
Canvas.onmousedown = function(e) {
MDown = true;
Context.strokeStyle = Color;
Context.lineWidth = 3;
Context.lineCap = 'round';
Context.beginPath();
Context.moveTo(e.pageX - Position(Canvas).left, e.pageY - 5);
}
Canvas.onmouseup = function() { MDown = false; };
Canvas.onmousemove = function(e) {
if (MDown) {
Context.lineTo(e.pageX - Position(Canvas).left, e.pageY - 5);
Context.stroke();
}
}
function Position(el) {
var position = {left: 0, top: 0};
if (el) {
if (!isNaN(el.offsetLeft) && !isNaN(el.offsetTop)) {
position.left += el.offsetLeft;
position.top += el.offsetTop;
}
}
return position;
}
Thanks for your help!
You need to set an explicit width and height on the canvas. The default dimensions of a canvas are a width of 300 and a height of 150 (see the spec here). By setting the width and height via CSS you are just stretching the canvas.
Either do:
<canvas id="canvas" width="300" height="200"></canvas>
or set the width/height via JavaScript:
canvas.width = 300;
canvas.height = 200;
See the updated jsfiddle: http://jsfiddle.net/ajTkP/13/
It looks like jimr beat me to the punch about the canvas height and width.
The poor quality of the line though is due to how you're drawing the line. You'll notice that you're calling stroke() on every onmousemove event. Keep in mind that it's keeping track of the path of the line from when you beginPath() to when you closePath(), so you're basically stroking the same line multiple times (every time your mouse moves). This is what's giving you the aliased (blocky-looking) lines, instead of the smooth anti-aliased lines you're expecting.
I've been messing around with the canvas element in html5, and this is what I've got after a bit of experimenting
function canvasMove(e) {
var canvas = document.getElementById('game');
if(canvas.getContext) {
var draw = canvas.getContext('2d');
draw.fillStyle = 'rgba(0,0,0,0.5)';
draw.fillRect('10', '10', '100', '100');
var code;
if (!e) var e = window.event;
if (e.keyCode) code = e.keyCode;
else if (e.which) code = e.which;
var character = String.fromCharCode(code);
if(character == '&') { draw.translate(0, -10); }
if(character == '(') { draw.translate(0, 10); }
if(character == '%') { draw.translate(-10, 0); }
if(character == "'") { draw.translate(10, 0); }
}
}
What it does is moves the rectangle whenever you press the arrow keys [Arrow keys were showing up as &, (, % and ', not sure if this is the same for everyone but it's just an experiment]. Anyway, I can move the rectangle about but it leaves a sort of residue, as in it doesn't delete it's previous form, so what I get is a very basic etch-n'-sketch using a very thick brush.
What I want to do is be able to delete the previous form of the rectangle so that only the new translated version is left.
On top of that I'd like to know how to make it move say, horizontally, by pressing maybe left and up simultaneously. I am aware my code probably isn't very versatile, but any help us much appreciated.
Thanks :)
I made an example for you. Your HTML has to call my init() function. I used:
<body onLoad="init()">
Let me know if you have any problems with it
var canvas;
var draw;
var WIDTH;
var HEIGHT;
var x = 10;
var y = 10;
// in my html I have <body onLoad="init()">
function init() {
canvas = document.getElementById('game');
HEIGHT = canvas.height;
WIDTH = canvas.width;
draw = canvas.getContext('2d');
// every 30 milliseconds we redraw EVERYTHING.
setInterval(redraw, 30);
// canvas.keydown = canvasMove;
document.onkeydown = canvasMove;
}
//wipes the canvas context
function clear(c) {
c.clearRect(0, 0, WIDTH, HEIGHT);
}
//clears the canvas and draws the rectangle at the appropriate location
function redraw() {
clear(draw);
draw.fillStyle = 'rgba(0,0,0,0.5)';
draw.fillRect(x, y, '100', '100');
}
function canvasMove(e) {
if(e.keyCode == '38') { y -= 1; }
if(e.keyCode == '40') { y += 1; }
if(e.keyCode == '37') { x -= 1; }
if(e.keyCode == "39") { x += 1; }
}
To answer the first question here is a function to clear a canvas. A is a reference to canvas element though you could edit what parameters it takes. You would need to call this every time before you draw a new rectangle.
function clear(a){
a.getContext('2d').clearRect(0,0,a.width,a.height);
}
I think in the second question you meant move at an angle. As far as I know that would be a little difficult because you would have record the key press and then set a timeout to see if another one was pressed within some amount of time. Then create a function to move both of those directions or just one if no other arrow keys were pressed. Right now your function would kind of work if both key were pressed, but the rectangle would jerk left and then up.
If you wanted to do this without redrawing the canvas, you can do a save on the context, set your transformation and then clear the rectangle off your screen while still remembering where that rectangle would have been drawn. Then call the translate to move the rectangle to its new position.
<!DOCTYPE html>
<body>
<button onClick="canvasMove('down');">Down</button>
<canvas id="game" style="padding-top:200px;background-color:white;"
height="300" width="300" />
</body>
<script>
const canvas = document.getElementById('game');
const draw = canvas.getContext('2d');
draw.fillStyle = 'rgba(0,0,0,0.5)';
draw.fillRect('10', '10', '100', '100');
function canvasMove(direction) {
// save the current state of the canvas and then clear the canvas,
// which removes any object on your canvas
draw.save();
draw.setTransform(1, 0, 0, 1, 0, 0);
draw.clearRect(0, 0, canvas.height, canvas.width);
// reverts canvas back to its saved state
draw.restore();
if(direction === 'down') {
draw.translate(0, 10);
draw.fillStyle = 'rgba(0,0,0,0.5)';
draw.fillRect('10', '10', '100', '100');
}
}
</script>
To get the same effect, you don't even need to do the save and restore. Context.globalCompositeOperation = "copy" will only draw the new object.
function canvasMove(direction) {
draw.globalCompositeOperation = "copy";
if(direction === 'down') {
draw.translate(0, 10);
draw.fillStyle = 'rgba(0,0,0,0.5)';
draw.fillRect('10', '10', '100', '100');
}
}