Animating circle in html canvas with javascript - javascript

I need to animate a circle moving in a html canvas. For this purpose, I decided to use a typical sprite animation technique which consist in the following general steps:
Initialize the background (in this case, just a gray rectangle)
Calculate new sprite's coordinates
Draw the sprite (the circle)
Reset the backgound
Goto 2
My problem is that the result looks like all the old circles seem to be redrawn each time I call ctx.fill() despite I am reseting the canvas.
What am I doing wrong? Any suggestion?
var canvas;
var ctx;
var canvasPos;
function rgbToHex(r, g, b) {
return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
}
function init() {
canvas = document.getElementById("target_temp");
ctx = canvas.getContext("2d");
canvasPos = { x: canvas.offsetLeft, y: canvas.offsetTop };
drawSlider();
}
var y = 7;
function animate() {
y = y + 1;
knob.setPosition(8, y);
knob.clear();
knob.draw();
if (y < 93) {
setTimeout(animate, 10);
}
}
function drawSlider() {
ctx = canvas.getContext("2d");
ctx.fillStyle = "#d3d3d3";
ctx.fillRect(0, 0, 16, 100);
}
var knob = {
position: { x: 8, y: 7 },
oldPosition: { x: 8, y: 7 },
setPosition(_x, _y) {
this.oldPosition = this.position;
this.position.x = _x;
this.position.y = _y
},
clear() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawSlider();
},
draw() {
ctx.fillStyle = rgbToHex(0, 0, 112);
ctx.arc(8, this.position.y, 7, 0, 2 * Math.PI);
ctx.fill();
}
}
window.onload = function () { init(); animate(); };
<!DOCTYPE html>
<html>
<boyd>
<canvas id="target_temp" width="16px" height="100px"></canvas>
</boyd>
</html>

I see you already figured out to use the beginPath but I will disagree with the location I will put that at the very beginning of the function animate, take a look at the code below, I refactored your code, got rid of some unused variables, and made the slider an object just like you have for the knob
var canvas = document.getElementById("target_temp");
var ctx = canvas.getContext("2d");
var slider = {
width: 16,
height: 100,
draw() {
ctx.fillStyle = "#d3d3d3";
ctx.fillRect(0, 0, this.width, this.height);
}
}
var knob = {
position: {x: 8, y: 7},
radius: 8,
draw() {
ctx.fillStyle = "#00D";
ctx.arc(this.position.x, this.position.y, this.radius, 0, 2 * Math.PI);
ctx.fill();
}
}
function animate() {
ctx.beginPath()
ctx.clearRect(0, 0, canvas.width, canvas.height);
slider.draw();
if (knob.position.y + knob.radius < slider.height)
knob.position.y++;
knob.draw();
setTimeout(animate, 10);
}
window.onload = function() {
animate()
};
<canvas id="target_temp"></canvas>

Ok, I found the cause: the arc() method stacks the circle as part of the path, so, for it to work, we need to reset the path like this:
draw() {
ctx.fillStyle = rgbToHex(0, 2*y, 107);
ctx.beginPath();
ctx.arc(8, this.position.y, 7, 0, 2 * Math.PI);
ctx.fill();
}

Related

How to work with timer in canvas javascript?

I need to make a rectangle appear randomly somewhere in the canvas, and then it will need to appear randomly in a new place, but I have one problem, it appears a new one but the previous rectangle stay where it was at the beginning and then there are so many rectangles in the canvas, I need to be only one, this is what I've done:
function rectangle(x,y){
var ctx
ctx.beginPath();
ctx.rect(20, 20, 15, 10);
ctx.stroke();
}
function randomMove(){
var myVar;
var x;
var y;
x = Math.floor(Math.random() * 10) + 1;
y = Math.floor(Math.random() * 10) + 1;
myVar = setInterval( ()=> {rectangle(x,y)}, 5000); // pass the rectangle function
}
You need to clear the canvas.
The easiest way is to draw a rectangle over entire canvas (assuming it's a white background)
ctx.fillStyle = "white";
ctx.fillRect(0, 0, width, height);
or if it is transparent...
ctx.clearRect(0, 0, width, height);
You will need to do this on every frame.
const ctx = window.canvas.getContext("2d");
function clearCanvas() {
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
}
function rectangle(x, y) {
ctx.beginPath();
ctx.fillStyle = "red";
ctx.rect(x, y, 15, 10);
ctx.stroke();
}
function randomMove() {
var myVar;
var x;
var y;
x = Math.floor(Math.random() * ctx.canvas.width) + 1;
y = Math.floor(Math.random() * ctx.canvas.height) + 1;
rectangle(x, y);
}
function draw() {
clearCanvas();
randomMove();
}
myVar = setInterval(draw, 200);
draw();
<canvas id="canvas"></canvas>

Use image to fill in arc in canvas using javascript

So I am totally new to canvas and trying a project in which I need to make small balls move around with their background as images. Following code is what I am trying right now.
ctx.beginPath();
ctx.arc(
this.pos[0], this.pos[1], this.radius, 0, 2 * Math.PI, true
);
let tempCanvas = document.createElement("canvas"),
tCtx = tempCanvas.getContext("2d");
let ballbackground = new Image();
if (this.color === "green") {
ballbackground.src = "https://s26.postimg.cc/fl2vwj1mh/greenball.png";
}
else if (this.color === "yellow") {
ballbackground.src = "https://s26.postimg.cc/if61a18yh/yellowball.png";
}
else if (this.color === "blue") {
ballbackground.src = "https://s26.postimg.cc/xb4khn7ih/blueball.jpg";
}
tempCanvas.width = 50;
tempCanvas.height = 50;
tCtx.drawImage(ballbackground,0,0,ballbackground.width, ballbackground.height,0,0,50,50);
ctx.fillStyle = ctx.createPattern(tempCanvas, "repeat");
And for moving those balls I do as follows:
const velocityScale = timeDelta / NORMAL_FRAME_TIME_DELTA,
offsetX = this.vel[0] * velocityScale * this.speed,
offsetY = this.vel[1] * velocityScale * this.speed;
this.pos = [this.pos[0] + offsetX, this.pos[1] + offsetY];
However, the problem is when objects move they seem like sliding over background image like so:
If I try "no-repeat" with createPattern, the balls won't display at all.
What I want is those balls with background images moving on the canvas?
move the balls by using the canvas transform?
const ctx = document.querySelector("canvas").getContext("2d");
const pattern = createPattern(ctx);
function drawCircleByPosition(ctx, x, y) {
ctx.beginPath();
ctx.arc(x, y, 50, 0, Math.PI * 2, true);
ctx.fill();
}
function drawCircleByTransform(ctx, x, y) {
ctx.save();
ctx.translate(x, y);
ctx.beginPath();
ctx.arc(0, 0, 50, 0, Math.PI * 2, true);
ctx.fill();
ctx.restore();
}
function render(time) {
time *= 0.001;
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.fillStyle = pattern;
drawCircleByPosition(ctx, 90, 75 + Math.sin(time) * 50);
drawCircleByTransform(ctx, 210, 75 + Math.sin(time) * 50);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
function createPattern(ctx) {
const tCtx = document.createElement("canvas").getContext("2d");
tCtx.canvas.width = 50;
tCtx.canvas.height = 50;
tCtx.fillStyle = "yellow";
tCtx.fillRect(0, 0, 50, 50);
for (let x = 0; x < 50; x += 20) {
tCtx.fillStyle = "red";
tCtx.fillRect(x, 0, 10, 50);
tCtx.fillStyle = "blue";
tCtx.fillRect(0, x, 50, 10);
}
return ctx.createPattern(tCtx.canvas, "repeat");
}
canvas { border: 1px solid black; }
<canvas></canvas>
note that rather than call save and restore you can also just set the transform with setTransform which is probably faster since save and restore saves all state (fillStyle, strokeStyle, font, globalCompositeOperation, lineWidth, etc...).
You can either pass in your own matrix. Example
ctx.setTransform(1, 0, 0, 1, x, y); // for translation
and/or you can reset it to the default whenever and then use the standard transform manipulation functions
ctx.setTransform(1, 0, 0, 1, 0, 0); // the default
ctx.translate(x, y);
or whatever combination of things you want to do.

Js object in a canvas game Uncaught TypeError: Cannot read property 'x' of undefined

I am making a canvas game, which draws an object on the canvas. But it keeps giving me an error x is undefined. It was working before but, when I added the star functions it broke. Any help as I am at a loss.
var spaceShip = {
position: {
x: canvas.width / 2,
y: canvas.height - 20
},
size: {
width: 50,
height: 12
},
Velocity: {
x: 20
},
drawSpaceShip: function(){ // Draw Spaceship Object
ctx.beginPath();
ctx.fillStyle = "blue";
ctx.fillRect(this.position.x, this.position.y, this.size.width, this.size.height);
ctx.fillRect(this.position.x + 15, this.position.y, this.size.width - 30, this.size.height / 2 - 12);
ctx.fillRect(this.position.x + 22.5, this.position.y, this.size.width - 45, this.size.height / 2 - 15);
ctx.closePath();
ctx.fill();
requestAnimationFrame(this.drawSpaceShip);
}// End drawShip function
};// End spaceShip Object
function Star(x, y, rad, velocity, fill){
this.x = Math.floor(Math.random() * 599);//this create a random number between 0 and 599 on the x axis
this.y = 0;
this.rad = Math.floor((Math.random() * 30) + 1);//this create a random number between 10 and 30 for the radius
this.velocity = 6;
this.fill = fill
this.draw = function(){
ctx.beginPath();
ctx.fillStyle = this.fill;
ctx.arc(this.x, this.y, this.rad, 0, Math.PI * 2, true);
ctx.closePath();
ctx.fill();
this.y += this.velocity;
}
}
function createMultipleStars(){
for (var i = 0; i <= 4; i++)
stars[i] = new Star(i * 50, 10, i, i, "rgba(255,215,0,0.6)");
}
createMultipleStars();
function step() {
ctx.clearRect(0,0,canvas.width, canvas.height);
for (var i = 0; i<= 4; i++)
stars[i].draw();
requestAnimationFrame(step);
}
spaceShip.drawSpaceShip();
step();
You are loosing spaceShip object context when detaching this.drawSpaceShip method and passing it to requestAnimationFrame function. In this case drawSpaceShip is invoked with global object context (this === window). You can bind context explicitly with Function.prototype.bind method:
requestAnimationFrame(this.drawSpaceShip.bind(this));

Update HTML5 canvas rectangle on hover?

I've got some code which draws a rectangle on a canvas, but I want that rectangle to change color when I hover the mouse over it.
The problem is after I've drawn the rectangle I'm not sure how I select it again to make the adjustment.
What I want to do:
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
ctx.rect(20,20,150,100);
ctx.stroke();
$('c.[rectangle]').hover(function(this){
this.fillStyle = 'red';
this.fill();
});
You can't do this out-of-the-box with canvas. Canvas is just a bitmap, so the hover logic has to be implemented manually.
Here is how:
Store all the rectangles you want as simple object
For each mouse move on the canvas element:
Get mouse position
Iterate through the list of objects
use isPointInPath() to detect a "hover"
Redraw both states
Example
var canvas = document.querySelector("canvas"),
ctx = canvas.getContext("2d"),
rects = [
{x: 10, y: 10, w: 200, h: 50},
{x: 50, y: 70, w: 150, h: 30} // etc.
], i = 0, r;
// render initial rects.
while(r = rects[i++]) ctx.rect(r.x, r.y, r.w, r.h);
ctx.fillStyle = "blue"; ctx.fill();
canvas.onmousemove = function(e) {
// important: correct mouse position:
var rect = this.getBoundingClientRect(),
x = e.clientX - rect.left,
y = e.clientY - rect.top,
i = 0, r;
ctx.clearRect(0, 0, canvas.width, canvas.height); // for demo
while(r = rects[i++]) {
// add a single rect to path:
ctx.beginPath();
ctx.rect(r.x, r.y, r.w, r.h);
// check if we hover it, fill red, if not fill it blue
ctx.fillStyle = ctx.isPointInPath(x, y) ? "red" : "blue";
ctx.fill();
}
};
<canvas/>
This is a stable code in base of #K3N answer. The basic problem of his code is because when one box is over the another the two may get mouse hover at same time. My answer perfectly solves that adding a 'DESC' to 'ASC' loop.
var canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d");
var map = [
{x: 20, y: 20, w: 60, h: 60},
{x: 30, y: 50, w: 76, h: 60}
];
var hover = false, id;
var _i, _b;
function renderMap() {
for(_i = 0; _b = map[_i]; _i ++) {
ctx.fillStyle = (hover && id === _i) ? "red" : "blue";
ctx.fillRect(_b.x, _b.y, _b.w, _b.h);
}
}
// Render everything
renderMap();
canvas.onmousemove = function(e) {
// Get the current mouse position
var r = canvas.getBoundingClientRect(),
x = e.clientX - r.left, y = e.clientY - r.top;
hover = false;
ctx.clearRect(0, 0, canvas.width, canvas.height);
for(var i = map.length - 1, b; b = map[i]; i--) {
if(x >= b.x && x <= b.x + b.w &&
y >= b.y && y <= b.y + b.h) {
// The mouse honestly hits the rect
hover = true;
id = i;
break;
}
}
// Draw the rectangles by Z (ASC)
renderMap();
}
<canvas id="canvas"></canvas>
You may have to track the mouse on the canvas using JavaScript and see when it is over your rectangle and change the color then. See code below from my blog post
<!DOCTYPE html>
<html>
<body>
<canvas id="myCanvas" width="700" height="500" style="border:1px solid #c3c3c3;">
Your browser does not support the HTML5 canvas tag.
</canvas>
<script>
var myRect={x:150, y:75, w:50, h:50, color:"red"};
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
ctx.fillStyle = myRect.color;
ctx.fillRect(myRect.x, myRect.y, myRect.w, myRect.h);
c.addEventListener("mousemove", function(e){
if ((e.clientX>=myRect.x)&(e.clientX<=myRect.x+myRect.w)&(e.clientY>=myRect.y)&(e.clientY<=myRect.y+myRect.h)){
myRect.color = "green";}
else{
myRect.color = "red";}
updateCanvas();
}, false);
function updateCanvas(){
ctx.fillStyle = myRect.color;
ctx.fillRect(myRect.x, myRect.y, myRect.w, myRect.h);
}
</script>
</body>
</html>
I believe this is a slightly more in-depth answer that would work better for you, especially if you are interested in game design with the canvas element.
The main reason this would work better for you is because it focuses more on an OOP (object orientated programming) approach. This allows for objects to be defined, tracked and altered at a later time via some event or circumstance. It also allows for easy scaling of your code and in my opinion is just more readable and organized.
Essentially what you have here is two shapes colliding. The cursor and the individual point / object it hovers over. With basic squares, rectangles or circles this isn't too bad. But, if you are comparing two more unique shapes, you'll need to read up more on Separating Axis Theorem (SAT) and other collision techniques. At that point optimizing and performance will become a concern, but for now I think this is the optimal approach.
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
const width = canvas.width = window.innerWidth;
const height = canvas.height = window.innerHeight;
const cx = width / 2;
const cy = height / 2;
const twoPie = Math.PI * 2;
const points = []; // This will be the array we store our hover points in later
class Point {
constructor(x, y, r) {
this.x = x;
this.y = y;
this.r = r || 0;
}
}
class HoverPoint extends Point {
constructor(x, y, r, color, hoverColor) {
super(x, y, r);
this.color = color;
this.hoverColor = hoverColor;
this.hovered = false;
this.path = new Path2D();
}
draw() {
this.hovered ? ctx.fillStyle = this.hoverColor : ctx.fillStyle = this.color;
this.path.arc(this.x, this.y, this.r, 0, twoPie);
ctx.fill(this.path);
}
}
class Cursor extends Point {
constructor(x, y, r) {
super(x, y, r);
}
collisionCheck(points) {
// This is the method that will be called during the animate function that
// will check the cursors position against each of our objects in the points array.
document.body.style.cursor = "default";
points.forEach(point => {
point.hovered = false;
if (ctx.isPointInPath(point.path, this.x, this.y)) {
document.body.style.cursor = "pointer";
point.hovered = true;
}
});
}
}
function createPoints() {
// Create your points and add them to the points array.
points.push(new HoverPoint(cx, cy, 100, 'red', 'coral'));
points.push(new HoverPoint(cx + 250, cy - 100, 50, 'teal', 'skyBlue'));
// ....
}
function update() {
ctx.clearRect(0, 0, width, height);
points.forEach(point => point.draw());
}
function animate(e) {
const cursor = new Cursor(e.offsetX, e.offsetY);
update();
cursor.collisionCheck(points);
}
createPoints();
update();
canvas.onmousemove = animate;
There is one more thing that I would like to suggest. I haven't done tests on this yet but I suspect that using some simple trigonometry to detect if our circular objects collide would preform better over the ctx.IsPointInPath() method.
However if you are using more complex paths and shapes, then the ctx.IsPointInPath() method would most likely be the way to go. if not some other more extensive form of collision detection as I mentioned earlier.
The resulting change would look like this...
class Cursor extends Point {
constructor(x, y, r) {
super(x, y, r);
}
collisionCheck(points) {
document.body.style.cursor = "default";
points.forEach(point => {
let dx = point.x - this.x;
let dy = point.y - this.y;
let distance = Math.hypot(dx, dy);
let dr = point.r + this.r;
point.hovered = false;
// If the distance between the two objects is less then their combined radius
// then they must be touching.
if (distance < dr) {
document.body.style.cursor = "pointer";
point.hovered = true;
}
});
}
}
here is a link containing examples an other links related to collision detection
I hope you can see how easily something like this can be modified and used in games and whatever else. Hope this helps.
Below code adds shadow to canvas circle on hovering it.
<html>
<body>
<canvas id="myCanvas" width="1000" height="500" style="border:1px solid #d3d3d3;">
Your browser does not support the HTML5 canvas tag.</canvas>
</body>
<script>
var canvas = document.getElementById("myCanvas"),
ctx = canvas.getContext("2d"),
circle = [{
x: 60,
y: 50,
r: 40,
},
{
x: 100,
y: 150,
r: 50,
} // etc.
];
// render initial rects.
for (var i = 0; i < circle.length; i++) {
ctx.beginPath();
ctx.arc(circle[i].x, circle[i].y, circle[i].r, 0, 2 * Math.PI);
ctx.fillStyle = "blue";
ctx.fill();
}
canvas.onmousemove = function(e) {
var x = e.pageX,
y = e.pageY,
i = 0,
r;
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < circle.length; i++) {
if ((x > circle[i].x - circle[i].r) && (y > circle[i].y - circle[i].r) && (x < circle[i].x + circle[i].r) && (y < circle[i].y + circle[i].r)) {
ctx.beginPath();
ctx.arc(circle[i].x, circle[i].y, circle[i].r, 0, 2 * Math.PI);
ctx.fillStyle = "blue";
ctx.fill();
ctx.shadowBlur = 10;
ctx.lineWidth = 3;
ctx.strokeStyle = 'rgb(255,255,255)';
ctx.shadowColor = 'grey';
ctx.stroke();
ctx.shadowColor = 'white';
ctx.shadowBlur = 0;
} else {
ctx.beginPath();
ctx.arc(circle[i].x, circle[i].y, circle[i].r, 0, 2 * Math.PI);
ctx.fillStyle = "blue";
ctx.fill();
ctx.shadowColor = 'white';
ctx.shadowBlur = 0;
}
}
};
</script>
</html>
I know this is old, but I am surprised no one has mentioned JCanvas. It adds to the simplicity of animating canvas on events. More documentation here https://projects.calebevans.me/jcanvas/docs/mouseEvents/
<html lang="en">
<head>
<!-- css and other -->
</head>
<body onload="draw();">
<canvas id = "canvas" width="500" height="500" style= border:1px solid #000000;"> </canvas>
<script>
function draw() {
$('canvas').drawRect({
layer: true,
fillStyle:'#333',
x:100, y: 200,
width: 600,
height: 400,
mouseover: function(layer) {
$(this).animateLayer(layer, {
fillStyle: 'green'
}, 1000, 'swing');
}
});
}
<script src="https://code.jquery.com/jquery-3.3.1.min.js" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jcanvas/21.0.1/jcanvas.js" crossorigin="anonymous"></script>
</body>
</html>
Consider this following code:
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
ctx.rect(20,20,150,100);
ctx.stroke();
c.addEventListener("mouseover", doMouseOver, false);//added event to canvas
function doMouseOver(e){
ctx.fillStyle = 'red';
ctx.fill();
}
DEMO
You could use canvas.addEventListener
var canvas = document.getElementById('canvas0');
canvas.addEventListener('mouseover', function() { /*your code*/ }, false);
It worked on google chrome
var c=document.getElementById("myCanvas");
var ctx=c.getContext("2d");
ctx.rect(20,20,150,100);
ctx.stroke();
$(c).hover(function(e){
ctx.fillStyle = 'red';
ctx.fill();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<canvas id="myCanvas"/>

How to move rectangle on canvas

I use canvas in my application using JavaScript. On that canvas I draw one rectangle. I want to move rectangle with the help of mouse (e.g moving slider) how to move that rectangle using JavaScript or jQuery.
A Canvas is literally just a surface that you paint on and none of the things you paint are objects.
If you want to pretend they are objects (like moving around a rectangle or a line) then you need to keep track of everything and do all the hit-testing and re-painting yourself .
I wrote a gentle introduction article on getting started by making rectangles that you can select and drag around. Give that a read.
On a second reading, I think I misunderstood your question, so here's an updated version:
http://jsfiddle.net/HSMfR/4/
$(function () {
var
$canvas = $('#canvas'),
ctx = $canvas[0].getContext('2d'),
offset = $canvas.offset(),
draw,
handle;
handle = {
color: '#666',
dim: { w: 20, h: canvas.height },
pos: { x: 0, y: 0 }
};
$canvas.on({
'mousedown.slider': function (evt) {
var grabOffset = {
x: evt.pageX - offset.left - handle.pos.x,
y: evt.pageY - offset.top - handle.pos.y
};
// simple hit test
if ( grabOffset.x >= 0
&& grabOffset.x <= handle.dim.w
&& grabOffset.y >= 0
&& grabOffset.x <= handle.dim.h
) {
$(document).on({
'mousemove.slider': function (evt) {
handle.pos.x = evt.pageX - offset.left - grabOffset.x;
// prevent dragging out of canvas
if (handle.pos.x < 0) {
handle.pos.x = 0;
}
if (handle.pos.x + handle.dim.w > canvas.width) {
handle.pos.x = canvas.width - handle.dim.w;
}
//handle.pos.y = evt.pageY - offset.top - grabOffset.y;
},
'mouseup.slider': function () {
$(document).off('.slider');
}
});
}
}
});
draw = function() {
var val = (100 * (handle.pos.x / (canvas.width - handle.dim.w))).toFixed(2) + '%';
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.fillStyle = handle.color;
ctx.fillRect(handle.pos.x, handle.pos.y, handle.dim.w, handle.dim.h);
ctx.textBaseline = 'hanging';
ctx.font = '12px Verdana';
ctx.fillStyle = '#333';
ctx.fillText(val, 4, 4);
ctx.fillStyle = '#fff';
ctx.fillText(val, 3, 3);
};
setInterval(draw, 16);
});
prev version:
Very simple solution to extend upon:
http://jsfiddle.net/HSMfR/
$(function () {
var
ctx = $('#canvas')[0].getContext('2d'),
$pos = $('#pos'),
draw;
draw = function() {
var x = ($pos.val() / 100) * (ctx.canvas.width - 20);
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.fillStyle = 'black';
ctx.fillRect(x, 0, 20, 20);
};
setInterval(draw, 40);
});

Categories

Resources