【问题标题】:HTML5 Canvas steam effect not displaying correctly without a canvas fillHTML5 Canvas 蒸汽效果在没有画布填充的情况下无法正确显示
【发布时间】:2016-10-15 11:28:39
【问题描述】:

我希望让 Canvas 蒸汽效果覆盖我的页面内容的其余部分,但如果我不给画布填充,它会以分色的方式显示蒸汽。

取消注释第 24 行和第 25 行将填满画布,并且 Steam 将按预期显示。

c.fillStyle = '#000';
c.fillRect(0,0,w,h);

下面是关于 sn-p 的完整演示。

var canvas = document.createElement('canvas');
var w = canvas.width = 800;
var h = canvas.height = 700;
var c = canvas.getContext('2d');
var img = new Image();
img.src = "http://wightfield.com/_temp/smoke_600-60.png";
var position = {x : 450, y : 410};
var mugPosition = {x : w/3, y : 500};

document.body.appendChild(canvas);

var particles = [];
var random = function(min, max){
  return Math.random()*(max-min)*min;
};

var draw = function(){
  position.x;
  position.y; 
  var p = new Particle(position.x, position.y);
  particles.push(p);
  while(particles.length > 500) particles.shift();
  
  //c.fillStyle = '#000';
  //c.fillRect(0,0,w,h);

  for(var i = 0; i < particles.length; i++)
  {
    particles[i].update();
  }
};
// generates the smoke particles
function Particle(x, y){
  this.x = x;
  this.y = y;
  this.velX = (random(1, 10)-5)/10;
  // distance of vertical travel
  this.velY = -9;
  this.size = random(3, 6)/10;
  this.alpha = 0.4;
  this.update = function(){
    this.y += this.velY;
    this.x += this.velX;
    this.velY *= 0.99;
    if(this.alpha < 0)
      this.alpha = 0;
    c.globalAlpha = this.alpha;
    c.save();
    c.translate(this.x, this.y);
    c.scale(this.size, this.size);
    
    c.drawImage(img, -img.width/2, -img.height/2);
    c.restore();
    this.alpha *= 0.90;
    this.size += 0.015;//
  };
}

setInterval(draw, 800/16);
body{
  background:green;
}
canvas {
  border: 1px dotted black;
}

没有画布填充颜色可以实现蒸汽效果吗?

【问题讨论】:

  • 使用ctx.clearRect(0,0,w,h)而不是fillRect,然后你就得到了你想要的透明画布。
  • 不知道这是在做什么,但它很管用!谢谢。
  • 我会给出一个答案,因为你有一些可以做的改变。

标签: canvas html5-canvas


【解决方案1】:

使用ctx.clearRect(0,0,w,h) 获得你想要的透明背景。

我也做了一些改动。

你每帧创建一个新粒子,随着时间的推移,这将导致大量的 GC。最好重置现有粒子,因此我为粒子添加了重置功能并添加新粒子,然后通过计数器重置它们。

您设置转换效率低下,因此我通过直接设置转换添加了一种更快的方法。现在您不必为每个粒子保存和恢复画布状态,这在许多机器/设备上可能会很慢。

我还检查了一个 alpha 值,该值太低,无法显示任何内容,也无法绘制图像,从而节省了一点时间

而不是使用setInterval,这只是一个错误,没有理由等待慢速机器成为痛苦。我添加了requestAnimationFrame,它将提供一个非常流畅的 60Fps,同步到屏幕刷新和浏览器渲染。

更新。

刚刚意识到可以减少粒子的数量以适应粒子 alpha 低于c = 1/255 的阈值所需的帧数(c 表示截止)。

你总是从a = 0.4开始alpha,衰减率是d = 0.9如果你把每一帧的步长看作时间t那么它可以表示为ctx.alpha = a * Math.pow(d ,t)

因此,如果我们想要直到 alpha 值低于 c 之前的帧数,我们需要为 t 求解 alpha = a*Math.pow(d,t)-c,即 t = Math.log(c/a)/Math.log(d)

衰减的结果是 44,因此浪费了 456 个数组条目。

更新 #2

我已经更新了答案以包括我之前错过的图像加载。您可以在下面的脚本中找到所有详细信息,因为我已经评论了我添加和更改的所有内容。

"use strict"; // this is a javascript directive and must be on the first line of the 
              // script (if included but is not a requirement). 
              // It forces a more code run under more stringent rules. The advantages
              // are many, including making the code run faster.

var imageLoadCount = 0;  // counts the number of image loading, counts down as they load
var readyToAnimate = false; // flag to indicate that resources are available
// image indexes in images array to get correct images in the animation.
const PARTICLE_IMAGE_INDEX = 0;
const BACKGROUND_IMAGE_INDEX = 1;
var images = []; // an array of images 
// What follows is a self evoking function, this will isolate the loading stuff from the
// rest of the script as it is only needed once at start so no point keeping references to it all
// the self invoking function is
//  (function(){...code body})()
// the () at the end forces javascript to run what is inside the () before it.
(function (){        
    function imageLoaded(){ // image onload function "this" is a reference to the image
        imageLoadCount -= 1; // count the loaded image
        // if the count is zero all images have loaded
        if(imageLoadCount === 0){
            readyToAnimate = true;
        }
    }
    // a list of image urls that need to be loaded. 
    const imageURLs = [
        "http://wightfield.com/_temp/smoke_600-60.png",
        "http://wightfield.com/_temp/smoke_600-60.png", // repeating the image just as example
    ];
    imageURLs.forEach(function(url){  // for each image url start the load process
        var img = new Image();
        img.src = url;
        img.onload = imageLoaded; // set the image onload function
        imageLoadCount += 1;  // count the number of images loading
        images.push(img); 
    });
})();  // run the function
var canvas = document.createElement('canvas');
var w = canvas.width = 800;
var h = canvas.height = 700;
var c = canvas.getContext('2d');   
document.body.appendChild(canvas);    

var position = {
    x : 450,
    y : 410
};
var mugPosition = {
    x : w / 3,
    y : 500
};


var particles = [];
var random = function (min, max) {
    // YOU had Math.random() * (max - min) * min; I assume you did not want to multiply by min
    return Math.random() * (max - min) + min;
};
var particleCount = 0;
const ALPHA_CUTOFF = 1/255;
const ALPHA_START = 0.4;
const ALPHA_DECAY = 0.9
// calculate the number of particles need to show each step of the alpha decay
const MAX_PARTICLES = Math.ceil(Math.log(ALPHA_CUTOFF / ALPHA_START) / Math.log(ALPHA_DECAY));
console.log(MAX_PARTICLES)

var draw = function () {
    var i;
    if(readyToAnimate){  // wait for the resources to load 
        c.setTransform(1,0,0,1,0,0); // reset transform         
        c.clearRect(0, 0, w, h);
        // If you want to render a background image do it here. If the image is the size of the
        // canvas then there is no need to clear the canvas and you can delete the line above
        /*  As an example
        c.drawImage(images[BACKGROUND_IMAGE_INDEX],0,0,w,h); // draws image filling the canvas
        */
        if (particles[particleCount % MAX_PARTICLES] === undefined) {
            particles[particleCount % MAX_PARTICLES] = new Particle(position.x, position.y);
        } else {
            particles[particleCount % MAX_PARTICLES].reset(position.x, position.y);
        }
        particleCount += 1;

        for (i = 0; i < particles.length; i++) {
            particles[i].update();
        }
    }else{
        // if you wanted you can add loading progress here
    }
    requestAnimationFrame(draw);
};


function Particle(x, y) {
    this.reset(x, y);
}
Particle.prototype = {
    reset : function (x, y) {
        this.x = x;
        this.y = y;
        this.velX = (random(1, 10) - 5) / 10;
        this.velY = -9;
        this.size = random(3, 6) / 10;
        this.alpha = ALPHA_START ;
        this.image = images[PARTICLE_IMAGE_INDEX];
    },
    update : function () {
        if(this.alpha >= ALPHA_CUTOFF ){  // no point in rendering a invisible sprite
            this.y += this.velY;
            this.x += this.velX;
            this.velY *= 0.99;            
            c.globalAlpha = this.alpha;
            c.setTransform(this.size,0,0,this.size,this.x, this.y);
            c.drawImage(this.image, -this.image.width / 2, -this.image.height / 2);
            this.alpha *= ALPHA_DECAY ;
            this.size += 0.015; //
        }
    }
}
// start the animation. Images may not have loaded yet
requestAnimationFrame(draw);
canvas {
  border: 1px dotted black;
}

【讨论】:

  • 感谢您提供如此全面的回答!它现在运行得更有效率。我唯一的疑问是如何在蒸汽效果后面添加另一个静态 PNG (pixabay.com/static/uploads/photo/2012/04/18/03/11/…) 而不通过将其添加到绘图函数来不断重绘它?
  • @PaulBenbow 如果你想要一个背景图像,你必须在渲染粒子之前每帧渲染它。我刚刚注意到您加载了图像,但不检查它是否已加载。在图像加载之前,您不应该开始动画,因为在设置 image.src 和图像准备就绪之间可能会有延迟。我将修改我的答案以说明如何做到这一点。
  • 感谢您的更新。当我显示背景图像并且不使用 c.clearRect(0, 0, w, h);我得到了最初的分色问题,如果我使用 clearRect ,背景会闪烁。有没有办法让背景保持静止?
  • @PaulBenbow 后台闪烁????这很奇怪.. 哦,我知道它是什么,我在你刚刚清理画布之前把它忽略了,但现在你有了一个图像,它将使用最后一个粒子 alpha 进行渲染。在 c.setTransform(.. 行之后的绘图函数中,在绘制背景图像之前添加 c.globalAlpha = 1; 这将停止闪烁
  • 这是一种享受。谢谢大家的帮助,我学到了很多!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-06-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-09
  • 2014-10-11
相关资源
最近更新 更多