"break" in line when drawing with lineTo - javascript

This is my first time working with Canvas in Javascript. I'm trying to draw lines, pretty much. Here's what I'm getting in some of the lines (image below). Sometimes the line appears correct, but most of the time like this. As you can see there's a little break in the line where it appears darker. I'm trying to figure out the cause of this but no luck.
Here's my code. It's not very clean as it's still in development:
var canvas = document.getElementById("canvas");
var Point = function(x, y) {
this.startX = x;
this.startY = y;
};
var Interval = function(x, y) {
this.jumpX = x;
this.jumpY = y;
};
var points = [
[
new Point(340, 130), // point start
new Point(220, 130), // end first line
new Point(220, 70), // end second line
new Interval(-10, -10),
],
[
new Point(560, 80), // point start
new Point(660, 80), // end first line
new Point(660, 20), // end second line
new Interval(10, -10),
],
[
new Point(620, 230), // point start
new Point(770, 230), // end first line
new Point(770, 150), // end second line
new Interval(10, -10),
],
[
new Point(620, 230), // point start
new Point(770, 230), // end first line
new Point(770, 150), // end second line
new Interval(10, -10),
],
];
var ctx = canvas.getContext('2d');
ctx.strokeStyle = "#DFC270";
var func = function(points, text, j1, j2) {
var startX = points[0].startX,
startY = points[0].startY,
tempX = startX,
tempY = startY,
line1X = points[1].startX,
line1Y = points[1].startY,
line2X = points[2].startX,
line2Y = points[2].startY;
ctx.lineWidth = 1;
ctx.beginPath();
var inter = function() {
ctx.moveTo(startX, startY);
// console.log(tempY + j1);
if (tempY == line1Y && tempX == line1X) {
if (startX !== line2X) {
startX += j2;
}
if (startY !== line2Y) {
startY += j2;
}
if (startY == line2Y && startX == line2X) {
ctx.beginPath();
ctx.lineWidth = 1;
ctx.arc(startX, startY-j2, 5, 0, 2*Math.PI);
ctx.fillStyle = "#DFC270";
ctx.fill();
ctx.closePath();
ctx.stroke();
clearInterval(inter);
return;
}
} else {
if (startX !== line1X && tempX !== line1X) {
startX += j1;
tempX = startX;
}
if (startY !== line1Y && tempY !== line1Y) {
startY += j1;
tempY = startY;
}
}
window.requestAnimationFrame(inter);
ctx.lineTo(startX, startY);
ctx.stroke();
};
window.requestAnimationFrame(inter);
};
for (var i = 0; i < points.length; ++i) {
var interval = points[i][points[i].length-1];
func(points[i], 'test', interval.jumpX, interval.jumpY);
};
Here's a fiddle I was able to recreate it on too
https://jsfiddle.net/e9vLoken/

This is due to calling beginPath() twice, remove the extra method call under your if-statement:
var func = function(points, text, j1, j2) {
...
ctx.beginPath(); // <-- Already called here
...
var inter = function() {
if (startY == line2Y && startX == line2X) {
//ctx.beginPath(); <-- Remove this
ctx.lineWidth = 1;
...

The trouble is that you never completed the final path stroke when the point where the circle is to be drawn.
at line 68 call the stroke function like this
if (startY == line2Y && startX == line2X ) {
ctx.stroke();
ctx.beginPath();
ctx.lineWidth = 1;
ctx.arc(startX, startY-j2, 5, 0, 2*Math.PI);
ctx.fillStyle = "#DFC270";
ctx.fill();
ctx.closePath();
ctx.stroke();
clearInterval(inter);
return;

Related

How to drag points with it's connecting line in html5 canvas?

Following is my code. I have face issue when I drag a point. The problem is that whenever I drag point all the line connect to one point. I need to have draggable points in a html5 Canvas. But I have a supplementary constraint: the 2 points must be linked by a line. When I drag a point, the line must be dynamically drawn, and still linked to the 2 points.
let points= [];
let drag_point= -1;
let pointSize= 6;
let canvas = document.querySelector("#myCanvas");
let w = canvas.width;
let h = canvas.height;
var ctx = canvas.getContext("2d");
$("#myCanvas").mousedown(function (e) {
var pos = getPosition(e);
drag_point = getPointAt(pos.x, pos.y);
console.log("pos", drag_point);
if (drag_point == -1) {
// no point at that position, add new point
drawlines(pos.x, pos.y);
points.push(pos);
}
});
$("#myCanvas").mousemove(function (e) {
if (drag_point != -1) {
// if currently dragging a point...
var pos = getPosition(e);
//...update that.points position...
points[drag_point].x = pos.x;
points[drag_point].y = pos.y;
redraw(); // ... and redraw myCanvas
}
});
$("#myCanvas").mouseup(function (e) {
drag_point = -1;
});
function getPosition(event) {
var rect = canvas.getBoundingClientRect();
var x = event.clientX - rect.left;
var y = event.clientY - rect.top;
console.log(x, y);
return { x: x, y: y };
}
function getPointAt(x, y) {
for (var i = 0; i < points.length; i++) {
if (
Math.abs(points[i].x - x) < pointSize &&
Math.abs(points[i].y - y) < pointSize
)
// check if x,y is inside points bounding box. replace with pythagoras theorem if you like.
return i;
}
return -1; // no point at x,y
}
function redraw() {
ctx.clearRect(0, 0, canvas.width, canvas.height); // clear canvas
for (var i = 0; i < points.length; i++) {
// draw all points again
drawlines(points[i].x, points[i].y);
}
}
function drawlines(x, y) {
drawImages(x, y);
if (points.length > 0) {
var last = points[points.length - 1];
ctx.beginPath();
ctx.moveTo(last.x, last.y);
ctx.lineTo(x, y);
ctx.strokeStyle = "blue";
ctx.stroke();
}
}
function drawImages(x, y) {
var ctx = document.getElementById("myCanvas").getContext("2d");
ctx.beginPath();
ctx.arc(x, y, pointSize, 0, Math.PI * 2, true);
ctx.strokeStyle = "red";
ctx.stroke();
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<canvas
id="myCanvas"
width="1000"
height="1000"
style="border: 1px solid #d3d3d3"
></canvas>
.
See code below...
I refactored your drawlines and drawImages to be called independently not inside a common loop, in my code we draw all lines, then we draw all circles, that way we don't have to change colors back and forth all the time and prevents any overlaps of the lines over the circles, also another change is in the mousedown I call the redraw instead of drawlines.
Looking at your code educated guess the problem is in your:
var last = points[points.length - 1];
that seems very off, technically that is making the last always be the same
let points = [{x:10,y:10},{x:55,y:50},{x:100,y:10}];
let drag_point = -1;
let pointSize = 6;
let canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
canvas.onmousedown = function(e) {
var pos = getPosition(e);
drag_point = getPointAt(pos.x, pos.y);
if (drag_point == -1) {
points.push(pos);
redraw();
}
};
canvas.onmousemove = function(e) {
if (drag_point != -1) {
var pos = getPosition(e);
points[drag_point].x = pos.x;
points[drag_point].y = pos.y;
redraw();
}
};
canvas.onmouseup = function(e) {
drag_point = -1;
};
function getPosition(event) {
var rect = canvas.getBoundingClientRect();
var x = event.clientX - rect.left;
var y = event.clientY - rect.top;
return {x, y};
}
function getPointAt(x, y) {
for (var i = 0; i < points.length; i++) {
if (
Math.abs(points[i].x - x) < pointSize &&
Math.abs(points[i].y - y) < pointSize
)
return i;
}
return -1;
}
function redraw() {
if (points.length > 0) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawLines()
drawCircles()
}
}
function drawLines() {
ctx.beginPath();
ctx.moveTo(points[0].x, points[0].y);
ctx.strokeStyle = "blue";
ctx.lineWidth = 2;
points.forEach((p) => {
ctx.lineTo(p.x, p.y);
})
ctx.stroke();
}
function drawCircles() {
ctx.strokeStyle = "red";
ctx.lineWidth = 4;
points.forEach((p) => {
ctx.beginPath();
ctx.arc(p.x, p.y, pointSize, 0, Math.PI * 2, true);
ctx.stroke();
})
}
redraw()
<canvas id="myCanvas" width=160 height=160 style="border: 1px solid"></canvas>

draw arc between 2 lines & draw a curve 2 points

Framework: fabricjs
My first problem is to draw a angle betweens 2 Lines. My code is working but i'm not happy with the result.
My second problem is to draw a curve between 2 Points.
My Code for the first problem.
I have 3 Points:
A , B , C
2 Lines:
AB , BC
With this information i calculate distance points with distance 10.
let angle = this.calcAngle(A,B,C);
let distanceAB = this.calcCornerPoint(A, B, 10);
let distanceBC = this.calcCornerPoint(C, B, 10);
Calc Angle:
calcAngle(A, B, C, final, finalAddon = "°") {
var nx1 = A.x - B.x;
var ny1 = A.y - B.y;
var nx2 = C.x - B.x;
var ny2 = C.y - B.y;
this.lineAngle1 = Math.atan2(ny1, nx1);
this.lineAngle2 = Math.atan2(ny2, nx2);
if (nx1 * ny2 - ny1 * nx2 < 0) {
const t = lineAngle2;
this.lineAngle2 = this.lineAngle1;
this.lineAngle1 = t;
}
// if angle 1 is behind then move ahead
if (lineAngle1 < lineAngle2) {
this.lineAngle1 += Math.PI * 2;
}
}
Than draw a Path with:
this.drawAngleTrapez(distanceAB, distanceBC, B);
drawAngleTrapez(AB, BC, B) {
let points = [AB, BC, B];
let path = "";
if (this.trapezObjs[this.iterator]) {
this.canvas.remove(this.trapezObjs[this.iterator]);
}
path += "M " + Math.round(points[0].x) + " " + Math.round(points[0].y) + "";
for (let i = 1; i < points.length; i++) {
path += " L " + Math.round(points[i].x) + " " + Math.round(points[i].y) + "";
}
this.currentTrapez = this.trapezObjs[this.iterator] = new fabric.Path(path, {
selectable: false,
hasControls: false,
hasBorders: false,
hoverCursor: 'default',
fill: '#ccc',
strokeWidth: this.strokeWidth,
});
this.canvas.add(this.trapezObjs[this.iterator]);
}
And than i draw a Circle:
drawAnglePoint(B,d = 10) {
this.currentCorner = new fabric.Circle({
left: B.x,
top: B.y,
startAngle: this.lineAngle1,
endAngle: this.lineAngle2,
radius: 10,
fill: '#ccc',
selectable: false,
hasControls: false,
hasBorders: false,
hoverCursor: 'default',
});
this.canvas.add(this.currentCorner);
}
But the result ist not beautiful:
And the blue point is not on the end of the line, mabye here also a little fix.
this.startPoint.set({ left: C.x, top: C.y });
Second Problem solved: was an error in my calculation.
The problem is , its not a beautiful curve:
Rather than draw the central 'wedge' as 2 shapes - a triangle and a portion of a circle, you should instead draw it as the 2-sided figure that it is.
Circles are drawn by supplying the origin. Therefore, to draw the blue point on the end of the line, you should specify the same coordinates for the end-point as you do for the circle-centre.
The below code will re-create your first image with the exception of the text.
While I've drawn the swept-angle indicator transparently, ontop of the lines, you'd probably want to change the draw order, colour and opacity.
(I used 0x88/0xFF = 136/255 = 53.3%),
"use strict";
function newEl(tag){return document.createElement(tag)}
function byId(id){return document.getElementById(id)}
class vec2
{
constructor(x,y){this.x = x;this.y = y;}
get x(){ return this._x; }
set x(newVal){ this._x = newVal; }
get y(){ return this._y; }
set y(newVal){ this._y = newVal; }
get length(){return Math.hypot(this.x,this.y);}
set length(len){var invLen = len/this.length; this.timesEquals(invLen);}
add(other){return new vec2(this.x+other.x, this.y+other.y);}
sub(other){return new vec2(this.x-other.x, this.y-other.y);}
plusEquals(other){this.x+=other.x;this.y+=other.y;return this;}
minusEquals(other){this.x-=other.x;this.y-=other.y;return this;}
timesEquals(scalar){this.x*=scalar;this.y*=scalar;return this;}
divByEquals(scalar){this.x/=scalar;this.y/=scalar;return this;}
setTo(other){this.x=other.x;this.y=other.y;}
toString(){return `vec2 {x: ${this.x}, y: ${this.y}}` }
toStringN(n){ return `vec2 {x: ${this.x.toFixed(n)}, y: ${this.y.toFixed(n)}}` }
dotProd(other){return this.x*other.x + this.y*other.y;}
timesEquals(scalar){this.x *= scalar;this.y *= scalar;return this;}
normalize(){let len = this.length;this.x /= len;this.y /= len;return this;}
static clone(other){let result = new vec2(other.x, other.y);return result;}
clone(){return vec2.clone(this);}
};
window.addEventListener('load', onWindowLoaded, false);
function onWindowLoaded(evt)
{
var can = byId('output');
let A = new vec2(172,602), B = new vec2(734,602), C = new vec2(847,194);
myTest(can, [A,B,C]);
}
function circle(canvas, x, y, radius)
{
let ctx = canvas.getContext('2d');
ctx.moveTo(x,y);
ctx.beginPath();
ctx.ellipse(x,y, radius,radius, 0, 0, 2*Math.PI);
ctx.closePath();
ctx.fill();
}
function getAngle(origin, pt)
{
let delta = pt.sub(origin);
let angle = Math.atan2( delta.y, delta.x );
return angle;
}
function myTest(canvas, points)
{
let ctx = canvas.getContext('2d');
// background colour
//
ctx.fillStyle = '#ebedf0';
ctx.fillRect(0,0,canvas.width,canvas.height);
// white square grid
//
//60,33 = intersection of first
//115 = square-size
ctx.strokeStyle = '#FFFFFF';
ctx.lineWidth = 12;
for (let x=60; x<canvas.width; x+=112)
{
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, canvas.height);
ctx.stroke();
ctx.closePath();
}
for (let y=33; y<canvas.height; y+=112)
{
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(canvas.width, y);
ctx.stroke();
ctx.closePath();
}
// wedge indicating swept angle
let angle1 = getAngle(points[1], points[2]);
let angle2 = getAngle(points[1], points[0]);
ctx.beginPath();
ctx.moveTo(points[1].x,points[1].y);
ctx.arc(points[1].x,points[1].y, 70, angle1,angle2, true);
ctx.fillStyle = '#cccccc88';
ctx.fill();
console.log(angle1, angle2);
// lines
//
ctx.lineWidth = 9;
ctx.strokeStyle = '#c3874c';
ctx.beginPath();
ctx.moveTo(points[0].x, points[0].y);
ctx.lineTo(points[1].x, points[1].y);
ctx.lineTo(points[2].x, points[2].y);
ctx.stroke();
ctx.closePath();
// points
//
ctx.fillStyle = '#3b89c9';
ctx.beginPath();
points.forEach( function(pt){circle(canvas, pt.x,pt.y, 10);} );
ctx.closePath();
}
canvas
{
zoom: 67%;
}
<canvas id='output' width='996' height='730'></canvas>

Canvas collision

I am a new in javascript and trying to find out how to make a collision with ball and plank which will stop the game and alert player with something like "You lost". But I only want red balls to hit the plank and blue to pass on without touching. Here is code that I am working on. (I dont mind if you could help to do collision only with both balls)
var spawnRate = 100;
var spawnRateOfDescent = 2;
var lastSpawn = -10;
var objects = [];
var startTime = Date.now();
function spawnRandomObject() {
var t;
if (Math.random() < 0.50) {
t = "red";
} else {
t = "blue";
}
var object = {
type: t,
x: Math.random() * (canvas.width - 30) + 15,
y: 0
}
objects.push(object);
}
function animate() {
var time = Date.now();
if (time > (lastSpawn + spawnRate)) {
lastSpawn = time;
spawnRandomObject();
}
for (var i = 0; i < objects.length; i++) {
var object = objects[i];
object.y += spawnRateOfDescent;
ctx.beginPath();
ctx.arc(object.x, object.y, 8, 0, Math.PI * 2);
ctx.closePath();
ctx.fillStyle = object.type;
ctx.fill();
}
}
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
var paddleHeight = 10;
var paddleWidth = 60;
var paddleY = 480
var paddleX = (canvas.width-paddleWidth)/2;
var rightPressed = false;
var leftPressed = false;
document.addEventListener("keydown", keyDownHandler, false);
document.addEventListener("keyup", keyUpHandler, false);
function keyDownHandler(e) {
if(e.keyCode == 39) {
rightPressed = true;
}
else if(e.keyCode == 37) {
leftPressed = true;
}
}
function keyUpHandler(e) {
if(e.keyCode == 39) {
rightPressed = false;
}
else if(e.keyCode == 37) {
leftPressed = false;
}
}
function drawPaddle() {
ctx.beginPath();
ctx.rect(paddleX, paddleY, paddleWidth, paddleHeight);
ctx.fillStyle = "#0095DD";
ctx.fill();
ctx.closePath();
}
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawPaddle();
animate();
if(rightPressed && paddleX < canvas.width-paddleWidth) {
paddleX += 3;
}
else if(leftPressed && paddleX > 0) {
paddleX -= 3;
}
}
setInterval(draw, 10);
Thanks!
If you have an object like this:
let ball = { type: 'red', x: 10, y: 10, width: 10, height: 10 };
You might want to consider adding a method to this to check if it overlaps any other rectangle:
ball.overlapsBall = function( otherBall ){
return !(
otherBall.x + otherBall.width < this.x
&& otherBall.y + otherBall.height < this.y
&& otherBall.y > this.y + this.height
&& otherBall.x > this.x + this.height
);
}
You do this by checking if it does not overlap, which is only true if one box is entirely outside of the other (have a read through the if statement and try to visualise it, its actually rather simple)
In your draw function you could now add a loop to see if any overlap occurs:
var overlap = objects.filter(function( ball ) { return paddle.overlapsBall( ball ) });
You could even place an if statement to check it's type! (The filter will take you entire array of balls and check the overlaps, and remove anything from the array that does not return true. Now you can use overlaps.forEach(function( ball ){ /* ... */}); to do something with all the balls that overlapped your paddle.)
One last thing, if you are planning on doing this with many objects you might want to consider using a simple class like this for every paddle or ball you make:
class Object2D {
constructor(x = 0, y = 0;, width = 1, height = 1){
this.x = x;
this.y = x;
this.width = width;
this.height = height;
}
overlaps( otherObject ){
!( otherObject.x + otherObject.width < this.x && otherObject.y + otherObject.height < this.y && otherObject.y > this.y + this.height && otherObject.x > this.x + this.height );
}
}
This allows you to this simple expression to create a new object that automatically has a method to check for overlaps with similar objects:
var paddle = new Object2D(0,0,20,10);
var ball = new Object2D(5,5,10,10);
paddle.overlaps( ball ); // true!
On top of that, you are ensured that any Object2D contains the values you will need for your calculations. You can check if this object is if the right type using paddle instanceof Object2D (which is true).
Note Please note, as #Janje so continuously points out in the comments below, that we are doing a rectangle overlap here and it might create some 'false positives' for all the pieces of rectangle that aren't the circle. This is good enough for most cases, but you can find the math for other overlaps and collisions easily ith a quick google search.
Update: Simple Implementation
See below for a very simple example of how overlaps work in action:
var paddle = { x: 50, y: 50, width: 60, height: 20 };
var box = { x: 5, y: 20, width: 20, height: 20 };
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
document.body.appendChild( canvas );
canvas.width = 300;
canvas.height = 300;
function overlaps( a, b ){
return !!( a.x + a.width > b.x && a.x < b.x + b.width
&& a.y + a.height > b.y && a.y < b.y + b.height );
}
function animate(){
ctx.clearRect( 0, 0, canvas.width, canvas.height );
ctx.fillStyle = overlaps( paddle, box ) ? "red" : "black";
ctx.fillRect( paddle.x, paddle.y, paddle.width, paddle.height );
ctx.fillRect( box.x, box.y, box.width, box.height );
window.requestAnimationFrame( animate );
}
canvas.addEventListener('mousemove', function(event){
paddle.x = event.clientX - paddle.width / 2;
paddle.y = event.clientY - paddle.height / 2;
})
animate();

How to set a delay in each penalty shot in HTML5 JS Canvas football penalty game

I am trying to make a simple football penalty game using HTML5/JS Canvas. The aim is to make a game where you control the goal keeper and you have three attempts to save the ball.
I have most of the functionality done, I have a score system and collision detection.
Currently I have a delay on the first attempt. I am however finding difficulty in adding a delay before the ball is shot into the goal in the second and third attempt.
I am using the method requestAnimationFrame() to paint my shapes on my canvas. If there is still attempts available, the ball is positioned to its original location but then there is no delay and fires the ball immediately.
Any advice ? Thanks!
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Football</title>
<style>
* { padding: 0; margin: 0; }
canvas { background: #a5bd7b; display: block; margin: 0 auto; }
</style>
</head>
<body>
<canvas id="myCanvas" width="300" height="250"></canvas>
<script>
var canvas = document.getElementById('myCanvas');
var ctx = canvas.getContext("2d");
//Sets the original position of the ball
var x = canvas.width/2;
var y = 50;
// Defines values that will be added to the position of x and y values
// List of possible values for the x position
var x_options = [3.5, 3, 2.5, 2, 1.5, 1, 0.5, 0, -0.5, -1, -1.5, -2, -2.5, -3, -3.5];
// Gets a random value from the x_options array
var dx = x_options[Math.floor(Math.random() * x_options.length)];
var dy = 5;
var ballRadius = 10;
// Defines the height and width of the goal
var goal_height = 40;
var goal_width = 200
// Defines the height, width and position of goalie
var goalieHeight = 20;
var goalieWidth = 40;
var goalieX = (canvas.width-goalieWidth)/2;
var goalieY = (canvas.height - goal_height) - 30;
// Set to false by default
var rightPressed = false;
var leftPressed = false;
var goalkeeper_blocked = 0;
var goalkeeper_missed = 0;
var attempts_left = 3;
var attempt1 = true;
var attempt2 = false;
var attempt3 = false;
var footBall = {
shapes : {
ball: function (){
ctx.beginPath();
ctx.arc(x, y, ballRadius, 0, Math.PI*2, false);
ctx.fillStyle = "red";
ctx.fill();
ctx.closePath();
},
goal : function (){
ctx.beginPath();
ctx.rect((canvas.width - goal_width) / 2 , canvas.height - goal_height, goal_width, goal_height);
ctx.strokeStyle = "#000000";
ctx.stroke();
ctx.closePath();
},
goalie : function(){
ctx.beginPath();
ctx.rect(goalieX, goalieY, goalieWidth, goalieHeight);
ctx.fillStyle = "#666666";
ctx.fill();
ctx.closePath();
},
score : function(){
ctx.font = "16px Arial";
ctx.fillStyle = "#ffffff";
ctx.fillText("Score: "+goalkeeper_blocked, 8, 20);
},
missed : function(){
ctx.font = "16px Arial";
ctx.fillStyle = "#ffffff";
ctx.fillText("Missed: "+goalkeeper_missed, 8, 40);
},
attempts : function(){
ctx.font = "16px Arial";
ctx.fillStyle = "#ffffff";
ctx.fillText("Attempts left: "+attempts_left, canvas.width-110, 20);
}
},
controls : {
keyDownHandler : function (e){
if(e.keyCode == 39) {
rightPressed = true;
}
else if(e.keyCode == 37) {
leftPressed = true;
}
},
keyUpHandler : function(e){
if(e.keyCode == 39) {
rightPressed = false;
}
else if(e.keyCode == 37) {
leftPressed = false;
}
}
},
calculateScore : function(){
if(goalkeeper_missed > goalkeeper_blocked){
alert("GAME OVER! YOU HAVE LOST!");
document.location.reload();
} else {
alert("GAME OVER! YOU HAVE WON!");
document.location.reload();
}
},
animateBall : function (){
// Sets a delay of 3 second before it shoots
setTimeout(function(){
x += dx;
y += dy;
}, 3000);
},
resetShapePositions : function(){
//Sets the original position of the ball
x = canvas.width/2;
y = 50;
// Sets a new shooting path
dx = x_options[Math.floor(Math.random() * x_options.length)];
dy = 5;
// Resets the goalie to the middle
goalieX = (canvas.width-goalieWidth)/2;
},
draw : function(){
// This ensures that the ball doesn't leave a trail
// Clears the canvas of this shape each frame
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draws shapes on the canvas
footBall.shapes.ball();
footBall.shapes.goal();
footBall.shapes.goalie();
footBall.shapes.score();
footBall.shapes.missed();
footBall.shapes.attempts();
// adds values to the balls x and y position every frame
footBall.animateBall();
// Ball hits the goal
if(y + dy > canvas.height - goal_height) {
attempts_left--;
goalkeeper_missed++;
if (!attempts_left){
footBall.calculateScore();
}
else {
footBall.resetShapePositions();
}
} // Ball saved by goalie
else if (x > goalieX && x < goalieX + goalieWidth && y + dy > goalieY - ballRadius){
attempts_left--;
goalkeeper_blocked++;
if (!attempts_left){
footBall.calculateScore();
}
else {
footBall.resetShapePositions();
}
}
// makes paddle move left and right and only within the canvas
if(rightPressed && goalieX < canvas.width-goalieWidth) {
goalieX += 7;
}
else if(leftPressed && goalieX > 0) {
goalieX -= 7;
}
requestAnimationFrame(footBall.draw);
}
}
footBall.draw();
// Defines what functions are fired when keydown or keyup event triggers
document.addEventListener("keydown", footBall.controls.keyDownHandler, false);
document.addEventListener("keyup", footBall.controls.keyUpHandler, false);
</script>
</body>
</html>
Add some properties to football that control if/when a shot is occuring:
// is a shot in progress?
isShooting:false,
// the time when next shot will start
nextShotTime:0,
// delay between shots
delayUntilNextShot:3000,
Then in the animation loop, use these properties to appropriately delay the next shot:
If isShooting, process the shot,
If not isShooting, see if the required delay has elapsed between shots. If yes, set isShooting=true,
When the goalie blocks or misses the shot, set isShooting=false and set nextShotTime=currentTime+delayUntilNextShot,
Example code and a Demo:
var canvas = document.getElementById('myCanvas');
var ctx = canvas.getContext("2d");
//Sets the original position of the ball
var x = canvas.width/2;
var y = 50;
// Defines values that will be added to the position of x and y values
// List of possible values for the x position
var x_options = [3.5, 3, 2.5, 2, 1.5, 1, 0.5, 0, -0.5, -1, -1.5, -2, -2.5, -3, -3.5];
// Gets a random value from the x_options array
var dx = x_options[Math.floor(Math.random() * x_options.length)];
var dy = 5;
var ballRadius = 10;
// Defines the height and width of the goal
var goal_height = 40;
var goal_width = 200
// Defines the height, width and position of goalie
var goalieHeight = 20;
var goalieWidth = 40;
var goalieX = (canvas.width-goalieWidth)/2;
var goalieY = (canvas.height - goal_height) - 30;
// Set to false by default
var rightPressed = false;
var leftPressed = false;
var goalkeeper_blocked = 0;
var goalkeeper_missed = 0;
var attempts_left = 3;
var attempt1 = true;
var attempt2 = false;
var attempt3 = false;
var footBall = {
// is a shot in progress
isShooting:false,
// time when next shot will run
nextShotTime:0,
// delay until next shot will run
delayUntilNextShot:3000,
shapes : {
ball: function (){
ctx.beginPath();
ctx.arc(x, y, ballRadius, 0, Math.PI*2, false);
ctx.fillStyle = "red";
ctx.fill();
ctx.closePath();
},
goal : function (){
ctx.beginPath();
ctx.rect((canvas.width - goal_width) / 2 , canvas.height - goal_height, goal_width, goal_height);
ctx.strokeStyle = "#000000";
ctx.stroke();
ctx.closePath();
},
goalie : function(){
ctx.beginPath();
ctx.rect(goalieX, goalieY, goalieWidth, goalieHeight);
ctx.fillStyle = "#666666";
ctx.fill();
ctx.closePath();
},
score : function(){
ctx.font = "16px Arial";
ctx.fillStyle = "#ffffff";
ctx.fillText("Score: "+goalkeeper_blocked, 8, 20);
},
missed : function(){
ctx.font = "16px Arial";
ctx.fillStyle = "#ffffff";
ctx.fillText("Missed: "+goalkeeper_missed, 8, 40);
},
attempts : function(){
ctx.font = "16px Arial";
ctx.fillStyle = "#ffffff";
ctx.fillText("Attempts left: "+attempts_left, canvas.width-110, 20);
}
},
controls : {
keyDownHandler : function (e){
if(e.keyCode == 39) {
rightPressed = true;
}
else if(e.keyCode == 37) {
leftPressed = true;
}
},
keyUpHandler : function(e){
if(e.keyCode == 39) {
rightPressed = false;
}
else if(e.keyCode == 37) {
leftPressed = false;
}
}
},
calculateScore : function(){
if(goalkeeper_missed > goalkeeper_blocked){
alert("GAME OVER! YOU HAVE LOST!");
document.location.reload();
} else {
alert("GAME OVER! YOU HAVE WON!");
document.location.reload();
}
},
resetShapePositions : function(){
//Sets the original position of the ball
x = canvas.width/2;
y = 50;
// Sets a new shooting path
dx = x_options[Math.floor(Math.random() * x_options.length)];
dy = 5;
// Resets the goalie to the middle
goalieX = (canvas.width-goalieWidth)/2;
},
drawField: function(){
// This ensures that the ball doesn't leave a trail
// Clears the canvas of this shape each frame
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draws shapes on the canvas
footBall.shapes.ball();
footBall.shapes.goal();
footBall.shapes.goalie();
footBall.shapes.score();
footBall.shapes.missed();
footBall.shapes.attempts();
},
draw : function(currentTime){
// makes paddle move left and right and only within the canvas
if(rightPressed && goalieX < canvas.width-goalieWidth) {
goalieX += 7;
}
else if(leftPressed && goalieX > 0) {
goalieX -= 7;
}
// draw the scene
footBall.drawField();
// delay until next shot time is due
if(!footBall.isShooting){
// time has elapsed, let's shoot again
if(currentTime>footBall.nextShotTime){
footBall.isShooting=true;
}else{
// time has not elapsed, just request another loop
requestAnimationFrame(footBall.draw);
return;
}
}
// adds values to the balls x and y position every frame
x += dx;
y += dy;
// Ball hits the goal
if(y + dy > canvas.height - goal_height) {
// end the shot
footBall.isShooting=false;
// delay the next shot
footBall.nextShotTime=currentTime+footBall.delayUntilNextShot;
attempts_left--;
goalkeeper_missed++;
if (!attempts_left){
footBall.calculateScore();
}
else {
footBall.resetShapePositions();
}
} // Ball saved by goalie
else if (x > goalieX && x < goalieX + goalieWidth && y + dy > goalieY - ballRadius){
// end the shot
footBall.isShooting=false;
// delay the next shot
footBall.nextShotTime=currentTime+footBall.delayUntilNextShot;
attempts_left--;
goalkeeper_blocked++;
if (!attempts_left){
footBall.calculateScore();
}
else {
footBall.resetShapePositions();
}
}
requestAnimationFrame(footBall.draw);
}
}
footBall.drawField();
footBall.nextShotTime=footBall.delayUntilNextShot;
requestAnimationFrame(footBall.draw);
// Defines what functions are fired when keydown or keyup event triggers
document.addEventListener("keydown", footBall.controls.keyDownHandler, false);
document.addEventListener("keyup", footBall.controls.keyUpHandler, false);
* { padding: 0; margin: 0; }
canvas { background: #a5bd7b; display: block; margin: 0 auto; }
<canvas id="myCanvas" width="300" height="250"></canvas>

Drawing on canvas, offset is 100px off

I know it´s not well seen, that you post a link to a code, but I followed this tutorial of making a html drawing canvas app and implemented the source to my wordpress site and everything works fine, except, that all the mouse-click-events seems offset to the left by 100px.
If I open the example html, everything works fine, so I think it has something to do with the parent container css´s or something.
Since I am not very well in js, I thought maybe you can help me figure it out, since I try since a few days now.
This is the tutorial and source code and this is the code of the js file
var drawingApp = (function () {
"use strict";
var canvas,
context,
canvasWidth = 490,
canvasHeight = 220,
colorPurple = "#cb3594",
colorGreen = "#659b41",
colorYellow = "#ffcf33",
colorBrown = "#986928",
outlineImage = new Image(),
crayonImage = new Image(),
markerImage = new Image(),
eraserImage = new Image(),
crayonBackgroundImage = new Image(),
markerBackgroundImage = new Image(),
eraserBackgroundImage = new Image(),
crayonTextureImage = new Image(),
clickX = [],
clickY = [],
clickColor = [],
clickTool = [],
clickSize = [],
clickDrag = [],
paint = false,
curColor = colorPurple,
curTool = "crayon",
curSize = "normal",
mediumStartX = 18,
mediumStartY = 19,
mediumImageWidth = 93,
mediumImageHeight = 46,
drawingAreaX = 111,
drawingAreaY = 11,
drawingAreaWidth = 267,
drawingAreaHeight = 200,
toolHotspotStartY = 23,
toolHotspotHeight = 38,
sizeHotspotStartY = 157,
sizeHotspotHeight = 36,
totalLoadResources = 8,
curLoadResNum = 0,
sizeHotspotWidthObject = {
huge: 39,
large: 25,
normal: 18,
small: 16
},
// Clears the canvas.
clearCanvas = function () {
context.clearRect(0, 0, canvasWidth, canvasHeight);
},
// Redraws the canvas.
redraw = function () {
var locX,
locY,
radius,
i,
selected,
drawCrayon = function (x, y, color, selected) {
context.beginPath();
context.moveTo(x + 41, y + 11);
context.lineTo(x + 41, y + 35);
context.lineTo(x + 29, y + 35);
context.lineTo(x + 29, y + 33);
context.lineTo(x + 11, y + 27);
context.lineTo(x + 11, y + 19);
context.lineTo(x + 29, y + 13);
context.lineTo(x + 29, y + 11);
context.lineTo(x + 41, y + 11);
context.closePath();
context.fillStyle = color;
context.fill();
if (selected) {
context.drawImage(crayonImage, x, y, mediumImageWidth, mediumImageHeight);
} else {
context.drawImage(crayonImage, 0, 0, 59, mediumImageHeight, x, y, 59, mediumImageHeight);
}
},
drawMarker = function (x, y, color, selected) {
context.beginPath();
context.moveTo(x + 10, y + 24);
context.lineTo(x + 10, y + 24);
context.lineTo(x + 22, y + 16);
context.lineTo(x + 22, y + 31);
context.closePath();
context.fillStyle = color;
context.fill();
if (selected) {
context.drawImage(markerImage, x, y, mediumImageWidth, mediumImageHeight);
} else {
context.drawImage(markerImage, 0, 0, 59, mediumImageHeight, x, y, 59, mediumImageHeight);
}
};
// Make sure required resources are loaded before redrawing
if (curLoadResNum < totalLoadResources) {
return;
}
clearCanvas();
if (curTool === "crayon") {
// Draw the crayon tool background
context.drawImage(crayonBackgroundImage, 0, 0, canvasWidth, canvasHeight);
// Draw purple crayon
selected = (curColor === colorPurple);
locX = selected ? 18 : 52;
locY = 19;
drawCrayon(locX, locY, colorPurple, selected);
// Draw green crayon
selected = (curColor === colorGreen);
locX = selected ? 18 : 52;
locY += 46;
drawCrayon(locX, locY, colorGreen, selected);
// Draw yellow crayon
selected = (curColor === colorYellow);
locX = selected ? 18 : 52;
locY += 46;
drawCrayon(locX, locY, colorYellow, selected);
// Draw brown crayon
selected = (curColor === colorBrown);
locX = selected ? 18 : 52;
locY += 46;
drawCrayon(locX, locY, colorBrown, selected);
} else if (curTool === "marker") {
// Draw the marker tool background
context.drawImage(markerBackgroundImage, 0, 0, canvasWidth, canvasHeight);
// Draw purple marker
selected = (curColor === colorPurple);
locX = selected ? 18 : 52;
locY = 19;
drawMarker(locX, locY, colorPurple, selected);
// Draw green marker
selected = (curColor === colorGreen);
locX = selected ? 18 : 52;
locY += 46;
drawMarker(locX, locY, colorGreen, selected);
// Draw yellow marker
selected = (curColor === colorYellow);
locX = selected ? 18 : 52;
locY += 46;
drawMarker(locX, locY, colorYellow, selected);
// Draw brown marker
selected = (curColor === colorBrown);
locX = selected ? 18 : 52;
locY += 46;
drawMarker(locX, locY, colorBrown, selected);
} else if (curTool === "eraser") {
context.drawImage(eraserBackgroundImage, 0, 0, canvasWidth, canvasHeight);
context.drawImage(eraserImage, 18, 19, mediumImageWidth, mediumImageHeight);
}
// Draw line on ruler to indicate size
switch (curSize) {
case "small":
locX = 467;
break;
case "normal":
locX = 450;
break;
case "large":
locX = 428;
break;
case "huge":
locX = 399;
break;
default:
break;
}
locY = 189;
context.beginPath();
context.rect(locX, locY, 2, 12);
context.closePath();
context.fillStyle = '#333333';
context.fill();
// Keep the drawing in the drawing area
context.save();
context.beginPath();
context.rect(drawingAreaX, drawingAreaY, drawingAreaWidth, drawingAreaHeight);
context.clip();
// For each point drawn
for (i = 0; i < clickX.length; i += 1) {
// Set the drawing radius
switch (clickSize[i]) {
case "small":
radius = 2;
break;
case "normal":
radius = 5;
break;
case "large":
radius = 10;
break;
case "huge":
radius = 20;
break;
default:
break;
}
// Set the drawing path
context.beginPath();
// If dragging then draw a line between the two points
if (clickDrag[i] && i) {
context.moveTo(clickX[i - 1], clickY[i - 1]);
} else {
// The x position is moved over one pixel so a circle even if not dragging
context.moveTo(clickX[i] - 1, clickY[i]);
}
context.lineTo(clickX[i], clickY[i]);
// Set the drawing color
if (clickTool[i] === "eraser") {
//context.globalCompositeOperation = "destination-out"; // To erase instead of draw over with white
context.strokeStyle = 'white';
} else {
//context.globalCompositeOperation = "source-over"; // To erase instead of draw over with white
context.strokeStyle = clickColor[i];
}
context.lineCap = "round";
context.lineJoin = "round";
context.lineWidth = radius;
context.stroke();
}
context.closePath();
//context.globalCompositeOperation = "source-over";// To erase instead of draw over with white
context.restore();
// Overlay a crayon texture (if the current tool is crayon)
if (curTool === "crayon") {
context.globalAlpha = 0.4; // No IE support
context.drawImage(crayonTextureImage, 0, 0, canvasWidth, canvasHeight);
}
context.globalAlpha = 1; // No IE support
// Draw the outline image
context.drawImage(outlineImage, drawingAreaX, drawingAreaY, drawingAreaWidth, drawingAreaHeight);
},
// Adds a point to the drawing array.
// #param x
// #param y
// #param dragging
addClick = function (x, y, dragging) {
clickX.push(x);
clickY.push(y);
clickTool.push(curTool);
clickColor.push(curColor);
clickSize.push(curSize);
clickDrag.push(dragging);
},
// Add mouse and touch event listeners to the canvas
createUserEvents = function () {
var press = function (e) {
// Mouse down location
var sizeHotspotStartX,
mouseX = e.pageX - this.offsetLeft,
mouseY = e.pageY - this.offsetTop;
if (mouseX < drawingAreaX) { // Left of the drawing area
if (mouseX > mediumStartX) {
if (mouseY > mediumStartY && mouseY < mediumStartY + mediumImageHeight) {
curColor = colorPurple;
} else if (mouseY > mediumStartY + mediumImageHeight && mouseY < mediumStartY + mediumImageHeight * 2) {
curColor = colorGreen;
} else if (mouseY > mediumStartY + mediumImageHeight * 2 && mouseY < mediumStartY + mediumImageHeight * 3) {
curColor = colorYellow;
} else if (mouseY > mediumStartY + mediumImageHeight * 3 && mouseY < mediumStartY + mediumImageHeight * 4) {
curColor = colorBrown;
}
}
} else if (mouseX > drawingAreaX + drawingAreaWidth) { // Right of the drawing area
if (mouseY > toolHotspotStartY) {
if (mouseY > sizeHotspotStartY) {
sizeHotspotStartX = drawingAreaX + drawingAreaWidth;
if (mouseY < sizeHotspotStartY + sizeHotspotHeight && mouseX > sizeHotspotStartX) {
if (mouseX < sizeHotspotStartX + sizeHotspotWidthObject.huge) {
curSize = "huge";
} else if (mouseX < sizeHotspotStartX + sizeHotspotWidthObject.large + sizeHotspotWidthObject.huge) {
curSize = "large";
} else if (mouseX < sizeHotspotStartX + sizeHotspotWidthObject.normal + sizeHotspotWidthObject.large + sizeHotspotWidthObject.huge) {
curSize = "normal";
} else if (mouseX < sizeHotspotStartX + sizeHotspotWidthObject.small + sizeHotspotWidthObject.normal + sizeHotspotWidthObject.large + sizeHotspotWidthObject.huge) {
curSize = "small";
}
}
} else {
if (mouseY < toolHotspotStartY + toolHotspotHeight) {
curTool = "crayon";
} else if (mouseY < toolHotspotStartY + toolHotspotHeight * 2) {
curTool = "marker";
} else if (mouseY < toolHotspotStartY + toolHotspotHeight * 3) {
curTool = "eraser";
}
}
}
}
paint = true;
addClick(mouseX, mouseY, false);
redraw();
},
drag = function (e) {
if (paint) {
addClick(e.pageX - this.offsetLeft, e.pageY - this.offsetTop, true);
redraw();
}
// Prevent the whole page from dragging if on mobile
e.preventDefault();
},
release = function () {
paint = false;
redraw();
},
cancel = function () {
paint = false;
};
// Add mouse event listeners to canvas element
canvas.addEventListener("mousedown", press, false);
canvas.addEventListener("mousemove", drag, false);
canvas.addEventListener("mouseup", release);
canvas.addEventListener("mouseout", cancel, false);
// Add touch event listeners to canvas element
canvas.addEventListener("touchstart", press, false);
canvas.addEventListener("touchmove", drag, false);
canvas.addEventListener("touchend", release, false);
canvas.addEventListener("touchcancel", cancel, false);
},
// Calls the redraw function after all neccessary resources are loaded.
resourceLoaded = function () {
curLoadResNum += 1;
if (curLoadResNum === totalLoadResources) {
redraw();
createUserEvents();
}
},
// Creates a canvas element, loads images, adds events, and draws the canvas for the first time.
init = function () {
// Create the canvas (Neccessary for IE because it doesn't know what a canvas element is)
canvas = document.createElement('canvas');
canvas.setAttribute('width', canvasWidth);
canvas.setAttribute('height', canvasHeight);
canvas.setAttribute('id', 'canvas');
document.getElementById('canvasDiv').appendChild(canvas);
if (typeof G_vmlCanvasManager !== "undefined") {
canvas = G_vmlCanvasManager.initElement(canvas);
}
context = canvas.getContext("2d"); // Grab the 2d canvas context
// Note: The above code is a workaround for IE 8 and lower. Otherwise we could have used:
// context = document.getElementById('canvas').getContext("2d");
// Load images
crayonImage.onload = resourceLoaded;
crayonImage.src = cvtemplateDir+ "/images/crayon-outline.png";
markerImage.onload = resourceLoaded;
markerImage.src = cvtemplateDir+ "/images/marker-outline.png";
eraserImage.onload = resourceLoaded;
eraserImage.src = cvtemplateDir+ "/images/eraser-outline.png";
crayonBackgroundImage.onload = resourceLoaded;
crayonBackgroundImage.src = cvtemplateDir+ "/images/crayon-background.png";
markerBackgroundImage.onload = resourceLoaded;
markerBackgroundImage.src = cvtemplateDir+ "/images/marker-background.png";
eraserBackgroundImage.onload = resourceLoaded;
eraserBackgroundImage.src = cvtemplateDir+ "/images/eraser-background.png";
crayonTextureImage.onload = resourceLoaded;
crayonTextureImage.src = cvtemplateDir+ "/images/crayon-texture.png";
outlineImage.onload = resourceLoaded;
outlineImage.src = cvtemplateDir+ "/images/watermelon-duck-outline.png";
};
return {
init: init
};
}());
It´s being implemented on my website here
This is the code to initialize it, which is being called on the hook:
add_action( 'comment_form_before', 'canvas_comments');
function canvas_comments() {
?>
<div id="canvasDiv" style="width:490px;height:220px;"></div>
<script type="text/javascript">
drawingApp.init();
</script>
<?php
}
EDIT:
Just realized in Firefox it´s offsetting even more, I used chrome
Did you try to get offsetTop and offsetLeft explicitly from the canvas?
You are now accessing offsetTop by this in a listener-function. Are you sure that this points to your canvas and not to something else?
I finally fixed with treeno´s suggestion to use canvas.getBoundingClientRect();
I inserted it at the end of the createUserEvents-function like this:
cancel = function () {
......
var rect = canvas.getBoundingClientRect();
// Add mouse event listeners to canvas element
canvas.addEventListener("mousedown", press, false);
.....
},
And finally i replaced every this.OffsetLeft and this.OffsetTop with rect.left and rect.top
Works like a charm!

Categories

Resources