I am trying to draw a group of shapes using canvas.
I have referenced below SO threads:
Draw a parallel line
How to draw parallel line using three.js?
but not able to figure out how to calculate points for the rectangles parallel in as we stretch the line.
Any reference for stretching shapes with canvas is appreciated.
//Canvas
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
//Variables
var canvasx = $(canvas).offset().left;
var canvasy = $(canvas).offset().top;
var last_mousex = last_mousey = 0;
var mousex = mousey = 0;
var mousedown = false;
// grid parameters
var gridSpacing = 20; // pixels
var gridWidth = 1;
//var gridColor = "#f1f1f1";
var gridColor = "lightgray";
/** */
var originX = 0;
/** */
var originY = 0;
drawGrid();
//Mousedown
$(canvas).on('mousedown', function(e) {
last_mousex = parseInt(e.clientX-canvasx);
last_mousey = parseInt(e.clientY-canvasy);
mousedown = true;
});
//Mouseup
$(canvas).on('mouseup', function(e) {
mousedown = false;
});
//Mousemove
$(canvas).on('mousemove', function(e) {
mousex = parseInt(e.clientX-canvasx);
mousey = parseInt(e.clientY-canvasy);
if(mousedown) {
ctx.clearRect(0,0,canvas.width,canvas.height); //clear canvas
drawGrid();
ctx.setLineDash([5, 15]);
ctx.beginPath();
ctx.moveTo(last_mousex,last_mousey);
ctx.lineTo(mousex,mousey);
//ctx.lineTo(mousex,mousey);
ctx.strokeStyle = 'blue';
ctx.lineDashOffset = 2;
ctx.lineWidth = 5;
ctx.lineJoin = ctx.lineCap = 'round';
ctx.stroke();
startx = last_mousex;
starty = last_mousey;
drawPolygon([last_mousex, mousex, mousex, last_mousex, last_mousex],
[last_mousey-10, mousey-10, mousey-60, last_mousey-60],true, 'gray', false, 'black', 2);
drawPolygon([last_mousex, mousex, mousex, last_mousex, last_mousex],
[last_mousey+10, mousey+10, mousey+60, last_mousey+60],true, 'gray', false, 'black', 2);
}
//Output
$('#output').html('current: '+mousex+', '+mousey+'<br/>last: '+last_mousex+', '+last_mousey+'<br/>mousedown: '+mousedown);
});
/** */
function drawLine(startX, startY, endX, endY, width, color) {
// width is an integer
// color is a hex string, i.e. #ff0000
ctx.beginPath();
ctx.moveTo(startX, startY);
ctx.lineTo(endX, endY);
ctx.lineWidth = width;
ctx.strokeStyle = color;
ctx.stroke();
}
function drawPolygon(xArr, yArr, fill, fillColor, stroke, strokeColor, strokeWidth) {
// fillColor is a hex string, i.e. #ff0000
fill = fill || false;
stroke = stroke || false;
ctx.beginPath();
ctx.moveTo(xArr[0], yArr[0]);
for (var i = 1; i < xArr.length; i++) {
ctx.lineTo(xArr[i], yArr[i]);
}
ctx.closePath();
if (fill) {
ctx.fillStyle = fillColor;
ctx.fill();
}
if (stroke) {
ctx.lineWidth = strokeWidth;
ctx.strokeStyle = strokeColor;
ctx.stroke();
}
//console.log(xArr);
//console.log(yArr);
}
/** returns n where -gridSize/2 < n <= gridSize/2 */
function calculateGridOffset(n) {
if (n >= 0) {
return (n + gridSpacing / 2.0) % gridSpacing - gridSpacing / 2.0;
} else {
return (n - gridSpacing / 2.0) % gridSpacing + gridSpacing / 2.0;
}
}
/** */
function drawGrid() {
var offsetX = calculateGridOffset(-originX);
var offsetY = calculateGridOffset(-originY);
var width = canvas.width;
var height = canvas.height;
for (var x = 0; x <= (width / gridSpacing); x++) {
drawLine(gridSpacing * x + offsetX, 0, gridSpacing * x + offsetX, height, gridWidth, gridColor);
}
for (var y = 0; y <= (height / gridSpacing); y++) {
drawLine(0, gridSpacing * y + offsetY, width, gridSpacing * y + offsetY, gridWidth, gridColor);
}
}
canvas {
cursor: crosshair;
border: 1px solid #000000;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<canvas id="canvas" width="800" height="500"></canvas>
<div id="output"></div>
I'm guessing this is what you wanted.
Instead of trying to manually draw your line rotated, instead, move the origin of the canvas to the start of the line,
// save the canvas state
ctx.save();
// move origin to start of line
ctx.translate(last_mousex, last_mousey);
then rotate the origin so it points toward the end of the line in the positive X direction
// compute direction of line from start to end
const dx = mousex - last_mousex;
const dy = mousey - last_mousey;
const angle = Math.atan2(dy, dx);
// rotate to point to end of line
ctx.rotate(angle);
then compute the length of the line from the start to the end
// compute length of line
const length = Math.sqrt(dx * dx + dy * dy);
and just draw an arrow in the positive x direction of that length
ctx.setLineDash([5, 15]);
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(length, 0);
ctx.strokeStyle = 'blue';
ctx.lineDashOffset = 2;
ctx.lineWidth = 5;
ctx.lineJoin = ctx.lineCap = 'round';
ctx.stroke();
drawPolygon([0, length, length, 0, 0],
[-10, -10, -60, -60],true, 'gray', false, 'black', 2);
drawPolygon([0, length, length, 0, 0],
[+10, +10, +60, +60],true, 'gray', false, 'black', 2);
// restore the canvas state
ctx.restore();
while we're at it your code for calculating the mouse position didn't work if the page is scrolled. This will get the mouse position relative to the pixels in the canvas.
const rect = canvas.getBoundingClientRect();
mousex = (e.clientX - rect.left) * canvas.width / canvas.clientWidth;
mousey = (e.clientY - rect.top ) * canvas.height / canvas.clientHeight;
//Canvas
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
//Variables
var last_mousex = last_mousey = 0;
var mousex = mousey = 0;
var mousedown = false;
// grid parameters
var gridSpacing = 20; // pixels
var gridWidth = 1;
//var gridColor = "#f1f1f1";
var gridColor = "lightgray";
/** */
var originX = 0;
/** */
var originY = 0;
drawGrid();
//Mousedown
$(canvas).on('mousedown', function(e) {
const rect = canvas.getBoundingClientRect();
last_mousex = (e.clientX - rect.left) * canvas.width / canvas.clientWidth;
last_mousey = (e.clientY - rect.top ) * canvas.height / canvas.clientHeight;
mousedown = true;
});
//Mouseup
$(canvas).on('mouseup', function(e) {
mousedown = false;
});
//Mousemove
$(canvas).on('mousemove', function(e) {
const rect = canvas.getBoundingClientRect();
mousex = (e.clientX - rect.left) * canvas.width / canvas.clientWidth;
mousey = (e.clientY - rect.top ) * canvas.height / canvas.clientHeight;
if(mousedown) {
ctx.clearRect(0,0,canvas.width,canvas.height); //clear canvas
drawGrid();
// save the canvas state
ctx.save();
// move origin to start of line
ctx.translate(last_mousex, last_mousey);
// compute direction of line from start to end
const dx = mousex - last_mousex;
const dy = mousey - last_mousey;
const angle = Math.atan2(dy, dx);
// rotate to point to end of line
ctx.rotate(angle);
// compute length of line
const length = Math.sqrt(dx * dx + dy * dy);
ctx.setLineDash([5, 15]);
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(length, 0);
ctx.strokeStyle = 'blue';
ctx.lineDashOffset = 2;
ctx.lineWidth = 5;
ctx.lineJoin = ctx.lineCap = 'round';
ctx.stroke();
drawPolygon([0, length, length, 0, 0],
[-10, -10, -60, -60],true, 'gray', false, 'black', 2);
drawPolygon([0, length, length, 0, 0],
[+10, +10, +60, +60],true, 'gray', false, 'black', 2);
// restore the canvas state
ctx.restore();
}
//Output
$('#output').html('current: '+mousex+', '+mousey+'<br/>last: '+last_mousex+', '+last_mousey+'<br/>mousedown: '+mousedown);
});
/** */
function drawLine(startX, startY, endX, endY, width, color) {
// width is an integer
// color is a hex string, i.e. #ff0000
ctx.beginPath();
ctx.moveTo(startX, startY);
ctx.lineTo(endX, endY);
ctx.lineWidth = width;
ctx.strokeStyle = color;
ctx.stroke();
}
function drawPolygon(xArr, yArr, fill, fillColor, stroke, strokeColor, strokeWidth) {
// fillColor is a hex string, i.e. #ff0000
fill = fill || false;
stroke = stroke || false;
ctx.beginPath();
ctx.moveTo(xArr[0], yArr[0]);
for (var i = 1; i < xArr.length; i++) {
ctx.lineTo(xArr[i], yArr[i]);
}
ctx.closePath();
if (fill) {
ctx.fillStyle = fillColor;
ctx.fill();
}
if (stroke) {
ctx.lineWidth = strokeWidth;
ctx.strokeStyle = strokeColor;
ctx.stroke();
}
//console.log(xArr);
//console.log(yArr);
}
/** returns n where -gridSize/2 < n <= gridSize/2 */
function calculateGridOffset(n) {
if (n >= 0) {
return (n + gridSpacing / 2.0) % gridSpacing - gridSpacing / 2.0;
} else {
return (n - gridSpacing / 2.0) % gridSpacing + gridSpacing / 2.0;
}
}
/** */
function drawGrid() {
var offsetX = calculateGridOffset(-originX);
var offsetY = calculateGridOffset(-originY);
var width = canvas.width;
var height = canvas.height;
for (var x = 0; x <= (width / gridSpacing); x++) {
drawLine(gridSpacing * x + offsetX, 0, gridSpacing * x + offsetX, height, gridWidth, gridColor);
}
for (var y = 0; y <= (height / gridSpacing); y++) {
drawLine(0, gridSpacing * y + offsetY, width, gridSpacing * y + offsetY, gridWidth, gridColor);
}
}
canvas {
cursor: crosshair;
border: 1px solid #000000;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<canvas id="canvas" width="800" height="500"></canvas>
<div id="output"></div>
Related
what exactly I want to achieve is
to draw objects on canvas and
on mouseover display relevant data in tooltip.
here you can view the code.
var canvasBack;
var canvasLabel;
var canvasDraw;
var ctxBack;
var ctxLabel;
var ctxDraw;
var last_mousex = 0;
var last_mousey = 0;
var mousex = 0;
var mousey = 0;
var canWidth;
var canHeight;
var scaleParameter;
var radius;
var xVertex;
var yVertex;
var hotspots = [];
// initialization on loading of canvas
$('canvas').ready(function() {
init();
});
// initialization function used for binding events, and inital logic implemented.
function init() {
scaleParameter = 1;
canvasBack = document.getElementById('backSpace');
canvasLabel = document.getElementById('layerCanvas');
canvasDraw = document.getElementById('drawSpace');
ctxBack = canvasBack.getContext('2d');
ctxLabel = canvasLabel.getContext('2d');
ctxDraw = canvasDraw.getContext('2d');
canWidth = parseInt($(canvasBack).attr('width'));
canHeight = parseInt($(canvasBack).attr('height'));
var canvasx = $(canvasBack).offset().left;
var canvasy = $(canvasBack).offset().top
var mousedown = false;
//Mousedown
$('canvas').on('mousedown', function(e) {
$('#drawSpace').css('display', 'block');
last_mousex = mousex = parseInt(e.clientX - canvasx);
last_mousey = mousey = parseInt(e.clientY - canvasy);
mousedown = true;
});
//Mouseup
$('canvas').on('mouseup', function(e) {
hotspots.push({
x: xVertex,
y: yVertex,
radius: radius,
tip: 'You are over ' + mousex + ',' + mousey
});
let cw = canvasBack.width;
let ch = canvasBack.height;
ctxBack.drawImage(canvasDraw, 0, 0, cw, ch);
$('#drawSpace').css('display', 'none');
mousedown = false;
});
//Mousemove
$('canvas').on('mousemove', function(e) {
mousex = parseInt(e.clientX - canvasx);
mousey = parseInt(e.clientY - canvasy);
if (mousedown) {
// draw(mousedown);
drawEllipse(last_mousex, last_mousey, mousex, mousey);
} else {
hoverTooltip();
}
});
}
function drawEllipse(x1, y1, x2, y2) {
var leftScroll = $("#scrollParent").scrollLeft();
var topScroll = $("#scrollParent").scrollTop();
let cw = canvasBack.width;
let ch = canvasBack.height;
ctxDraw.clearRect(0, 0, cw, ch);
var radiusX = x2 - x1,
radiusY = y2 - y1,
centerX = x1 + radiusX,
centerY = y1 + radiusY,
step = 0.01,
a = step,
pi2 = Math.PI * 2 - step;
radius = Math.sqrt(radiusX * radiusX + radiusY * radiusY) / 2;
ctxDraw.beginPath();
ctxDraw.arc(centerX, centerY, radius, 0, 2 * Math.PI, true);
ctxDraw.closePath();
ctxDraw.fillStyle = 'green';
ctxDraw.fill();
ctxDraw.strokeStyle = '#000';
ctxDraw.stroke();
xVertex = centerX;
yVertex = centerY;
}
// tooltip show on hover over objects
function hoverTooltip() {
var leftScroll = $("#scrollParent").scrollLeft();
var topScroll = $("#scrollParent").scrollTop();
let cw = canvasBack.width;
let ch = canvasBack.height;
for (var i = 0; i < hotspots.length; i++) {
var h = hotspots[i];
var dx = mousex - h.x;
var dy = mousey - h.y;
if (dx * dx + dy * dy < h.radius * h.radius) {
$('#console').text(h.tip);
ctxLabel.clearRect(0, 0, cw, ch);
ctxLabel.fillText(h.tip, mousex + leftScroll, mousey + topScroll);
} else {
ctxLabel.clearRect(0, 0, cw, ch);
}
}
}
#scrollParent {
width: 644px;
height: 364px;
overflow: auto;
position: relative;
}
#scrollParent>canvas {
position: absolute;
left: 0;
top: 0;
border: 1px solid #ababab;
}
#backSpace {
z-index: 0;
}
#drawSpace {
display: none;
z-index: 1;
}
#layerCanvas {
z-index: 2;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="scrollParent">
<!-- actual canvas that is visible -->
<canvas width="640" height="360" id="backSpace"></canvas>
<!-- canvas used for drawing new objects -->
<canvas width="640" height="360" id="drawSpace"></canvas>
<!-- canvas used to display tooltip -->
<canvas width="640" height="360" id="layerCanvas"></canvas>
</div>
<div id="console"></div>
</div>
actual problem is in the bellow image, tooltip worked fine when the 1st object was drawn, but once the second object was drawn tooltip worked only for the second one, not for previously drawn objects.
what is causing this issue, and how to fix it ?
Removing else will not remove the label when leaving the elipse.
You need to exit the loop once you found the correct elipse from the array using break.
function hoverTooltip() {
var leftScroll = $("#scrollParent").scrollLeft();
var topScroll = $("#scrollParent").scrollTop();
let cw = canvasBack.width;
let ch = canvasBack.height;
for (var i = 0; i < hotspots.length; i++) {
var h = hotspots[i];
var dx = mousex - h.x;
var dy = mousey - h.y;
if (dx * dx + dy * dy < h.radius * h.radius) {
$('#console').text(h.tip);
ctxLabel.clearRect(0, 0, cw, ch);
ctxLabel.fillText(h.tip, mousex + leftScroll, mousey + topScroll);
break; // exit the loop
} else {
ctxLabel.clearRect(0, 0, cw, ch);
}
}
}
UPDATE
I figured that if you draw two objects over each other, it will behave poorly. Try this instead. It will display information of the latest drawn spot.
function hoverTooltip() {
var leftScroll = $("#scrollParent").scrollLeft();
var topScroll = $("#scrollParent").scrollTop();
let cw = canvasBack.width;
let ch = canvasBack.height;
var spots = hotspots.filter((h) => {
var dx = mousex - h.x;
var dy = mousey - h.y;
return (dx * dx + dy * dy < h.radius * h.radius);
})
if (spots.length > 0) {
var h = spots[spots.length - 1]; // latest drawn spot
$('#console').text(h.tip);
ctxLabel.clearRect(0, 0, cw, ch);
ctxLabel.fillText(h.tip, mousex + leftScroll, mousey + topScroll);
}
else
{
ctxLabel.clearRect(0, 0, cw, ch);
}
}
I see there are already a few answers. This is mine:
In order to be able to show the label for every circle on hover you need to save all your circles in am array: the circles array. I'm using the ctx.isPointInPath() method to know if the mouse is over the circle, and if it is I paint the label.
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
let cw = canvas.width = 640;
let ch = canvas.height = 360;
let found = false;//is a circle found?
const cText = document.querySelector("#text");
const ctxText = cText.getContext("2d");
cText.width = 640;
cText.height = 360;
ctxText.font="1em Verdana";
let drawing = false;
let circles = []
class Circle{
constructor(x,y){
this.x = x;
this.y = y;
this.r = 0;
}
updateR(m) {
this.r = dist(this,m);
}
draw(){
ctx.beginPath();
ctx.arc(this.x,this.y,this.r,0,2*Math.PI);
}
paint(){
ctx.fillStyle = "green";
ctx.strokeStyle = "black";
this.draw();
ctx.stroke();
ctx.fill();
}
label(m){
this.draw();
if (ctx.isPointInPath(m.x, m.y)) {
ctx.beginPath();
ctx.arc(this.x, this.y, 4, 0, 2 * Math.PI);
ctxText.fillStyle = "black";
ctxText.fillText(`you are over ${this.x},${this.y}`,m.x,m.y)
found = true;
}
}
}
let m = {}// mouse
cText.addEventListener("mousedown",(e)=>{
drawing = true;
m = oMousePos(canvas, e);
let circle = new Circle(m.x,m.y)
circles.push(circle);
})
cText.addEventListener("mouseup",(e)=>{
drawing = false;
})
cText.addEventListener("mousemove",(e)=>{
m = oMousePos(canvas, e);
found = false;
if(drawing){
let circle = circles[circles.length-1];//the last circle in the circles arrey
circle.updateR(m);
}
ctx.clearRect(0,0, cw,ch);
ctxText.clearRect(0,0,cw,ch)
circles.map((c) => {c.paint();});
for(let i = circles.length-1; i >=0 ; i--){
circles[i].label(m);
if(found){break;}
}
})
function oMousePos(canvas, evt) {
var ClientRect = canvas.getBoundingClientRect();
return { //objeto
x: Math.round(evt.clientX - ClientRect.left),
y: Math.round(evt.clientY - ClientRect.top)
}
}
function dist(p1, p2) {
let dx = p2.x - p1.x;
let dy = p2.y - p1.y;
return Math.sqrt(dx * dx + dy * dy);
}
canvas{border:1px solid;position:absolute; top:0; left:0;}
#scrollParent{position:relative;}
<div id="scrollParent">
<!-- actual canvas that is visible -->
<canvas width="640" height="360"></canvas>
<canvas width="640" height="360" id="text"></canvas>
</div>
I've updated the code in base of the comment of #HelderSepu
A SECOND UPDATE in base of a second message from #HelderSepu. He wants to see "multiple message but avoid overlapping the messages"
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
let cw = canvas.width = 640;
let ch = canvas.height = 360;
let text = "";
const cText = document.querySelector("#text");
const ctxText = cText.getContext("2d");
cText.width = 640;
cText.height = 360;
ctxText.font="1em Verdana";
let drawing = false;
let circles = []
class Circle{
constructor(x,y){
this.x = x;
this.y = y;
this.r = 0;
}
updateR(m) {
this.r = dist(this,m);
}
draw(){
ctx.beginPath();
ctx.arc(this.x,this.y,this.r,0,2*Math.PI);
}
paint(){
ctx.fillStyle = "green";
ctx.strokeStyle = "black";
this.draw();
ctx.stroke();
ctx.fill();
}
label(m){
this.draw();
if (ctx.isPointInPath(m.x, m.y)) {
this.text = `[${this.x},${this.y}]`
}else{
this.text = "";
}
}
}
let m = {}// mouse
cText.addEventListener("mousedown",(e)=>{
drawing = true;
m = oMousePos(canvas, e);
let circle = new Circle(m.x,m.y)
circles.push(circle);
})
cText.addEventListener("mouseup",(e)=>{
drawing = false;
})
cText.addEventListener("mousemove",(e)=>{
m = oMousePos(canvas, e);
if(drawing){
let circle = circles[circles.length-1];//the last circle in the circles arrey
circle.updateR(m);
}
ctx.clearRect(0,0, cw,ch);
ctxText.clearRect(0,0,cw,ch);
text="";
circles.map((c) => {c.paint();c.label(m);});
circles.map((c) => {text += c.text;});
ctxText.fillStyle = "black";
ctxText.fillText(text,m.x,m.y)
})
function oMousePos(canvas, evt) {
var ClientRect = canvas.getBoundingClientRect();
return { //objeto
x: Math.round(evt.clientX - ClientRect.left),
y: Math.round(evt.clientY - ClientRect.top)
}
}
function dist(p1, p2) {
let dx = p2.x - p1.x;
let dy = p2.y - p1.y;
return Math.sqrt(dx * dx + dy * dy);
}
canvas{border:1px solid;position:absolute; top:0; left:0;}
#scrollParent{position:relati
<div id="scrollParent">
<!-- actual canvas that is visible -->
<canvas width="640" height="360"></canvas>
<canvas width="640" height="360" id="text"></canvas>
<div id="console"></div>
</div>
How do I shoot bullets from my Player X and Y towards the mouse x and y?
I can find the angle of the mouse X and Y but I have no idea how to create a bullet which will fly towards the mouse.
The code for the mouse coordinates are (dx, dy).
Also, if you could explain the logic behind what you did and how you did it, I would be greatful.
Thanks!
Fiddle
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
var pressingDown = false;
var pressingUp = false;
var pressingLeft = false;
var pressingRight = false;
var mouseX, mouseY;
function Player(x, y) {
this.x = x;
this.y = y;
this.angle;
}
Player.prototype.draw = function() {
context.save();
context.translate(this.x, this.y);
context.rotate(this.angle);
context.beginPath();
context.fillStyle = "green";
context.arc(0, 0, 30, 0, 2 * Math.PI);
context.fill();
context.fillStyle = "red";
context.fillRect(0, -10, 50, 20);
context.restore();
}
Player.prototype.update = function(mouseX, mouseY) {
var dx = mouseX - this.x;
var dy = mouseY - this.y;
this.angle = Math.atan2(dy, dx);
}
canvas.addEventListener('mousemove', mouseMove);
function mouseMove(evt) {
mouseX = evt.x;
mouseY = evt.y;
}
var player = new Player(350, 250);
function updatePlayer() {
context.clearRect(0, 0, canvas.width, canvas.height);
player.draw();
player.update(mouseX, mouseY);
updatePlayerPosition();
}
document.onkeydown = function(event) {
if (event.keyCode === 83) //s
pressingDown = true;
else if (event.keyCode === 87) //w
pressingUp = true;
else if (event.keyCode === 65) //a
pressingLeft = true;
else if (event.keyCode === 68) //d
pressingRight = true;
}
document.onkeyup = function(event) {
if (event.keyCode === 83) //s
pressingDown = false;
else if (event.keyCode === 87) //w
pressingUp = false;
else if (event.keyCode === 65) //a
pressingLeft = false;
else if (event.keyCode === 68) //d
pressingRight = false;
}
updatePlayerPosition = function() {
if (pressingRight)
player.x += 1;
if (pressingLeft)
player.x -= 1;
if (pressingDown)
player.y += 1;
if (pressingUp)
player.y -= 1;
}
function update() {
updatePlayer();
}
setInterval(update, 0)
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<style>
body {
background-color: black;
}
canvas {
position: absolute;
margin: auto;
left: 0;
right: 0;
border: solid 1px white;
border-radius: 10px;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<script type="application/javascript">
(function() {
"use strict";
var canvasWidth = 180;
var canvasHeight = 160;
var canvas = null;
var bounds = null;
var ctx = null;
var mouseX = 0.0;
var mouseY = 0.0;
var player = {
x: (canvasWidth * 0.5) | 0,
y: (canvasHeight * 0.5) | 0,
dx: 0.0,
dy: 0.0,
angle: 0.0,
radius: 17.5,
tick: function() {
this.angle = Math.atan2(mouseY - this.y,mouseX - this.x);
},
render: function() {
ctx.fillStyle = "darkred";
ctx.strokeStyle = "black";
ctx.translate(this.x,this.y);
ctx.rotate(this.angle);
ctx.beginPath();
ctx.moveTo(this.radius,0.0);
ctx.lineTo(-0.5 * this.radius,0.5 * this.radius);
ctx.lineTo(-0.5 * this.radius,-0.5 * this.radius);
ctx.lineTo(this.radius,0.0);
ctx.fill();
ctx.stroke();
ctx.rotate(-this.angle);
ctx.translate(-this.x,-this.y);
}
};
var bullet = {
x: (canvasWidth * 0.5) | 0,
y: (canvasHeight * 0.5) | 0,
dx: 0.0,
dy: 0.0,
radius: 5.0,
tick: function() {
this.x += this.dx;
this.y += this.dy;
if (this.x + this.radius < 0.0
|| this.x - this.radius > canvasWidth
|| this.y + this.radius < 0.0
|| this.y - this.radius > canvasHeight)
{
this.dx = 0.0;
this.dy = 0.0;
}
},
render: function() {
ctx.fillStyle = "darkcyan";
ctx.strokeStyle = "white";
ctx.beginPath();
ctx.arc(this.x,this.y,this.radius,0.0,2.0*Math.PI,false);
ctx.fill();
ctx.stroke();
}
};
function loop() {
// Tick
bullet.tick();
player.tick();
// Render
ctx.fillStyle = "gray";
ctx.fillRect(0,0,canvasWidth,canvasHeight);
bullet.render();
player.render();
//
requestAnimationFrame(loop);
}
window.onmousedown = function(e) {
// The mouse pos - the player pos gives a vector
// that points from the player toward the mouse
var x = mouseX - player.x;
var y = mouseY - player.y;
// Using pythagoras' theorm to find the distance (the length of the vector)
var l = Math.sqrt(x * x + y * y);
// Dividing by the distance gives a normalized vector whose length is 1
x = x / l;
y = y / l;
// Reset bullet position
bullet.x = player.x;
bullet.y = player.y;
// Get the bullet to travel towards the mouse pos with a new speed of 10.0 (you can change this)
bullet.dx = x * 10.0;
bullet.dy = y * 10.0;
}
window.onmousemove = function(e) {
mouseX = e.clientX - bounds.left;
mouseY = e.clientY - bounds.top;
}
window.onload = function() {
canvas = document.getElementById("canvas");
canvas.width = canvasWidth;
canvas.height = canvasHeight;
bounds = canvas.getBoundingClientRect();
ctx = canvas.getContext("2d");
loop();
}
})();
</script>
</body>
</html>
Moving from point to point.
Given two points p1 and p2, each point as a coordinate (x,y) and a speed value that is the number of pixels per step, there are two methods you can use to move between them.
Cartesian
Calculate the vector from p1 to p2
var vx = p2.x - p1.x;
var vy = p2.y - p1.y;
Then calculate the delta by getting the length of the vector, normalise it (make the vector 1 pixel long) then multiply by the speed
var dist = Math.sqrt(vx * vx + vy * vy);
var dx = vx / dist;
var dy = vy / dist;
dx *= speed;
dy *= speed;
This can be optimized a little by scaling the speed with the distance.
var scale = speed / Math.sqrt(vx * vx + vy * vy);
var dx = vx / dist;
var dy = vy / dist;
Polar
The other way is to get the direction from p1 to p2 and use that create the deltas
var dir = Math.atan2(p2.y - p1.y, p2.x - p1.x);
var dx = Math.cos(dir) * speed;
var dx = Math.sin(dir) * speed;
Animate
Once you have the delta you just need to update the position via addition.
const bullet = {x : p1.x, y : p1.y}; // set the bullet start pos
// then each frame add the delta
bullet.x += dx;
bullet.y += dy;
// draw the bullet
So I've recently been messing around with html5 canvas and trying to figure out how to make a particle system. While it does work I want to make vx = mouse coordinates(specifically x) but they start from the center.
What I mean by this is basically if the cursor is in the center of the canvas then vx would be equal to 0 and if the cursor is to the right from the center of the canvas it have positive mouse coordinates (and if the cursor is to the left from the center of canvas it have negative mouse coordinates).
Once that is accomplished I would just do p.speed += p.vx
Here is my javascript:
window.onload = function(){
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var W = window.innerWidth, H = window.innerHeight;
canvas.width = W;
canvas.height = H;
var particles = [];
var particle_count = 100;
for(var i = 0; i < particle_count; i++)
{
particles.push(new particle());
}
function particle()
{
this.vx = -1 + Math.random() * 2;
this.speed = {x: 0, y: -15+Math.random()*10};
this.location = {x: W/2, y: H/2};
this.radius = 5+Math.random()*10;
this.life = 20+Math.random()*10;
this.remaining_life = this.life;
this.r = Math.round(Math.random()*255);
this.g = Math.round(Math.random()*55);
this.b = Math.round(Math.random()*5);
}
function draw()
{
ctx.globalCompositeOperation = "source-over";
ctx.fillStyle = "black";
ctx.fillRect(0, 0, W, H);
ctx.globalCompositeOperation = "lighter";
for(var i = 0; i < particles.length; i++)
{
var p = particles[i];
ctx.beginPath();
p.opacity = Math.round(p.remaining_life/p.life*100)/100
var gradient = ctx.createRadialGradient(
p.location.x, p.location.y, 0, p.location.x, p.location.y, p.radius);
gradient.addColorStop(0, "rgba("+p.r+", "+p.g+", "+p.b+", "+p.opacity+")");
gradient.addColorStop(0.5, "rgba("+p.r+", "+p.g+", "+p.b+", "+p.opacity+")");
gradient.addColorStop(1, "rgba("+p.r+", "+p.g+", "+p.b+", 0)");
ctx.fillStyle = gradient;
ctx.arc(p.location.x, p.location.y, p.radius, Math.PI*2, false);
ctx.fill();
p.remaining_life--;
p.radius--;
p.location.x += p.speed.x += p.vx;
p.location.y += p.speed.y;
if(p.remaining_life < 0 || p.radius < 0) {
particles[i] = new particle();
}
}
}
setInterval(draw, 33); }
Here is my codepen.
You need to translate context to move origin to center.
You also need to reverse the translation on the mouse coordinates as they will still be relative to the upper-left corner (assuming the coordinates are corrected to be relative to canvas).
Example:
var cx = canvas.width * 0.5;
var cy = canvas.height * 0.5;
ctx.translate(cx, cy); // translate globally once
For each mouse coordinate compensate with the translated position:
var pos = getMousePosition(evt); // see below
var x = pos.x - cx;
var y = pos.y - cy;
To adjust mouse position:
function getMousePosition(evt) {
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
}
}
I am creating a canvas dynamically and then drawing a circle.
However, as shown, the circle appears stretched and offset:
var max = 50;
var canvas = document.createElement('canvas');
canvas.id = "mazecanvas";
var size = (document.documentElement.clientWidth / 100) * max;
canvas.style.width = size + "px";
canvas.style.height = size + "px";
canvas.style.position = "absolute";
canvas.style.left = size / 2 + "px";
canvas.style.top = (document.documentElement.clientHeight / 2) - (size / 2) + "px";
document.body.appendChild(canvas);
var x, y;
var ctx = canvas.getContext('2d');
function circle() {
ctx.globalCompositeOperation = "destination-in";
ctx.beginPath();
console.log(x, y);
ctx.arc(x, y, 10, 0, 2 * Math.PI, false);
ctx.closePath();
ctx.fill();
}
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
canvas.addEventListener('mousemove', function(evt) {
var mousePos = getMousePos(canvas, evt);
x = mousePos.x;
y = mousePos.y;
}, false);
function draw() {
ctx.globalCompositeOperation = "source-over";
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < max; i++) {
for (var j = 0; j < max; j++) {
ctx.fillStyle = 'rgb(' + Math.floor(255 - 5.5 * i) + ',' + Math.floor(255 - 5.5 * j) + ',0)';
ctx.fillRect(j * (canvas.width / max), i * (canvas.height / max), canvas.width / max, canvas.height / max);
}
}
circle();
setTimeout(draw, 10);
}
draw();
I do not understand what I am doing wrong, I know it is to do with the canvas creation as when I remove it, and replace it with a static one, the problem is gone:
var max = 50;
var canvas = document.getElementById('canvas');
var x, y;
var ctx = canvas.getContext('2d');
function circle() {
ctx.globalCompositeOperation = "destination-in";
ctx.beginPath();
console.log(x, y);
ctx.arc(x, y, 10, 0, 2 * Math.PI, false);
ctx.closePath();
ctx.fill();
}
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
}
canvas.addEventListener('mousemove', function(evt) {
var mousePos = getMousePos(canvas, evt);
x = mousePos.x;
y = mousePos.y;
}, false);
function draw() {
ctx.globalCompositeOperation = "source-over";
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (var i = 0; i < max; i++) {
for (var j = 0; j < max; j++) {
ctx.fillStyle = 'rgb(' + Math.floor(255 - 5.5 * i) + ',' + Math.floor(255 - 5.5 * j) + ',0)';
ctx.fillRect(j * (canvas.width / max), i * (canvas.height / max), canvas.width / max, canvas.height / max);
}
}
circle();
setTimeout(draw, 10);
}
draw();
<canvas id="canvas"></canvas>
Setting the canvas's CSS style will stretch and squish the pixels. That's why your circle becomes an oval.
Instead, set the canvas element's width and height. This actually adds (or removes) pixels to the canvas to become the specified size. This will keep your circle circular. ;-)
canvas.width=500; // no need to add "px"
canvas.height=400;
On an HTML5 canvas I can't find a method to make colored circles. I've been consulting this as a reference.
Here is my current attempt
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.fillRect(20, 20, 100, 100);
ctx.lineJoin = "round";
ctx.lineWidth = "cornerRadius";
<canvas id="myCanvas" width="300" height="150" style="border:1px solid #d3d3d3;">
(Also on jsFiddle: http://jsfiddle.net/kvnm21r1/1/)
I've tried using the canvas arc method, which creates a circle, but doesn't color it.
I can't use the border-radius property, because ctx is not an element.
Is there any way, I can transform my squares into circles?
Thanks in advance.
You can use quadratic curves to "round-out" the straight lines of your square until they form a circle.
// change sideCount to the # of poly sides desired
//
var sideCount = 4;
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
ctx.lineWidth = 2;
ctx.fillStyle = randomColor();
var PI2 = Math.PI * 2;
var cx = 150;
var cy = 150;
var radius = 100;
var xx = function (a) {
return (cx + radius * Math.cos(a));
}
var yy = function (a) {
return (cy + radius * Math.sin(a));
}
var lerp = function (a, b, x) {
return (a + x * (b - a));
}
var sides = [];
for (var i = 0; i < sideCount; i++) {
sides.push(makeSide(i, sideCount));
}
var percent = 0;
var percentDirection = 0.50;
$("#toShape").click(function () {
percentDirection = -0.50;
})
$("#toCircle").click(function () {
percentDirection = 0.50;
})
animate();
// functions
function animate() {
requestAnimationFrame(animate);
drawSides(percent);
percent += percentDirection;
if (percent > 100) {
percent = 100;
}
if (percent < 0) {
percent = 0;
}
}
function drawSides(pct, color) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
if (pct == 100) {
ctx.beginPath();
ctx.arc(cx, cy, radius, 0, PI2);
ctx.closePath();
ctx.fill();
} else {
ctx.beginPath();
ctx.moveTo(sides[0].x0, sides[0].y0);
for (var i = 0; i < sideCount; i++) {
var side = sides[i];
var cpx = lerp(side.midX, side.cpX, pct / 100);
var cpy = lerp(side.midY, side.cpY, pct / 100);
ctx.quadraticCurveTo(cpx, cpy, side.x2, side.y2);
}
ctx.fill();
}
}
function makeSide(n, sideCount) {
var sweep = PI2 / sideCount;
var sAngle = sweep * (n - 1);
var eAngle = sweep * n;
var x0 = xx(sAngle);
var y0 = yy(sAngle);
var x1 = xx((eAngle + sAngle) / 2);
var y1 = yy((eAngle + sAngle) / 2);
var x2 = xx(eAngle);
var y2 = yy(eAngle);
var dx = x2 - x1;
var dy = y2 - y1;
var a = Math.atan2(dy, dx);
var midX = lerp(x0, x2, 0.50);
var midY = lerp(y0, y2, 0.50);
var cpX = 2 * x1 - x0 / 2 - x2 / 2;
var cpY = 2 * y1 - y0 / 2 - y2 / 2;
return ({
x0: x0,
y0: y0,
x2: x2,
y2: y2,
midX: midX,
midY: midY,
cpX: cpX,
cpY: cpY,
color: randomColor()
});
}
function randomColor() {
return ('#' + Math.floor(Math.random() * 16777215).toString(16));
}
body{ background-color: ivory; }
canvas{border:1px solid red;}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<button id="toShape">Animate to Shape</button>
<button id="toCircle">Animate to Circle</button><br>
<canvas id="canvas" width=300 height=300></canvas>
JSfiddle with a circle
To draw a circle you'll need to draw an arc and have a starting angle and an ending angle. So you'll have to use Pi and define a radius.
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
var centerX = canvas.width / 2;
var centerY = canvas.height / 2;
var radius = 70;
context.beginPath();
context.arc(centerX, centerY, radius, 0, 2 * Math.PI, false);
context.fillStyle = 'green';
context.fill();
context.lineWidth = 5;
context.strokeStyle = '#003300';
context.stroke();
Avoid w3schools.com whenever possible. Refer to sites like MDN instead.
var canvas = document.getElementById('myCanvas');
var ctx = canvas.getContext("2d");
ctx.beginPath();
var radius = 50; // Arc radius
var startAngle = 0; // Starting point on circle
var endAngle = Math.PI*2; // End point on circle
ctx.arc(150, 75, radius, startAngle, endAngle, true);
ctx.fill();
<canvas id="myCanvas" width="300" height="150" style="border:1px solid #d3d3d3;">