This animation (based on the answer of
Вася Воронцов) loads the computer very much. I do this animation in canvas. Animation loads proccesor very much. Here the light follows the cursor and leaves traces. Animation works correctly but proccesor loads very much.
Deleting and changing the radii of circles is done by saving their coordinates.
The effect is controlled by changing the variables radius (circle radius), period (time for which the circle disappears), color (circle color), blur (blur radius) and cursor radius (pointer circle radius).
How to optimize this animation so that it loads the computer less?
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var width = document.body.offsetWidth;
var height = document.body.offsetHeight;
var points = [],
cursor = [-10, -10];
var t = 0;
var radius = 100;
var period = 2100;
var color = "rgba(239, 91, 59, .5)";
var blur = 600;
canvas.style.width = canvas.width = width;
canvas.style.height = canvas.height = height;
context.fillStyle = color;
var filter = context.filter = "blur(" + 50 + "px)";
var dr = radius / period;
function draw() {
context.clearRect(0, 0, width, height);
let i = 0;
let deleted = 0;
let dt = -t + (t = window.performance.now());
context.beginPath();
while (i++ < points.length-1) {
let p = points[i];
p[2] += dt;
let r = radius - p[2] * dr;
context.moveTo(p[0], p[1]);
if (p[2] <= period) {
context.arc(p[0], p[1], r, 0, 2*Math.PI, true);
} else deleted = i;
}
context.fill();
points.splice(0, deleted);
context.beginPath();
context.arc(cursor[0], cursor[1], 20, 0, 2*Math.PI, true);
context.filter = "none";
context.fill();
context.filter = filter;
window.requestAnimationFrame(draw);
}
window.onmousemove = function(event) {
let x = event.pageX;
let y = event.pageY;
let backwardX = 0;
let backwardY = 0;
backwardX += (x-backwardX) / 5
backwardY += (y-backwardY) / 5
points.push([x, y, 0]);
cursor = [x, y];
}
t = window.performance.now();
window.requestAnimationFrame(draw);
body {
height: 100%;
width: 100%;
position: absolute;
cursor: none;
margin: 0;
}
<canvas id="canvas"></canvas>
PS: Question in Russian.
It's slow because you have a lot of overdraw. Each frame, a large number of points is being drawn, and each point touches a lot of pixels.
You can achieve something that looks very similar if you realize that the canvas retains its contents between frames. So every frame, you could do something like this:
Fade the canvas towards white by drawing a nearly transparent white rectangle over it.
Draw one new blurred point, at the current cursor location.
The circle that follows the mouse can easily be achieved by overlaying a separate element on top of the canvas, for example a <div>. Use transform: translate(x, y); to move it, which is more performant than using left/top because it's a compositor-only property. Add will-change: transform; for an extra potential performance boost.
I have a blue circle which is rotating around the red circle and moves on canvas continuously in one direction as long as the button is pressed.
Now I want to draw with the red circle while it is moving when the button is pressed (trace of its path).
Problems:
I have tried to make changes to clearRect() but I didn't succeed. the blue circle starts to draw on the canvas while moving which I don't need.
If its not possible to do with clearRect() function, Is it possible to do this by stacking canvas layers. Please help with example
const canvas = document.getElementById('canvas1');
const ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
let positionX = 100;
let positionY = 100;
let X = 50;
let Y = 50;
let angle = 0;
let mouseButtonDown = false;
document.addEventListener('mousedown', () => mouseButtonDown = true);
document.addEventListener('mouseup', () => mouseButtonDown = false);
function circle(){
ctx.fillStyle = 'red';
ctx.beginPath();
ctx.arc(X, Y, 20, 0, Math.PI*2);
ctx.closePath();
ctx.fill();
}
function direction(){
ctx.fillStyle = 'blue';
ctx.beginPath();
ctx.arc(positionX + X, positionY + Y, 10, 0, Math.PI*2);
ctx.closePath();
positionX = 35 * Math.sin(angle);
positionY = 35 * Math.cos(angle);
ctx.fill();
}
function animate(){
if (mouseButtonDown) {
X += positionX / 10;
Y += positionY / 10;
} else {
angle += 0.1;
}
ctx.clearRect(X-positionX,Y-positionY, 20, 20);
circle();
direction();
requestAnimationFrame(animate);
}
animate();
#canvas1{
position: absolute;
top:0;
left: 0;
width: 100%;
height: 100%;
}
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="style.css">
</head>
<body>
<canvas id="canvas1"></canvas>
<script src="script.js"></script>
</body>
</html>
Don`t stack canvas on the page
Each canvas you add to the page increases the amount of work the GPU and page compositor needs to do to render the page.
Use a second canvas that is not on the page and do the compositing by rendering the canvas to the onpage canvas using ctx.drawImage(secondCanvas, 0, 0).
This reduces the workload for the compositor, and in many cases avoid the need to do an addition image render (composite) for the second canvas I.E. onpage can require 3 drawImages (one for each canvas and once for the result) rather than 2 (once in your code and once as the result) if you use only one onpage canvas.
Using second canvas
Create a second canvas to store the drawn red lines.
You can create a copy of a canvas using
function copyCanvas(canvas, copyContent = false) {
const can = Object.assign(document.createElement("canvas"), {
width: canvas.width, height: canvas.height
});
can.ctx = can.getContext("2d");
copyContent && can.ctx.drawImage(canvas, 0, 0);
return can;
}
When you create render functions like circle, and direction pass as an argument the 2D context eg circle(ctx) so that it is easy to direct the rendering to any canvas.
function circle(ctx){
ctx.fillStyle = 'red';
ctx.beginPath();
ctx.arc(X, Y, redSize, 0, Math.PI*2);
ctx.fill();
}
// the background canvas
const bgCan = copyCanvas(canvas);
circle(bgCan.ctx); // will draw to the background canvas
Updating animation
When animating is is easiest to clear the whole canvas rather than mess about clearing only rendered pixels. Clearing rendered pixels gets complicated very quickly and will end up being many times slower than clearing the whole canvas.
After you clear the canvas draw the background canvas to the main canvas
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.drawImage(bgCan, 0, 0);
When the mouse button is down draw the circle to the background canvas and while it is up draw to the main canvas.
Example
Adds a function to copy a canvas. copyCanvas
Clears the main canvas, and draws the background canvas onto the main canvas.
Render functions circle and direction have argument ctx to direct rendering to any context.
When mouse is down circle is drawn to background canvas bgCan else to the main canvas.
requestAnimationFrame(animate);
const ctx = canvas1.getContext('2d');
canvas1.width = innerWidth;
canvas1.height = innerHeight;
const bgCan = copyCanvas(canvas1);
const redSize = 10, blueSize = 5; // circle sizes on pixels
const drawSpeed = 2; // when button down draw speed in pixels per frame
var X = 50, Y = 50;
var angle = 0;
var mouseButtonDown = false;
document.addEventListener('mousedown', () => mouseButtonDown = true);
document.addEventListener('mouseup', () => mouseButtonDown = false);
function copyCanvas(canvas) {
const can = Object.assign(document.createElement("canvas"), {
width: canvas.width, height: canvas.height
});
can.ctx = can.getContext("2d");
return can;
}
function circle(ctx){
ctx.fillStyle = 'red';
ctx.beginPath();
ctx.arc(X, Y, redSize, 0, Math.PI*2);
ctx.fill();
}
function direction(ctx){
const d = blueSize + redSize + 5;
ctx.fillStyle = 'blue';
ctx.beginPath();
ctx.arc(d * Math.sin(angle) + X, d * Math.cos(angle) + Y, blueSize, 0, Math.PI*2);
ctx.fill();
}
function animate(){
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
ctx.drawImage(bgCan, 0, 0);
if (mouseButtonDown) {
circle(bgCan.ctx);
X += Math.sin(angle) * drawSpeed;
Y += Math.cos(angle) * drawSpeed;
} else {
angle += 0.1;
circle(ctx);
}
direction(ctx);
requestAnimationFrame(animate);
}
#canvas1{
position: absolute;
top:0;
left: 0;
width: 100%;
height: 100%;
}
<canvas id="canvas1"></canvas>
BTW ctx.closePath() is like ctx.lineTo it is not the opposite to ctx.beginPath. A full arc or if you are just filling a shape you don't need to use ctx.closePath
BTW window is the default this, you don't need to include it, you dont use it to get at window.documentso why use it forwindow.innerWidth(same asinnerWidth` )
You could alter your code to keep track of the path of the red circle, with an array property, like this:
const canvas = document.getElementById('canvas1');
const ctx = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
let mouseButtonDown = false;
document.addEventListener('mousedown', () => mouseButtonDown = true);
document.addEventListener('mouseup', () => mouseButtonDown = false);
function drawCircle({x, y, radius, color}) {
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI*2);
ctx.fill();
}
const red = { x: 50, y: 50, radius: 20, color: "red", path: [] };
const blue = { x: 100, y: 100, radius: 10, color: "blue", angle: 0 };
function animate(){
if (mouseButtonDown) {
red.path.push({x: red.x, y: red.y}); // store the old value
red.x += (blue.x - red.x) / 10;
red.y += (blue.y - red.y) / 10;
} else {
blue.angle += 0.1;
}
blue.x = red.x + 35 * Math.sin(blue.angle);
blue.y = red.y + 35 * Math.cos(blue.angle);
ctx.clearRect(0, 0, canvas.width, canvas.height); // clear the whole canvas
for (const {x, y} of red.path) { // draw circle at all the previous positions
drawCircle({...red, x, y});
}
drawCircle(red);
drawCircle(blue);
requestAnimationFrame(animate);
}
animate();
Using 2 canvases also works and may perform better especially when the path of the red circle has gotten long, because the background canvas doesn't need to be cleared and redrawn. Add a 2nd canvas in your html page with the same positioning, and give them ids 'background' and 'foreground'. You can then adjust the code to draw the blue circle to the foreground and red circles to the background (or vice versa).
// Create 2 canvases, set them to full size and get the contexts
const backgroundCanvas = document.getElementById('background');
const foregroundCanvas = document.getElementById('foreground');
const background = backgroundCanvas.getContext("2d");
const foreground = foregroundCanvas.getContext("2d");
backgroundCanvas.width = innerWidth;
backgroundCanvas.height = innerHeight;
foregroundCanvas.width = innerWidth;
foregroundCanvas.height = innerHeight;
let mouseButtonDown = false;
document.addEventListener('mousedown', () => mouseButtonDown = true);
document.addEventListener('mouseup', () => mouseButtonDown = false);
// Create objects to represent the current properties of the red and blue circle
const red = { x: 50, y: 50, radius: 20, color: "red" };
const blue = { x: 100, y: 100, radius: 10, color: "blue", angle: 0};
function drawCircle(ctx, {x, y, radius, color}) {
//--- Draw a circle to the specified canvas context, ctx = foreground or background
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI*2);
ctx.closePath();
ctx.fill();
}
function animate(){
if (mouseButtonDown) {
red.x += (blue.x - red.x) / 10;
red.y += (blue.y - red.y) / 10;
} else {
blue.angle += 0.1;
}
blue.x = red.x + 35 * Math.sin(blue.angle);
blue.y = red.y + 35 * Math.cos(blue.angle);
drawCircle(background, red); // Draw the red circle in the background (without clearing the existing circles)
foreground.clearRect(0, 0, foregroundCanvas.width, foregroundCanvas.height); // Clear the foreground
drawCircle(foreground, blue); // Draw the blue circle on the foreground
requestAnimationFrame(animate);
}
animate();
Either way, it's convenient to abstract out the circle drawing code into a function or method, and to store the properties of the two circles in objects.
As #Blindman67's answer notes, there may be a performance cost of stacking 2 canvases, and if that is an issue you may want to try drawing the background offscreen then copying it to the on-screen canvas.
If you're not opposed to just building a particle class you can do it using them. In the snippet below I have a Circle class and a Particles class to creat what you are trying to achieve. I currently have the particles max at 500 but you can change it or delete that line all together if you ne er want them gone.
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
let mouseButtonDown = false;
//the array holding particles
let particles = [];
//the counter is only needed it you want to slow down how fast particles are being pushed and dispolayed
let counter = 0;
document.addEventListener("mousedown", () => (mouseButtonDown = true));
document.addEventListener("mouseup", () => (mouseButtonDown = false));
//ES6 constructor class
class Circle {
//sets the basic structor of the object
constructor(r, c) {
this.x = 100;
this.y = 100;
this.x2 = 50;
this.y2 = 50;
this.r = r; //will be assigned the argument passed in through the constructor by each instance created later
this.color = c; //same as above. This allows each instance to have different parameters.
this.angle = 0;
}
//this function creates the red circle
drawRed() {
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2);
ctx.fill();
ctx.closePath();
}
//this function creates the blue circle
drawBlue() {
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.arc(this.x + this.x2, this.y + this.y2, this.r, 0, Math.PI * 2);
ctx.fill();
ctx.closePath();
}
//this function is where we'll place parameter that change our object
update() {
//makes the blue circle rotate
this.x2 = 35 * Math.sin(this.angle);
this.y2 = 35 * Math.cos(this.angle);
//mouse action is same as your code
if (mouseButtonDown) {
this.x += this.x2 / 20;
this.y += this.y2 / 20;
} else {
this.angle += 0.1;
}
}
}
//When using this type of constructor class you have to create an instance of it by calling new Object. You can create as money as you want.
let blueCircle = new Circle(10, "blue"); //passing in the radius and color in to the constructor
let redCircle = new Circle(20, "red");
//another class for the particles
class Particles {
constructor() {
this.x = redCircle.x;
this.y = redCircle.y;
this.r = redCircle.r;
this.color = redCircle.color;
}
draw() {
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.arc(this.x, this.y, this.r, 0, Math.PI * 2);
ctx.fill();
ctx.closePath();
}
}
//just wrapping all of the particle stuff into one function
function handleParticles() {
//while the mouse is held it will push particles
if (mouseButtonDown) {
particles.push(new Particles());
}
//this loops through the array and calls the draw() function for each particle
for (let i = 0; i < particles.length; i++) {
particles[i].draw();
}
//this keeps the array from getting too big.
if (particles.length > 500) {
particles.shift();
}
}
//wrap all functions into this one animate one and call requeatAnimationFrame
function animate() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
handleParticles();
//These must be called for each instance created of the object
blueCircle.drawBlue();
blueCircle.update();
redCircle.drawRed();
redCircle.update();
requestAnimationFrame(animate);
}
animate();
#canvas1{
position: absolute;
top:0;
left: 0;
width: 100%;
height: 100%;
}
<canvas id="canvas"></canvas>
I'd also like to add you can change the rate that the particles are drawn by adding a counter variable and then limiting the draw like counter % 10 == 0
EXAMPLE
add global variable let counter = 0;
then in the handleParticles function add this
function handleParticles() {
counter++
if (mouseButtonDown && counter % 10 == 0) {
particles.push(new Particles());
}
for (let i = 0; i < particles.length; i++) {
particles[i].draw();
}
if (particles.length > 500) {
particles.shift();
}
}
I want to create a break out game through javascript. I am wondering why the ctx.clearRect does not working. I want to put the rectangle in the y coordinate 430 to make it show at the bottom of the canvas. It moves when I have used the window.setInterval. But the rectangle move continuously.
Any help would be appreciated. Sorry for my poor English.
var canvas = document.getElementById("canvas");
const ctx = canvas.getContext('2d');
var position = 0;
var yposition = 430;
var length = 80;
var width = 20;
var xSpeed = length*1;
var ySpeed = 0;
function R(){
ctx.fillStyle = "green";
ctx.fillRect(position, yposition, length, width);
};
function C(){
position += xSpeed;
yposition += ySpeed;
};
window.setInterval(() => {
ctx.clearRect(0, 430, length, width);
R();
C();
},150);
ctx.beginPath();
ctx.arc(150, 50, 20, 0, Math.PI * 2, true);
ctx.closePath();
ctx.fillStyle = "blue";
ctx.fill();
The culprit are the parameters you're feeding into the clearRect function:
(0, 430, length, width)
Since length and width are hardcoded values of 80 and 20 respectively, the above means every time the intervals callback function gets fired it clears a rectangular area of 80 x 20 pixels at x = 0 and y = 430.
As your green paddle is moving you're actually clearing an area your paddle isn't located at anymore.
So you basically have two options:
Clear the whole canvas every frame
Clear the screen area your paddle has been before changing it's position
The second would look a little something like this:
var canvas = document.getElementById("canvas");
const ctx = canvas.getContext('2d');
var position = 0;
var yposition = 150;
var length = 80;
var width = 20;
var xSpeed = length * 1;
var ySpeed = 0;
function R() {
ctx.fillStyle = "green";
ctx.fillRect(position, yposition, length, width);
}
function C() {
position += xSpeed;
yposition += ySpeed;
}
window.setInterval(() => {
ctx.clearRect(position, yposition, length, width);
C();
R();
}, 500);
<canvas id="canvas" width="600" height="400"></canvas>
I'd definitely recommend clearing the whole canvas though since there will be other on-screen objects beside the paddle.
I have small question about my concept in photoshop:
I need to create html5/css/js adwords banner, where they will levitate background images (red pentagons) but also at the forefront of the entire banner, because in addition to those levitating pictures there will be other objects (texts, buttons).
It is possible to achive this, and also it is possible change path of moving these red pentagons base base on cursor position if user will move his cursor on over that banner?
Edit:
I found this project: https://codepen.io/VIRU/pen/FAdkl
Can be edited for flying pictures?
window.onload = function() {
//Create canvas and initialize it's context
var canvas = document.getElementById("flying-bubbles");
var ctx = canvas.getContext("2d");
//Set the dimensions of canvas equal to the window's dimensions
var W = window.innerWidth, H = window.innerHeight;
canvas.width = W;
canvas.height = H;
//Create an array of circles
var circles = [];
for(var i = 0; i < 20; i++ ){
circles.push(new create_circle());
}
//Function to create circles with different positions and velocities
function create_circle() {
//Random Position
this.x = Math.random()*W;
this.y = Math.random()*H;
//Random Velocities
this.vx = 0.1+Math.random()*1;
this.vy = -this.vx;
//Random Radius
this.r = 10 + Math.random()*50;
}
//Function to draw the background
function draw() {
//Create the gradient
var grad = ctx.createLinearGradient(0, 0, W, H);
grad.addColorStop(0, 'rgb(19, 105, 168)');
grad.addColorStop(1, 'rgb(0, 0, 0)');
//Fill the canvas with the gradient
ctx.globalCompositeOperation = "source-over";
ctx.fillStyle = grad;
ctx.fillRect(0,0,W,H);
//Fill the canvas with the circles
for(var j = 0; j < circles.length; j++) {
var c = circles[j];
//Draw the circle and it with the blur grad
ctx.beginPath();
ctx.globalCompositeOperation = "lighter";
ctx.fillStyle = grad;
ctx.arc(c.x, c.y, c.r, Math.PI*2, false);
ctx.fill();
//Lets use the velocity now
c.x += c.vx;
c.y += c.vy;
//To prevent the circles from moving out of the canvas
if(c.x < -50) c.x = W+50;
if(c.y < -50) c.y = H+50;
if(c.x > W+50) c.x = -50;
if(c.y > H+50) c.y = -50;
}
}
setInterval(draw, 25);
}
http://codepen.io/zimon/pen/KNmKpN
Try to change circles drawing to the image.
var img = new Image();
img.src= 'http://www.media3.net/images/redsquare.png';
ctx.drawImage(img,c.x,c.y);
I have a 2D canvas and drawing circle indefinitely one above the other.
Take this example : http://jsfiddle.net/umaar/fnMvf/
<html>
<head>
</head>
<body>
<canvas id="canvas1" width="500" height="500"></canvas>
</body>
</html>
JavaScript :
var currentEndAngle = 0
var currentStartAngle = 0;
var currentColor = 'black';
var lineRadius = 75;
var lineWidth = 15;
setInterval(draw, 50);
function draw() {
var can = document.getElementById('canvas1'); // GET LE CANVAS
var canvas = document.getElementById("canvas1");
var context = canvas.getContext("2d");
var x = canvas.width / 2;
var y = canvas.height / 2;
var radius;
var width;
var startAngle = currentStartAngle * Math.PI;
var endAngle = (currentEndAngle) * Math.PI;
currentStartAngle = currentEndAngle - 0.01;
currentEndAngle = currentEndAngle + 0.01;
if (Math.floor(currentStartAngle / 2) % 2) {
currentColor = "white";
radius = lineRadius - 1;
width = lineWidth + 3;
} else {
currentColor = "black";
radius = lineRadius;
width = lineWidth;
}
var counterClockwise = false;
context.beginPath();
context.arc(x, y, radius, startAngle, endAngle, counterClockwise);
context.lineWidth = width;
context.lineCap = "round";
// line color
context.strokeStyle = currentColor;
context.stroke();
}
Do I really need to clear canvas at some specific interval ?
How does canvas work in that case ? As it is '2D' context, does it still store previous data ? If yes, What should be approach to achieve smoothness for drawing circle keeping performance in mind ?
Canvas is a drawing surface. When you draw an element (e.g. call fill method), you are just changing the color of some pixels on the drawing surface. The canvas does not store any information about the element being drawn. In your example, there is no need to clear the canvas.