【问题标题】:Make clearRect() of canvas work faster使画布的 clearRect() 工作更快
【发布时间】:2015-02-01 23:04:01
【问题描述】:

我正在尝试用 JavaScript 设计一个行进的正弦波,但设计看起来很慢。主要瓶颈是用于画布清除的clearRect()

我该如何解决这个问题?

我也是通过ctx.fillRect(x, y,1,1) 绘制像素,但是当我使用clearRect(x, y,1,1) 清除时,它会留下一些脚印。相反,我必须做clearRect(x, y,5,5) 才能得到适当的清理。可以解决什么问题?

/******************************/

var x = 0;
var sineval = [];
var offset = 0;
var animFlag;

function init() {

    for(var i=0; i<=1000; ++i){
        sineval[i] = Math.sin(i*Math.PI/180);   
    }
    // Call the sineWave() function repeatedly every 1 microseconds
    animFlag = setInterval(sineWave, 1);
    //sineWave();
}


function sineWave()
{   //console.log('Drawing Sine');

    var canvas = document.getElementById("canvas");

    if (canvas.getContext) {
        var ctx = canvas.getContext("2d");
    }

    for(x=0 ; x<1000 ;++x){

        // Find the sine of the angle
        //var i = x % 361;
        var y = sineval[x+offset];

        // If the sine value is positive, map it above y = 100 and change the colour to blue
        if(y >= 0)
        {
            y = 100 - (y-0) * 70;
            ctx.fillStyle = "green";
        }

        // If the sine value is negative, map it below y = 100 and change the colour to red
        if( y < 0 )
        {
            y = 100 + (0-y) * 70;
            ctx.fillStyle = "green";
        }

        // We will use the fillRect method to draw the actual wave. The length and breath of the
        if(x == 0) ctx.clearRect(0,y-1,5,5);
        else ctx.clearRect(x,y,5,5);
        ctx.fillRect(x, y,1,1 /*Math.sin(x * Math.PI/180) * 5, Math.sin(x * Math.PI/180 * 5)*/);

    }

    offset = (offset > 360) ? 0 : ++offset ;
}

【问题讨论】:

  • 你为什么要清除单点?只需在每次重绘之前清除整个画布即可。
  • 我确实尝试过,但它会产生闪烁效果。
  • 正如Bergi 所说,清除整个画布。另外,不要将计时器设置为低于 16 毫秒(您的是 1 毫秒),因为浏览器刷新显示的速度不会超过 16 毫秒。由于您的正弦波是不变的,因此请考虑预渲染正弦波的图像,并通过在每一帧期间更改其 X 坐标来在画布上“动画化”该图像。
  • 正弦波是不同的......它具有幅度和频率控制
  • 请注意,绿色、红色注释未在代码中实现。 y 的两个公式是相同的,每个只是 y=-70y+100。正如其他人所说,不是将 [-1,1] 范围正弦数据的大小调整为每帧 [30,170],只需在初始化时执行此操作并将扩大的范围保存在正弦波数组中。直接操作像素,而不是使用填充和清除。在初始化时使用 context.getImageData 或 context.createImageData (pick) 一次,然后使用帧所需的像素值操作返回对象的 .data 数组。在帧结束时使用 context.putImageData。

标签: javascript html canvas


【解决方案1】:

一些加速和奇怪的结局:

  • 在init中,设置一次正弦波像素值。

  • 对这些使用类型化数组,因为如果可能的话,坚持使用整数比使用浮点数更快。

  • 我们将直接操作像素数据,而不是使用填充和清除。首先,在 init 中我们调用 ctx.getImageData 一次。我们也只是将所有像素的 alpha 值设置为最大一次,因为默认的 0 值是透明的,我们希望完全不透明度为 255。

  • 像以前一样使用 setInterval。我们希望以稳定的速率更新像素。

  • 使用“adj”作为旋钮来调整正弦波在屏幕上移动的速度。实际值(小数)将取决于绘图帧速率。我们使用 Date.now() 调用来跟踪跨帧消耗的毫秒数。所以对毫秒的调整是 mod 360 来设置'offset'变量。因此偏移值不是每帧增加 1,而是根据时间消耗来决定。如果需要,可以稍后将 adj 值连接到 gui。

  • 在工作结束时(在 sineWave 函数中),我们调用 requestAnimationFrame 只是为了对画布执行 ctx.putImageData,同步屏幕以避免撕裂。请注意,“paintit”功能既快速又简单。另请注意,我们仍然需要 setInterval 来保持稳定的步伐。

  • 在设置偏移量和调用 requestAnimationFrame 之间,我们做了两个循环。第一个有效地使我们从前一帧(设置为 0)中绘制的确切像素变黑。第二个循环绘制新的正弦波。波的上半部分为绿色(将像素 rgba 中的 G 设置为 255)。下半部分为红色(将 R 像素 rgba 设置为 255)。

  • 使用 .data 数组绘制像素,并使用 4x + 4y*canvas.width 将其索引到像素。如果想要绿色值而不是红色值,则再添加 1 个。无需触摸蓝色值(字节偏移 2)或已设置的 alpha(字节偏移 3)。

  • 在某些地方使用的 >>>0 会将受影响的值转换为无符号整数,如果它还没有的话。它也可以用来代替 Math.ceil。我认为 .data 已经是 Array 类型了。

  • 这个答案比较晚,但它解决了 cmets 中提出的一些问题或尚未解决的问题。谷歌搜索时出现了这个问题。

  • 尚未分析代码。有可能一些加速没有加速任何东西;然而,Firefox 的 cpu 消耗在调整结束时相当轻。它设置为以 40 fps 运行。使“延迟”更小,以加快速度并增加 CPU 负担。

var sineval;
var offset = 0;
var animFlag;
var canvas;
var ctx;
var obj;
var milli;
var delay=25;
var adj=1/delay;  // .04 or so for 25 delay

function init() {
    canvas = document.getElementById("canvas");
    ctx = canvas.getContext("2d");
    obj=ctx.getImageData(0,0,canvas.width,canvas.height);

    for (let i=0; i<obj.data.length; i+=4) {
        obj.data[i+3]=255;  //set all alpha to full one time only needed.
    }

    sineval=new Uint8Array(1400);  //set up byte based table of final pixel sine values.. 1400 degrees total
    for (let i=0; i<=1400; ++i) {  //1400
        sineval[i] = (100-70*Math.sin(i*Math.PI/180))>>>0;   
    }

    animFlag = setInterval(sineWave, delay);  //do processing once every 25 milli

    milli=Date.now()>>>0;   //start time in milli
}

function sineWave() {
    let m=((Date.now()-milli)*adj)>>>0;
    let oldoff = offset;
    offset=(m % 360)>>>0;     //offset,frequency tuned with adj param.
    
    for(x=0 ; x<1000 ;++x) {  //draw sine wave across canvas length of 1000
        let y=sineval[x+oldoff];
        obj.data [0+x*4+y*4*canvas.width]=0; //black the reds
        obj.data [1+x*4+y*4*canvas.width]=0; //black the greens
    }

    for(x=0 ; x<1000 ;++x) {  //draw sine wave across canvas length of 1000
        let y=sineval[x+offset];
        if (y<100) {
            obj.data [1+x*4+y*4*canvas.width]=255; //rGba  //green for top half
        } else {
            obj.data [0+x*4+y*4*canvas.width]=255; //Rgba  //red for bottom half
        }
    }

    requestAnimationFrame(paintit);   //at end of processing try to paint next frame boundary
}

function paintit() {
    ctx.putImageData(obj,0,0);
}

init();
&lt;canvas id="canvas" height=300 width=1000&gt;&lt;/canvas&gt;

【讨论】:

    【解决方案2】:

    你需要稍微重构一下代码:

    • 将所有全局变量(例如画布和上下文)移出循环函数
    • 在循环内部,在开始时清除整个画布,重绘正弦
    • 使用requestAnimationFrame 而不是setInterval
    • fillRect() 替换为 rect() 并在内部 for 循环外执行单个 fill()

    使用 1 毫秒的超时值可能会导致浏览器阻塞,或者至少会显着减慢浏览器的速度。考虑到监视器更新仅每 16.7 毫秒发生一次,这当然会浪费周期。如果您想减少/增加正弦的速度,您可以减少/增加增量步长。

    本质上:

    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");
    var sineval = [];
    var offset = 0;
    
    init();
    
    function init() {
    
      for (var i = 0; i <= 1000; ++i) {
        sineval.push(Math.sin(i * Math.PI / 180));
      }
    
      // Call the sineWave() function
      sineWave();
    }
    
    
    function sineWave() {
    
      ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
    
      ctx.beginPath();
      ctx.fillStyle = "green";
    
      // draw positive part of sine wave here
      for (var x = 0; x < 1000; x++) {
        var y = sineval[x + offset];
        if (y >= 0) {
          y = 100 - (y - 0) * 70;
          ctx.rect(x, y, 2, 2);
        }
      }
    
      ctx.fill();
    
      ctx.beginPath();
      ctx.fillStyle = "red";
    
      // draw negative part of sine wave here
      for (var x = 0; x < 1000; x++) {
        var y = sineval[x + offset];
        if (y < 0) {
          y = 100 - (y - 0) * 70;
          ctx.rect(x, y, 2, 2);
        }
      }
      ctx.fill();
    
      offset = (offset > 360) ? 0 : ++offset;
      
      requestAnimationFrame(sineWave);
    }
    &lt;canvas id="canvas" width=800 height=500&gt;&lt;/canvas&gt;

    当然,如果您在&lt;head&gt; 中加载脚本,则需要将其包装在window.onload 块中,以便可以使用画布元素。或者,如果您还没有将脚本放在页面底部,则直接将其放在页面底部。

    【讨论】:

    • 我按照你说的做了,但速度只提高了一点点。
    • @AbhinavMishra 您的代码中没有任何内容表明这将是性能问题。您能否设置一个有效的小提琴/内联示例,以便我们仔细查看它?
    • @AbhinavMishra 我实际上会收回它......看看它的绘制方式,每帧都会发生 1000 次填充。我重写了您的绘图方法,仅添加到路径并在该循环之外进行一次填充。看看有没有帮助。
    • @AbhinavMishra 我在 FF 和 Chrome 中都获得了 60 fps,这很好(您需要进行性能测试以了解实际使用了多少时间片)。但是我看不到代码有任何特殊问题(没有闪烁)?您使用的计算机是硬件加速(gpu)吗?如果您尝试了我更新的示例代码,它对您的设置有何影响?顺便说一句,“闪烁”是指闪烁还是运动不均匀?
    • 我有 i5 处理器,在我的 mozilla 浏览器中,波动有点离散,但 jsfiddle 的输出对我来说也很好。顺便说一句,如果我想提高波浪的速度,我该怎么做?
    猜你喜欢
    • 2022-01-15
    • 2015-06-02
    • 2013-07-31
    • 2018-12-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-11-06
    相关资源
    最近更新 更多