Make a shape move up on a canvas - javascript

Currently, I have a canvas which is the width and height of your browser. Using this code:
var requestAnimationFrame = window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame;
var canvas = document.getElementById("canvas");
var width = window.innerWidth;
var height = window.innerHeight;
var circle = canvas.getContext("2d");
canvas.width = width;
canvas.height = height;
for(var i = 0; i < numofcirc; i++)
{
name = "circleno" + i;
var name = new Array(3);
name = [height, rndwidth, rndradius, vel]
circles[i] = name;
}
var vel = 2;
var circles = [];
var numofcirc = 1;
var name;
function DrawCircle()
{
rndwidth = Math.floor((Math.random() * width) + 1);
height = height - 13;
rndradius = Math.floor((Math.random() * 15) + 5);
circle.beginPath();
circle.arc(rndwidth, height, rndradius, 0, 2*Math.PI);
circle.fillStyle = "white";
circle.fill();
circle.translate(0,6);
}
function Move()
{
circle.translate(0,6);
requestAnimationFrame(Move);
}
Move();
DrawCircle();
I am able to create a circle placed randomly at the bottom of your screen. The bit of the code that isn't working is this:
function Move()
{
circle.translate(0,6);
requestAnimationFrame(Move);
}
Fireworks();
When DrawCircle(); is called, the circle is drawn on the canvas. Then Move(); is called. Becuase it uses requestAnimationFrame the function Move(); repeats over and over again. I want this code to move that circle drawn ealier up by 6, so it looks like the circle moving up.
If I add the circle.translate(0,6); to the DrawCircle(); function and change the DrawCircle(); function to this:
function DrawCircle()
{
rndwidth = Math.floor((Math.random() * width) + 1);
height = height - 13;
rndradius = Math.floor((Math.random() * 15) + 5);
circle.beginPath();
circle.arc(rndwidth, height, rndradius, 0, 2*Math.PI);
circle.fillStyle = "white";
circle.fill();
circle.translate(0,6);
requestAnimationFrame(Move);
}
DrawCircle();
then it keeps on drawing rows of circles across the screen which are all separated by 6.
Is there any way I can just make one single circle move up on your screen when it is drawn?
Thank you for you help #HelderSepu !

You should look at examples and build from that...
Here is one simple case:
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
canvas.width = canvas.height = 170;
var circles = []
circles.push({color:"red", x:120, y:120, r:15, speed:{x: 0, y: -0.5}})
circles.push({color:"blue", x:80, y:120, r:20, speed:{x: -0.5, y: -2.5}})
circles.push({color:"green", x:40, y:120, r:5, speed:{x: -1.5, y: -1.0}})
function DrawCircle() {
context.clearRect(0, 0, canvas.width, canvas.height);
circles.forEach(function(c) {
c.x += c.speed.x;
c.y += c.speed.y;
context.beginPath();
context.arc(c.x, c.y, c.r, 0, 2 * Math.PI);
context.fillStyle = c.color;
context.fill();
if (c.x + c.r < 0) c.x = canvas.width + c.r
if (c.y + c.r < 0) c.y = canvas.height + c.r
});
window.requestAnimationFrame(DrawCircle);
}
DrawCircle();
<canvas id="canvas"></canvas>
But if you are going to do a lot more animations you should consider using a game engine, there are a lot of great open source ones:
https://github.com/collections/javascript-game-engines

Since you're getting a sequence of circles, it looks like you're not clearing the canvas when a frame is drawn. Simply draw a white rectangle that fills the canvas whenever a new frame is requested, then draw your circle.
The method you provide as an argument to requestAnimationFrame is responsible for drawing a complete image on the canvas which replaces whatever was there during the previous frame.

Related

How to make moving canvas objects with images in HTML with JavaScript

So I'm working on a project where I have a canvas filled with moving balls. Its an extension/inspired by this codepen project : https://codepen.io/zetyler/pen/LergVR .
It essentially runs with the same physics in place as the codepen, but now I'm trying to draw the moving and colliding balls with images instead of random colors.
The original draw() method looks like this :
var pen = canvas.getContext('2d');
const W = canvas.width;
const H = canvas.height;
var numBalls = 30;
var grav = [0,-0.1];
function Ball(x,y,dx,dy,r) {
this.x = x;
this.y = y;
this.dx = dx;
this.dy = dy;
this.r = r;
this.color = 'hsl('+(Math.random()*360)+',90%,50%)';
this.draw = function() {
pen.fillStyle = this.color;
pen.beginPath();
pen.arc(this.x,this.y,this.r,0,2*Math.PI);
pen.fill();
}
I'm refactoring the draw method to try and work with an image instead of a random color fill, and so far I can't even get an image to show up. Currently my draw method looks like this:
const canvas = document.querySelector('canvas');
const context = canvas.getContext('2d');
const width = canvas.width;
const height = canvas.height;
let numBalls = 1;
let grav = [0,-0.1];
//try feeding the ball function an object
//and destructuring the inputs
class Ball {
constructor (x, y, dx, dy, r) {
this.x = x;
this.y = y;
this.dx = dx;
this.dy = dy;
this.r = r;
//probably won't need this
//this.color = 'hsl(' + (Math.random() * 360) + ', 90%, 50%)';
}
draw() {
var thumbImg = document.createElement('img');
thumbImg.src = './svgs/javascriptIcon.svg';
thumbImg.onload = function() {
context.save();
context.beginPath();
context.arc(25, 25, 25, 0, Math.PI * 2, true);
context.closePath();
context.clip();
context.drawImage(thumbImg, 0, 0, 50, 50);
context.beginPath();
context.arc(0, 0, 25, 0, Math.PI * 2, true);
context.clip();
context.closePath();
context.restore();
};
}
It's been so long since I've used the html canvas. I can't figure out what I'm doing wrong. I thought I would at least be able to get the image to show up, but no such luck.
Thanks for checking it out! Please let me know what you think.
I am not sure why you are using ctx.clip,
but if you just want to replace the coloured balls with images try this in your draw method
this.draw = function() {
// make sure the img is loaded
//pen.fillStyle = this.color;
pen.beginPath();
pen.arc(this.x,this.y,this.r,0,2*Math.PI);
pen.drawImage(img,this.x, this.y,this.r, this.r)
pen.fill();
}
after that play with IMG x and y positions for example pen.drawImage(img,this.x + somefactor, this.y - somefactor,this.r + somefactor, this.r + somefatcor)
just to make sure that img is perfectly cover the coloured ball so it behavies just like it

How can I hover over a shape in canvas and change the color if I have multiple shapes?

I want to be able to hover my mouse over different rectangles and have the rectangle change color when hovered, what I have now works for the last rectangle but the others get cleared. The rectangles are created using a class/constructor, an array, and a loop. Code is below:
/*Variables*/
let canvas = document.querySelector('#canvas'),
ctx = canvas.getContext('2d'),
square;
/*Board Class*/
class Board {
constructor(startX, startY, height, width, angle) {
this.startX = startX;
this.startY = startY;
this.height = height;
this.width = width;
this.angle = angle;
}
drawBoard() {
let canvasWidth = window.innerWidth * .95,
drawWidth = canvasWidth * this.width,
drawHeight = canvasWidth * this.height,
drawStartX = canvasWidth * this.startX,
drawStartY = canvasWidth * this.startY;
square = new Path2D();
ctx.rotate(this.angle * Math.PI / 180);
square.rect(drawStartX, drawStartY, drawHeight, drawWidth);
ctx.fillStyle = 'red';
ctx.fill(square);
}
}
/*Event Listener for changing rectangle color and redrawing*/
canvas.addEventListener('mousemove', function(event) {
if (ctx.isPointInPath(square, event.offsetX, event.offsetY)) {
ctx.fillStyle = 'white';
}
else {
ctx.fillStyle = 'red';
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fill(square);
});
/*Instantiate Array*/
let b = [];
/*Loop to create boards and push to array*/
for(let i = 1; i < 11; i++){
b.push(new Board(.05 * i, .25, .04, .03, 0));
}
/*Function to loop through array and draw boards when page loads*/
function loadFunctions(){
background.draw();
b.forEach(function(board){
board.drawBoard();
})
}
This is my first project with the Canvas API and it's giving me a lot of trouble, normally I could identify the shape by class/id if it where made with a regular HTML element but I'm not sure where to go from here...
I've tried looping through the array that contains the board info but cannot get anything to work. Any help is appreciated!
Thanks
Let's step through your code, to get a better picture of what's going on.
As soon as you move your mouse over the canvas, the mousemove listener gets fired and executes it's associated callback function.
Inside this callback function we'll find this as the very first line:
if (ctx.isPointInPath(square, event.offsetX, event.offsetY))
So this if-statement checks it the current mouse position is inside of square. Well, the big question is: what is square actually?
If we look over your code a bit more, we'll find out that it's a global variable, which gets some value inside the Board class drawBoard() function as:
square = new Path2D();
square.rect(drawStartX, drawStartY, drawHeight, drawWidth);
Apparently it's a Path2D holding the rectangle of one of the bars - but which one actually?
Let's take a look at this function:
for (let i = 0; i < 10; i++) {
b.push(new Board(0.05 * i, 0.25, 0.04, 0.03, 0));
}
and
function loadFunctions() {
b.forEach(function(board) {
board.drawBoard();
})
}
In the first loop, you're populating the array b with ten instances of Board and in the forEach loop, you're calling each Board's drawBoard() function.
What does all this mean? Yes, square will always hold a reference to the bar, which's drawBoard() function has been called the last time - which will always be the last Board in your array.
To summarize: the only bar your checking in the mousemove callback is always the last one in the array.
So:
if (ctx.isPointInPath(square, event.offsetX, event.offsetY)) {
ctx.fillStyle = 'white';
}
else {
ctx.fillStyle = 'red';
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fill(square);
translated to plain english means: if the point is in square's bound, set the fillStyle to red, clear the whole screen and afterwards fill one bar with red.
What you need to do instead is checking the mouse position with every Board instance from the array. It ain't to hard though - just make the Path2D a class variable of Board and inside the callback function loop over the whole array and compare the mouse position with each Board's .square property.
Here's an example (just click on 'Run code snippet'):
let canvas = document.querySelector('#canvas'),
ctx = canvas.getContext('2d');
let b = [];
class Board {
constructor(startX, startY, height, width, angle) {
this.startX = startX;
this.startY = startY;
this.height = height;
this.width = width;
this.angle = angle;
this.square = new Path2D();
}
drawBoard() {
let canvasWidth = window.innerWidth * 0.95,
drawWidth = canvasWidth * this.width,
drawHeight = canvasWidth * this.height,
drawStartX = canvasWidth * this.startX,
drawStartY = canvasWidth * this.startY;
ctx.rotate(this.angle * Math.PI / 180);
this.square.rect(drawStartX, drawStartY, drawHeight, drawWidth);
ctx.fillStyle = 'red';
ctx.fill(this.square);
}
}
canvas.addEventListener('mousemove', function(event) {
ctx.clearRect(0, 0, canvas.width, canvas.height);
let currentSquare;
for (let i = 0; i < b.length; i++) {
currentSquare = b[i].square;
if (ctx.isPointInPath(currentSquare, event.offsetX, event.offsetY)) {
ctx.fillStyle = 'white';
} else {
ctx.fillStyle = 'red';
}
ctx.fill(currentSquare);
}
});
for (let i = 0; i < 10; i++) {
b.push(new Board(0.05 * i, 0.25, 0.04, 0.03, 0));
}
function loadFunctions() {
b.forEach(function(board) {
board.drawBoard();
})
}
loadFunctions();
<canvas id="canvas" width=500 height=300></canvas>

Make canvas transparent

This is what my body looks like:
body
{
background-image:url('../images/bg.png');
background-repeat:no-repeat;
background-size:fixed 100vw;
background-position:center;
}
The issue is, the canvas is white instead of being transparent. Is there a way to make it transparent so I can place the dna wave on top of a background?
Codepen example
One easy way, is using an offscreen canvas.
First set its context's globalAlpha value to something between 0 and 1, this will determine how fast your previous drawings will disappear.
Then, in the animation loop, before doing the new drawings,
clear the offscreen context,
draw the visible canvas on the offscreen one,
clear the visible canvas
draw back the offscreen one on the visible one
In the process, your image will have lost opacity.
var clear = function(){
// clear the clone canvas
cloneCtx.clearRect(0,0,canvasWidth, canvasHeight)
// this should be needed at init and when canvas is resized but for demo I leave it here
cloneCtx.globalAlpha = '.8';
// draw ou visible canvas, a bit less opaque
cloneCtx.drawImage(context.canvas, 0,0)
// clear the visible canvas
context.clearRect(0,0,canvasWidth, canvasHeight)
// draw back our saved less-opaque image
context.drawImage(clone, 0,0)
}
var canvas = document.getElementById('canvas'),
context = canvas.getContext('2d'),
// create an offscreen clone
clone = canvas.cloneNode(),
cloneCtx = clone.getContext('2d'),
canvasWidth = canvas.width =
clone.width =window.innerWidth,
canvasHeight = canvas.height = clone.height = window.innerHeight,
globalTick = 0,
points = [],
pointCount = 12,
pointSpeed = 6,
spacing = canvasWidth / pointCount,
pointCount = pointCount + 2,
verticalPointRange = 60,
randomRange = function(min, max){
return Math.floor( (Math.random() * (max - min + 1) ) + min);
},
iPath,
iPoints;
var Point = function(x, y, alt){
this.x = x;
this.y = y;
this.yStart = y;
this.alt = alt;
}
Point.prototype.update = function(i){
var range = (this.alt) ? verticalPointRange : -verticalPointRange;
this.x += pointSpeed;
this.y = (this.yStart) + Math.sin(globalTick/14) * -range;
if(this.x > (canvasWidth + spacing)){
this.x = -spacing;
var moved = points.splice(i, 1);
points.unshift(moved[0]);
}
}
var updatePoints = function(){
var i = points.length;
while(i--){
points[i].update(i);
}
}
for(iPoints = 0; iPoints < pointCount; iPoints++){
var alt = (iPoints % 2 === 0);
var offset = (alt) ? verticalPointRange : -verticalPointRange;
points.push(new Point(spacing * (iPoints-1), canvasHeight/2, alt));
}
var renderPath = function(){
context.beginPath();
context.moveTo(points[0].x, points[0].y);
for(iPath = 1; iPath < pointCount; iPath++){
context.lineTo(points[iPath].x, points[iPath].y);
}
context.stroke();
}
var loop = function(){
requestAnimationFrame(loop, canvas);
clear();
updatePoints();
renderPath();
globalTick++;
};
loop();
canvas { display: block; }
body{
background-color: ivory;
}
<canvas id="canvas"></canvas>
Canvases are transparent by default.
Try setting a page background image, and then put a canvas over it. If nothing is drawn on the canvas, you can fully see the page background.
you should try
context.clearRect(0,0,width,height);
for more you can refer How do I make a transparent canvas in html5?

Scribing a cicrular line in Canvas

I am attempting to mouse-drag a dot around the outer perimeter of a large circle and have that dot appear to scribe a thick line around the outer perimeter behind itself. I can get everything to work except scribing the outer line behind the dot. I have researched many ideas and tried many of my own but the line still produces "spotted" results. Here is an image to show what I'm attempting.
MounseDrag Scribed Line
Thank you for taking the time to read my question. :-)
<script type="text/javascript">
var canvas1 = document.getElementById("canvas1"),
canvas2 = document.getElementById("canvas2"),
c1 = canvas1.getContext("2d"),
c2 = canvas2.getContext("2d"),
dot = 7,
started = false,
width = 350,
height = 350,
radians = 0,
cRad = 165, // Circle Radius
cord = {mX:0, mY:0, csX:0, snY:0, x:0, y:0},
init = function(){
cord.mX = 0;
cord.mY = 0;
cord.csX = width /2 + cRad;
cord.snY = height /2;
cord.x = width /2;
cord.y = height /2;
};
init();
canvas1.width = width;
canvas1.height = height;
canvas2.width = width;
canvas2.height = height;
canvas1.addEventListener("mousemove", function(event) {
cord.mX = event.clientX - canvas1.offsetLeft;
cord.mY = event.clientY - canvas1.offsetTop;
});
canvas1.addEventListener("mousedown", function(event) {
if (started) {
started = false;
} else {
started = true;
render();
};
});
function update() {
radians = Math.atan2(cord.mY - width/2, cord.mX - height/2);
cord.csX = width/2 - Math.cos(radians) * cRad * -1;
cord.snY = height/2 - Math.sin(radians) * cRad * -1;
};
function outerTheta() {
c2.beginPath();
c2.arc(cord.csX, cord.snY, 3, 0, Math.PI * 2);
c2.closePath();
c2.fillStyle = "#000";
c2.fill();
};
function render() {
c1.clearRect(0, 0, width, height);
c1.beginPath();
c1.moveTo(cord.x, cord.y);
c1.lineTo(cord.csX, cord.snY);
c1.lineWidth = 3;
c1.strokeStyle = "#000";
c1.stroke();
c1.beginPath(); //<---------------------------------- Drag-Dot
c1.arc(cord.csX, cord.snY, dot, 0, Math.PI * 2);
c1.closePath();
c1.fillStyle = "#000";
c1.fill();
if(started){
update();
outerTheta();
requestAnimationFrame(render);
};
};
render();
</script>
The browser is not able to cycle the animation as quickly as the mouse is moving. If you move the mouse slowly, then the dots that are drawn in each animation cycle overlap and the circle has a solid line. If you move the mouse quickly, then the dots do not overlap and you get "spotting".
If you pay close attention to the way drawing programs work, you will see that the "pen" tool draws a continuous line. If you move the mouse quickly while using the tool, the continuous line is made up of line segments that stretch from each point that the computer was able to capture while your mouse was moving quickly.
I modified your program so that a line segment stretches between each captured point during the animation cycle:
https://jsfiddle.net/17hvw5pp
var canvas1 = document.getElementById("canvas1"),
canvas2 = document.getElementById("canvas2"),
c1 = canvas1.getContext("2d"),
c2 = canvas2.getContext("2d"),
dot = 7,
started = false,
width = 350,
height = 350,
radians = 0,
cRad = 165, // Circle Radius
cord = {mX:0, mY:0, csX:0, snY:0, x:0, y:0},
init = function(){
cord.mX = 0;
cord.mY = 0;
cord.csX = width /2 + cRad;
cord.snY = height /2;
cord.lastCSX = cord.csX;
cord.lastSNY = cord.snY;
cord.x = width /2;
cord.y = height /2;
};
canvas1.style.position="absolute";
canvas2.style.position="absolute";
init();
canvas1.width = width;
canvas1.height = height;
canvas2.width = width;
canvas2.height = height;
canvas1.addEventListener("mousemove", function(event) {
cord.mX = event.clientX - canvas1.offsetLeft;
cord.mY = event.clientY - canvas1.offsetTop;
});
canvas1.addEventListener("mousedown", function(event) {
if (started) {
started = false;
} else {
started = true;
render();
};
});
function update() {
radians = Math.atan2(cord.mY - width/2, cord.mX - height/2);
cord.csX = width/2 - Math.cos(radians) * cRad * -1;
cord.snY = height/2 - Math.sin(radians) * cRad * -1;
};
function outerTheta() {
//draw a line from the last known coordinate to the new known coordinate
c2.beginPath();
c2.moveTo(cord.lastCSX, cord.lastSNY);
c2.lineTo(cord.csX, cord.snY);
c2.lineWidth=5;
c2.strokeStyle="#000";
c2.stroke();
cord.lastCSX = cord.csX;
cord.lastSNY = cord.snY;
c2.beginPath();
c2.arc(cord.csX, cord.snY, 3, 0, Math.PI * 2);
c2.closePath();
c2.fillStyle = "#000";
c2.fill();
};
function render() {
c1.clearRect(0, 0, width, height);
c1.beginPath();
c1.moveTo(cord.x, cord.y);
c1.lineTo(cord.csX, cord.snY);
c1.lineWidth = 3;
c1.strokeStyle = "#000";
c1.stroke();
c1.beginPath(); //<---------------------------------- Drag-Dot
c1.arc(cord.csX, cord.snY, dot, 0, Math.PI * 2);
c1.closePath();
c1.fillStyle = "#000";
c1.fill();
if(started){
update();
outerTheta();
requestAnimationFrame(render);
};
};
render();
This works better, but not perfectly: If you move the mouse quickly, the line segment will become a chord across the circle and this ruins the effect.
I attempted to modify the program to draw an arc between the two known points:
https://jsfiddle.net/17hvw5pp/1/
You can see that this implementation is also not ideal because the arc function becomes confused about which direction to draw the partial circle based on just two radians coordinates. Using quaternion math will solve this problem for you.
https://en.wikipedia.org/wiki/Quaternion
But that may be more complication that you want to introduce into this project.

Animation loop and scaling

Well I've got a few question to ask! Firstly What this code is doing is creating and drawing snowflakes with unique density which will all fall at a different rate. My first question is how do i make this loop continuous?
Secondly, I've translated my origin point(0,0) to the middle of the canvas (it was part of the criteria). I've now got this issue in which that when the snowfall is called it will either be drawn on the left side of the screen or the right, not both. How do i solve this?
Finally i know when doing animations that you have to clear the canvas after each re-drawing, however i haven't added this in and yet it still works fine?
//Check to see if the browser supports
//the addEventListener function
if(window.addEventListener)
{
window.addEventListener
(
'load', //this is the load event
onLoad, //this is the evemnt handler we going to write
false //useCapture boolen value
);
}
//the window load event handler
function onLoad(Xi, Yy) {
var canvas, context,treeObj, H, W, mp;
Xi = 0;
Yy = 0;
mp = 100;
canvas = document.getElementById('canvas');
context = canvas.getContext('2d');
W = window.innerWidth;
H = window.innerHeight;
canvas.width = W;
canvas.height = H;
context.translate(W/2, H/2);
var particles = [];
for(var i = 0; i < mp; i++) {
particles.push({
x: Math.random()*-W, //x
y: Math.random()*-H, //y
r: Math.random()*6+2, //radius
d: Math.random()* mp // density
})
}
treeObj = new Array();
var tree = new TTree(Xi, Yy);
treeObj.push(tree);
function drawCenterPot(){
context.beginPath();
context.lineWidth = "1";
context.strokeStyle = "Red";
context.moveTo(0,0);
context.lineTo(0,-H);
context.lineTo(0, H);
context.lineTo(-W, 0);
context.lineTo(W,0);
context.stroke();
context.closePath();
}
function drawMountain() {
context.beginPath();
context.fillStyle = "#FFFAF0";
context.lineWidth = "10";
context.strokeStyle = "Black";
context.moveTo(H,W);
context.bezierCurveTo(-H*10,W,H,W,H,W);
context.stroke();
context.fill();
}
function drawSky() {
var linearGrad = context.createLinearGradient(-100,-300, W/2,H);
linearGrad.addColorStop(0, "#000000");
linearGrad.addColorStop(1, "#004CB3");
context.beginPath();
context.fillStyle = linearGrad;
context.fillRect(-W/2, -H/2, W, H);
context.stroke();
context.fill();
drawMountain();
drawCenterPot();
}
function drawSnow(){
context.fillStyle = "White";
context.beginPath();
for(i = 0; i<mp; i++)
{
var p = particles[i];
context.moveTo(p.x,p.y);
context.arc(p.x, p.y, p.r, Math.PI*2, false);
}
context.fill();
}
function update() {
var angle = 0;
angle+=0.1;
for(var i=0; i<mp; i++) {
var p = particles[i];
p.x += Math.sin(angle) * 2;
p.y += Math.cos(angle+p.d) + 1 * p.r;
}
drawSky();
drawSnow();
draw();
}
function draw() {
for(var i =0; i < treeObj.length; i++)
{
context.save();
context.translate(Xi-H,Yy-W);
context.scale(1, 1);
treeObj[0].draw(context);
context.restore();
}
}
setInterval(update, 33);
}
About your animation:
What's happening is your flakes are falling out of view below the bottom of the canvas.
So when any flake's p.y+p.r > canvas.height you could:
destroy that flake and (optionally) add another falling from above the canvas
or
"recycle" that flake by changing its p.y to above the canvas.
About your design working without context.clearRect:
In your design, when you fill the whole canvas with "sky", you are effectively clearing the canvas.
About your flakes only falling on half the screen:
Instead of translating to mid-screen:
Don't translate at all and let p.x be any Math.random()*canvas.width

Categories

Resources