简体   繁体   中英

How to animate several dots moving here and there in html canvas

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 :)

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(cx + cr, cy); 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(cx + cr, cy); ctx.arc(cx, cy, cr, 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.

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>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM