I have a requirement to move many dots here and there inside a canvas.
Hence I created several arcs with different radius and placed them at random places.
var context = document.getElementById('stage').getContext('2d');
var radian = Math.PI / 180;
var x = 40;
var y = 40;
var r = 20;
var colorPoints = [];
var frames = 50;
var currentFrame = 0;
var toggle = false;
var iconsLoaded = false;
context.beginPath();
context.arc(x,y, r, 0 * radian, 360 * radian, false)
context.fill();
var drawMultipleCurves = function(ctx){
if(!iconsLoaded){
for (let i = 0; i < 600; i ) {
ctx.beginPath();
ctx.filter = 'blur(5px)';
ctx.fillStyle = '#B835FF';
colorPoints.push({x: Math.floor((Math.random() * 700) 0), xMove: Math.floor((Math.random() * 2) 0) , yMove: Math.floor((Math.random() * 2) 0) , y: Math.floor((Math.random() * 700) 0), radius: Math.floor((Math.random() * 20) 5)});
ctx.arc(colorPoints[colorPoints.length - 1].x, colorPoints[colorPoints.length - 1].y, colorPoints[colorPoints.length - 1].radius, 0 * radian, 360 * radian, false);
ctx.fill();
ctx.closePath();
iconsLoaded = true;
}
}
else{
for(let i =0;i< colorPoints.length; i ){
if(frames === currentFrame ){
toggle = !toggle;
currentFrame = 0;
}
if(!toggle){
colorPoints[i].xMove === 1 ? colorPoints[i].x = colorPoints[i].x 5 : colorPoints[i].x = colorPoints[i].x - 5;
colorPoints[i].yMove === 1 ? colorPoints[i].y = colorPoints[i].y 5 : colorPoints[i].y = colorPoints[i].y - 5;
}
else{
colorPoints[i].xMove === 1 ? colorPoints[i].x = colorPoints[i].x - 5 : colorPoints[i].x = colorPoints[i].x 5;
colorPoints[i].yMove === 1 ? colorPoints[i].y = colorPoints[i].y - 5 : colorPoints[i].y = colorPoints[i].y 5;
}
ctx.beginPath();
ctx.arc(colorPoints[i].x, colorPoints[i].y, colorPoints[i].radius, 0 * radian, 360 * radian, false);
context.closePath( );
ctx.fill();
currentFrame = currentFrame 1;
}
}
}
var animate = function(){
setTimeout(()=>{
context.clearRect(0,0,400,400);
context.beginPath();
drawMultipleCurves(context);
context.fill();
requestAnimationFrame(animate)
}, 1000/30)
}
requestAnimationFrame(animate)
<canvas id="stage" width="400" height="400">
<p>Your browser doesn't support canvas.</p>
</canvas>
Above is the code that I have tried. I have first created and placed several dots at random places with random radius. When I created them I saved all these random places in an array 'colorPoints'
Now I'm looping into this array and moving all the dots everytime 'requestAnimation' is called.
I'm able to achieve my animation of moving the dots randomly but as I have used 800 dots and then saving them into an array and then again looping them to move their position, the animation is not looking smooth.
It looks like it is moving and strucking. How can I achieve this animation smoothly?
Thanks in advance :)
CodePudding user response:
The CanvasRenderingContext2D
blur filter is quite heavy - especially if you use it on a canvas consisting of 600 circles. That means on every screen update it has to re-draw 600 circles and apply a blur filter afterwards.
The usual approach is a little different. Initially you create a master texture with a blurred circle. This texture can then be re-used and drawn onto the canvas using the drawImage()
method. To vary the size of the circles there is no radius
anymore though. We can get the same effect by using a scale
instead.
Here's an example:
var context = document.getElementById('stage').getContext('2d');
var radian = Math.PI / 180;
var x = 40;
var y = 40;
var r = 20;
var colorPoints = [];
var frames = 50;
var currentFrame = 0;
var toggle = false;
var iconsLoaded = false;
var texture = document.createElement("canvas");
var textureContext = texture.getContext("2d");
texture.width = 80;
texture.height = 80;
textureContext.filter = 'blur(5px)';
textureContext.fillStyle = '#B835FF';
textureContext.arc(texture.width / 2, texture.height / 2, 25, 0 * radian, 360 * radian, false);
textureContext.fill();
textureContext.closePath();
var drawMultipleCurves = function(ctx) {
if (!iconsLoaded) {
for (let i = 0; i < 600; i ) {
colorPoints.push({
x: Math.floor((Math.random() * 700) 0),
xMove: Math.floor((Math.random() * 2) 0),
yMove: Math.floor((Math.random() * 2) 0),
y: Math.floor((Math.random() * 700) 0),
scale: 0.2 Math.random() * 0.8
});
iconsLoaded = true;
}
} else {
for (let i = 0; i < colorPoints.length; i ) {
if (frames === currentFrame) {
toggle = !toggle;
currentFrame = 0;
}
if (!toggle) {
colorPoints[i].xMove === 1 ? colorPoints[i].x = colorPoints[i].x 5 : colorPoints[i].x = colorPoints[i].x - 5;
colorPoints[i].yMove === 1 ? colorPoints[i].y = colorPoints[i].y 5 : colorPoints[i].y = colorPoints[i].y - 5;
} else {
colorPoints[i].xMove === 1 ? colorPoints[i].x = colorPoints[i].x - 5 : colorPoints[i].x = colorPoints[i].x 5;
colorPoints[i].yMove === 1 ? colorPoints[i].y = colorPoints[i].y - 5 : colorPoints[i].y = colorPoints[i].y 5;
}
ctx.drawImage(texture, colorPoints[i].x, colorPoints[i].y, texture.width * colorPoints[i].scale, texture.height * colorPoints[i].scale);
currentFrame = currentFrame 1;
}
}
}
var animate = function() {
setTimeout(() => {
context.clearRect(0, 0, 400, 400);
context.beginPath();
drawMultipleCurves(context);
context.fill();
requestAnimationFrame(animate)
})
}
requestAnimationFrame(animate)
<canvas id="stage" width="400" height="400">
<p>Your browser doesn't support canvas.</p>
</canvas>
CodePudding user response:
Render "fill" once per style
Your code is slowing down due to where you placed fill
(same if you use stroke
)
When you have many objects with the same style call fill
only once per frame for each object.
You had something like
for (const c of circles) {
ctx.beginPath();
ctx.arc(c.x, c.y, c.r, 0, TAU)
ctx.fill();
}
With a filter active the fill command forces the filter to be reset, which for blur is complex.
Rather add all the arcs then fill.
ctx.beginPath();
for (const c of circles) {
ctx.moveTo(c.x c.r, c.y);
ctx.arc(c.x, c.y, c.r, 0, TAU)
}
ctx.fill();
The move ctx.moveTo(c.x c.r, c.y);
is used to close the previous arc.
You can also close the arc with ctx.closePath
but this can be a lot slower when you have many arcs in the path buffer.
// slower than using moveTo
ctx.beginPath();
for (const c of circles) {
ctx.arc(c.x, c.y, c.r, 0, TAU)
ctx.closePath();
}
ctx.fill();
Example
Example draws 600 arcs using the blur filter as it only calls fill
once per frame. This should run smooth on all but the most low end devices.
See function drawCircles
requestAnimationFrame(animate);
const ctx = canvas.getContext('2d');
const W = canvas.width;
const BLUR = 5;
const CIRCLE_COUNT = 600;
const MIN_RADIUS = BLUR;
const MAX_RADIUS = 30;
const MAX_DELTA = 1;
const MAX_CIR_R = MAX_RADIUS BLUR;
const MOVE_SIZE = MAX_CIR_R * 2 W;
const TAU = 2 * Math.PI;
const setOf = (c, cb, i = 0, a = []) => { while(i < c) { a.push(cb(i )) } return a };
const rnd = (m, M) => Math.random() * (M - m) m;
const style = {
filter: "blur(" BLUR "px)",
fillStyle: '#B835FF',
};
var currentStyle;
function setStyle(ctx, style) {
if (currentStyle !== style) {
Object.assign(ctx, style);
currentStyle = style;
}
}
const circle = {
get x() { return rnd(-MAX_CIR_R, W MAX_CIR_R) },
get y() { return rnd(-MAX_CIR_R, W MAX_CIR_R) },
get dx() { return rnd(-MAX_DELTA, MAX_DELTA) },
get dy() { return rnd(-MAX_DELTA, MAX_DELTA) },
get r() { return rnd(MIN_RADIUS, MAX_RADIUS) },
move() {
var x = this.x this.dx MOVE_SIZE MAX_CIR_R;
var y = this.y this.dy MOVE_SIZE MAX_CIR_R;
this.x = x % MOVE_SIZE - MAX_CIR_R;
this.y = y % MOVE_SIZE - MAX_CIR_R;
}
};
const circles = setOf(CIRCLE_COUNT, () => Object.assign({}, circle));
function drawCircles(circles, ctx, style) {
setStyle(ctx, style);
ctx.beginPath();
for (const c of circles) {
ctx.moveTo(c.x c.r, c.y);
ctx.arc(c.x, c.y, c.r, 0, TAU);
}
ctx.fill();
}
function updateCircles(circles) {
for (const c of circles) { c.move(); }
}
function animate() {
ctx.clearRect(0,0,W, W);
updateCircles(circles);
drawCircles(circles, ctx, style);
requestAnimationFrame(animate);
}
<canvas id="canvas" width="600" height="600"> </canvas>
If you have several colors, group all the same colors so you can keep the number of fill calls as low as possible.
There are many ways to get the same effect with many colors (each circle a different color) but will need more setup code.