html 5 canvas javascript rain animation(how to imp

2019-03-19 14:16发布

问题:

I tried both of these in canvas and nothing showed, also I doubt it is even efficient :/. I am trying to make rain that comes down the screen.. Wondering what is the most efficient way of doing this. I am a beginner at animation and would really appreciate help.

I suspect that creating a rain object would be best, each with the quality of coming down the screen then coming to the top and then an array with them...maybe with random x values withing the canvas width and y values of 0 but I don't know how to implement that. Please help!

                xofRain = 20;
        startY = 0;
        ctx.beginPath();
        ctx.moveTo(xofRain, startY);
        ctx.lineTo(xofRain, startY + 20);
        ctx.closePath();
        ctx.fillStyle = "black"; 
        ctx.fill();


     function rain(xofRain){

        startY = canvas.height();

        ctx.moveTo(xofRain, startY);
        ctx.beginPath();
        ctx.lineTo(xofRain, startY + 3);
        ctx.closePath();
        ctx.fillStyle = "blue"; 
        ctx.fill();
    }

回答1:

Here comes your answer, this snow rain is created using pure HTML5 Canvas, the technique used to achieve this animation is called "Double Buffer Animation". First it is good to know what is Double Buffer animation technique.

Double Buffer Technique: This is an advanced technique to make animation clear and with less flickers in it. In this technique 2 Canvas is used, one is displayed on webpage to show the result and second one is used to create animation screens in backed process.

How this will help full, suppose we have to create a animation with very high number of move, as in our Snow Fall example, there are number of Flakes are moving with there own speed, so keep them moving, we have to change position of each flake and update it on the canvas, this is quite heavy process to deal with.

So Now instead of updating each Flake directly on our page canvas, we will create a buffer Canvas, where all these changes take place and we just capture a Picture from Buffer canvas after 30ms and display it on our real canvas.

This way our animation will be clear and without flickers. So here is a live example of it.

http://aspspider.info/erishaan8/html5rain/

Here is the code of it:

    <!DOCTYPE html>
    <html>
    <head>
    <meta charset=utf-8 />
    <title>HTML5 Rain</title>
    <!--[if IE]>
      <script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
    <![endif]-->
    <style>
      article, aside, figure, footer, header, hgroup, 
      menu, nav, section { display: block; }
    </style>
    <script type="text/javascript">
        var canvas = null;
        var context = null;
        var bufferCanvas = null;
        var bufferCanvasCtx = null;
        var flakeArray = [];
        var flakeTimer = null;
        var maxFlakes = 200; // Here you may set max flackes to be created 
    
        function init() {
            //Canvas on Page
            canvas = document.getElementById('canvasRain');
            context = canvas.getContext("2d");
            //Buffer Canvas
            bufferCanvas = document.createElement("canvas");
            bufferCanvasCtx = bufferCanvas.getContext("2d");
            bufferCanvasCtx.canvas.width = context.canvas.width;
            bufferCanvasCtx.canvas.height = context.canvas.height;
    
            
            flakeTimer = setInterval(addFlake, 200);
    
            Draw();
    
            setInterval(animate, 30);
             
        }
        function animate() {
            
            Update();
            Draw();
            
        }
        function addFlake() {
    
            flakeArray[flakeArray.length] = new Flake();
            if (flakeArray.length == maxFlakes)
                clearInterval(flakeTimer);
        }
        function blank() {
            bufferCanvasCtx.fillStyle = "rgba(0,0,0,0.8)";
            bufferCanvasCtx.fillRect(0, 0, bufferCanvasCtx.canvas.width, bufferCanvasCtx.canvas.height);
            
        }
        function Update() {
            for (var i = 0; i < flakeArray.length; i++) {
                if (flakeArray[i].y < context.canvas.height) {
                    flakeArray[i].y += flakeArray[i].speed;
                    if (flakeArray[i].y > context.canvas.height)
                        flakeArray[i].y = -5;
                    flakeArray[i].x += flakeArray[i].drift;
                    if (flakeArray[i].x > context.canvas.width)
                        flakeArray[i].x = 0;
                }
            }
            
        }
        function Flake() {
            this.x = Math.round(Math.random() * context.canvas.width);
            this.y = -10;
            this.drift = Math.random();
            this.speed = Math.round(Math.random() * 5) + 1;
            this.width = (Math.random() * 3) + 2;
            this.height = this.width;
        }
        function Draw() {
            context.save();
            
            blank();
    
            for (var i = 0; i < flakeArray.length; i++) {
                bufferCanvasCtx.fillStyle = "white";
                bufferCanvasCtx.fillRect(flakeArray[i].x, flakeArray[i].y, flakeArray[i].width, flakeArray[i].height);
            }
    
            
            context.drawImage(bufferCanvas, 0, 0, bufferCanvas.width, bufferCanvas.height);
            context.restore();
        }
      
    </script>
    </head>
    <body onload="init()">
      <canvas  id="canvasRain" width="800px" height="800px">Canvas Not Supported</canvas>
    </body>
    </html>

Also if you find this help full, accept as Answer and make it up. o_O

Cheers!!!



回答2:

I'm not sure what "most efficient" is. If it was me I'd do it in WebGL but whether or not that's efficient is not clear to me.

In either case I'd try to use a stateless formula. Creating and updating state for every raindrop is arguably slow.

const ctx = document.querySelector("canvas").getContext("2d");
const numRain = 200;

function render(time) {
  time *= 0.001;  // convert to seconds
  
  resizeCanvasToDisplaySize(ctx.canvas);
  
  const width = ctx.canvas.width;
  const height = ctx.canvas.height;
  ctx.fillStyle = "black";
  ctx.fillRect(0, 0, width, height);  
  
  resetPseudoRandom();
  
  const speed = time * 500;
  ctx.fillStyle = "#68F";
  for (let i = 0; i < numRain; ++i) {
    const x = pseudoRandomInt(width);
    const y = (pseudoRandomInt(height) + speed) % height;
    ctx.fillRect(x, y, 3, 8);
  }
  
  requestAnimationFrame(render);
}

requestAnimationFrame(render);

let randomSeed_ = 0;
const RANDOM_RANGE_ = Math.pow(2, 32);

function pseudoRandom() {
  return (randomSeed_ =
          (134775813 * randomSeed_ + 1) %
          RANDOM_RANGE_) / RANDOM_RANGE_;
};

function resetPseudoRandom() {
  randomSeed_ = 0;
};

function pseudoRandomInt(n) {
  return pseudoRandom() * n | 0;
}

function resizeCanvasToDisplaySize(canvas) {
  const width = canvas.clientWidth;
  const height = canvas.clientHeight;
  if (canvas.width !== width || canvas.height !== height) {
    canvas.width = width;
    canvas.height = height;
  }
}
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
<canvas></canvas>

Note that I could have used ctx.moveTo(x, y); ctx.lineTo(x, y + 8); for each line and then at the end of the loop called ctx.stroke(). I didn't do that because I'm assuming it would be less efficient than using ctx.fillRect. In order for the canvas to draw lines it actually has to allocate a dynamic path (you call ctx.beginPath). It then has to record all the lines you add. Then it has to expand those lines into vertices of various kinds to rasterize the lines. You can basically see the various algorithms it uses here. Conversely none of that has to happen with ctx.fillRect. No allocations have to happen (not saying they don't happen, just saying they don't have to). The canvas can just use a single pre-allocated quad and draw it on the GPU by passing the correct matrix to draw whatever rectangle you ask of it. Of course they're might be more overhead calling ctx.fillRect 200 times rather than ctx.moveTo, ctx.lineTo 200s + ctx.stroke once but really that's up to the browser.

The rain above may or may not be a good enough rain effect. That wasn't my point in posting really. The point is efficiency. Pretty much all games that have some kind of rain effect do some kind of stateless formula for their rain. A different formula would generate different or less repetitive rain. The point is it being stateless.