简体   繁体   中英

Animating opacity in HTML5 Canvas

I'm trying to recreate this effect , but I want this to be in canvas, basically drawing squares with fadein effect using a loop. The loop part is ok, what I cannot figure out is the fade in effect. I'm using requestAnimationFrame, with each repaint the globalAlpha is incremented, the old sqaure is deleted and a new one drawn. This is the function:

function renderSquare(x, y) {

requestID = requestAnimationFrame(renderSquare);
alpha = requestID/100;  

ctx.clearRect(x,y,size,size);
ctx.globalAlpha = alpha;

var colour = "rgb(58,150,270)";
ctx.fillStyle = colour;
ctx.fillRect(x, y, size, size);


console.log("ID: " + requestID);  
console.log("Alpha: " + alpha);

if(alpha == 1) {
    cancelAnimationFrame(requestID);
}   

};

function drawSquare(x,y) {
  requestAnimationFrame(function render(){
    renderSquare(x,y);          
  });   
}

But I just can't get it to work. Here is a codepen with the whole thing.

http://codepen.io/easynowbaby/pen/GJKOej?editors=001

Ultimately, I want to be able to use the function in the loopSquares function. Any help is very appreciated. Cheers!

EDIT: I should have made myself clearer. I don't want to recreate the gallery with images, I'm only interested in the cascading fade in effect of squares. I want to achieve this effect in canvas where I'm gonna fade in little squares using fillRect function.

Problems

The first thing to point out here is how you use the requestID to set the alpha. From MDN (my emphasis):

A long integer value, the request id, that uniquely identifies the entry in the callback list. This is a non-zero value, but you may not make any other assumptions about its value . You can pass this value to window.cancelAnimationFrame() to cancel the refresh callback request.

In other words, don't assume this will keep a running value equivalent to current index of the cell. It may do so in one browser, accidentally, but no in another. Keep track of this value by other means.

Second, the globalAlpha works on the entire context for anything drawn next to it. This means you need to track current alpha per square, or use a color style rgba which allow you to set alpha per style. This doesn't matter so much though as you need to track alpha also here.

One solution

I would suggest using an object for this, a square-monkey which can be trained to set its alpha correctly, then duplicated across a grid.

Example

Main object will keep track of all settings such as current alpha, how much to update, what color and so forth. It's of course not limited to these alone - you could add scale, rotation etc. to it as well, but here I'll only show for alpha:

// Square-monkey object
function Rectangle(ctx, x, y, w, h, color, speed) {

  this.ctx = ctx;
  this.x = x;
  this.y = y;
  this.height = h;
  this.width = w;
  this.color = color;

  this.alpha = 0;                        // current alpha for this instance
  this.speed = speed;                    // increment for alpha per frame
  this.triggered = false;                // is running
  this.done = false;                     // has finished
}

To save some memory and increase performance we can use prototypes for the common functions/methods:

Rectangle.prototype = {

  trigger: function() {                  // start this rectangle
    this.triggered = true
  },

  update: function() {
    if (this.triggered && !this.done) {  // only if active
      this.alpha += this.speed;          // update alpha
      this.done = (this.alpha >= 1);     // update status
    }

    this.ctx.fillStyle = this.color;           // render this instance
    this.ctx.alpha = Math.min(1, this.alpha);  // clamp value
    this.ctx.fillRect(this.x, this.y, this.width, this.height);
  }  
};

What we need to do now is to define a grid and cell size:

var cols = 10,
    rows = 6,
    cellWidth = canvas.width / cols,
    cellHeight = canvas.height /rows;

Now we can populate the grid with an object per cell:

var grid = [],
    len = cols * rows,
    y = 0, x;

for(; y < rows; y++) {
  for(x = 0; x < cols; x++) {
    grid.push(new Rectangle(ctx, x * cellWidth, y * cellHeight,
                            cellWidth, cellHeight, "#79f", 0.25));
  }
}

And finally we need to trigger these square in any fashion we like, here just a diagonal cascading way. Lets also keep track of if all are done and not:

function loop() {
  ctx.globalAlpha = 1;           // reset alpha
  ctx.clearRect(0, 0, w, h);     // clear canvas

  // trigger cells
  for(y = 0; y < rows; y++) {    // iterate each row
    var gx = (x|0) - y;          // calc an x for current row
    if (gx >= 0 && gx < cols) {  // within limit?
      index = y * cols + gx;     // calc index
      grid[index].trigger();     // trigger this cell's animation if not running
    }
  }

  x += 0.25;                     // how fast to cascade

  hasActive = false;             // enable ending the animation when all done

  // update/render all cells
  for(var i = 0; i < grid.length; i++) {
    grid[i].update();
    if (!grid[i].done) hasActive = true;      // anyone active?
  }

  if (hasActive) requestAnimationFrame(loop); // animate if anyone's active
}

Live example

 var canvas = document.querySelector("canvas"), ctx = canvas.getContext("2d"), w = canvas.width, h = canvas.height; // Square-monkey object function Rectangle(ctx, x, y, w, h, color, speed) { this.ctx = ctx; this.x = x; this.y = y; this.height = h; this.width = w; this.color = color; this.alpha = 0; // current alpha for this instance this.speed = speed; // increment for alpha per frame this.triggered = false; // is running this.done = false; // has finished } // prototype methods that will be shared Rectangle.prototype = { trigger: function() { // start this rectangle this.triggered = true }, update: function() { if (this.triggered && !this.done) { // only if active this.alpha += this.speed; // update alpha this.done = (this.alpha >= 1); // update status } this.ctx.fillStyle = this.color; // render this instance this.ctx.globalAlpha = Math.min(1, this.alpha); this.ctx.fillRect(this.x, this.y, this.width, this.height); } }; // Populate grid var cols = 10, rows = 6, cellWidth = canvas.width / cols, cellHeight = canvas.height /rows, grid = [], len = cols * rows, y = 0, x; for(; y < rows; y++) { for(x = 0; x < cols; x++) { grid.push(new Rectangle(ctx, x * cellWidth, y * cellHeight, cellWidth, cellHeight, "#79f", 0.025)); } } var index, hasActive = true; x = 0; function loop() { ctx.globalAlpha = 1; ctx.clearRect(0, 0, w, h); // trigger cells for(y = 0; y < rows; y++) { var gx = (x|0) - y; if (gx >= 0 && gx < cols) { index = y * cols + gx; grid[index].trigger(); } } x += 0.25; hasActive = false; // update all for(var i = 0; i < grid.length; i++) { grid[i].update(); if (!grid[i].done) hasActive = true; } if (hasActive) requestAnimationFrame(loop) } loop(); 
 <canvas width=500 height=300></canvas> 

Live example extended object

By using a single object you can maintain code in a single place. For the fun of it, lets add rotation and scale as well:

 var canvas = document.querySelector("canvas"), ctx = canvas.getContext("2d"), w = canvas.width, h = canvas.height; // Square-monkey object function Rectangle(ctx, x, y, w, h, color, speed) { this.ctx = ctx; this.x = x; this.y = y; this.height = h; this.width = w; this.color = color; this.alpha = 0; // current alpha for this instance this.speed = speed; // increment for alpha per frame this.triggered = false; // is running this.done = false; // has finished } // prototype methods that will be shared Rectangle.prototype = { trigger: function() { // start this rectangle this.triggered = true }, update: function() { if (this.triggered && !this.done) { // only if active this.alpha += this.speed; // update alpha this.done = (this.alpha >= 1); // update status } this.ctx.fillStyle = this.color; // render this instance this.ctx.globalAlpha = Math.min(1, this.alpha); var t = this.ctx.globalAlpha, // use current alpha as t cx = this.x + this.width * 0.5, // center position cy = this.y + this.width * 0.5; this.ctx.setTransform(t, 0, 0, t, cx, cy); // scale and translate this.ctx.rotate(0.5 * Math.PI * (1 - t)); // rotate, 90° <- alpha this.ctx.translate(-cx, -cy); // translate back this.ctx.fillRect(this.x, this.y, this.width, this.height); } }; // Populate grid var cols = 20, rows = 12, cellWidth = canvas.width / cols, cellHeight = canvas.height /rows, grid = [], len = cols * rows, y = 0, x; for(; y < rows; y++) { for(x = 0; x < cols; x++) { grid.push(new Rectangle(ctx, x * cellWidth, y * cellHeight, cellWidth, cellHeight, "#79f", 0.02)); } } var index, hasActive = true; x = 0; function loop() { ctx.setTransform(1,0,0,1,0,0); ctx.globalAlpha = 1; ctx.clearRect(0, 0, w, h); // trigger cells for(y = 0; y < rows; y++) { var gx = (x|0) - y; if (gx >= 0 && gx < cols) { index = y * cols + gx; grid[index].trigger(); } } x += 0.333; hasActive = false; // update all for(var i = 0; i < grid.length; i++) { grid[i].update(); if (!grid[i].done) hasActive = true; } if (hasActive) requestAnimationFrame(loop) } loop(); 
 <canvas width=500 height=300></canvas> 

This is not a canvas nor a canvas effect.

Those are simple image tags animated by size and opacity. Just drag any of the images to the URL bar and you'll see the full-resolution image opens. This is so because they are DOM elements. If this was a canvas you couldn't possibly drag them to the URL bar.

All you need to do is animate one image tag properly (size and opacity). Both are really simple and well-explained for jQuery. Next, combine that effect in an array with a delay betweem them and you are on.

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