为什么canvas对象在动画期间会一直闪烁?

时间:2016-11-12 23:42:54

标签: object animation canvas settimeout

我用javascript为一个冒烟的房子做了一个动画。从烟囱向上流动的每一部分烟雾都有三种功能。它们由一个滑块控制,该滑块切换烟囱烟雾存在的速度。一切正常,除非从左到右切换滑块,上升时烟雾闪烁。谁能告诉我为什么会这样?

由于

HTML:

<!DOCTYPE html>
<html>
<head>
<title>Carrey, Justin, Myshkin, Rost</title>
<meta charset="utf-8"/>
    <link rel="stylesheet" href="style.css"/>

</head>
<body>
    <canvas id="canvas" width="500" height="500">Get a new Browser!</canvas>

    <script src="script.js" ></script>

    <form>
    <input type="range" min="10" max="250" value="100" id="speedCont"/>
    <p>
        Rostislav Myshkin A00787633 rmyshkin@my.bcit.ca
        <br />
        Completed:3-D house, smoke, animation for smoke, slider for speed.
        <br />
        Challanges: animating the smoke.
    </p>

</form>

</body>
</html>

的javascript:

var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
ctx.lineWidth = 4;
ctx.strokeLinecap = 'round';
var a = 1;
var speed = 100;
var posY = 100,
posY2 = 120,
posY3 = 140,
posX = 100,
vx = 5,
vy = 5;


function foundation() {

    //grass
    ctx.fillStyle = "green";
    ctx.strokeStyle = "#000000";
    ctx.beginPath();
    ctx.moveTo(25, 375);
    ctx.lineTo(125, 325);
    ctx.lineTo(471, 325);
    ctx.lineTo(400, 375);
    ctx.lineTo(25, 375);
    ctx.fill();
    ctx.stroke();

    //front face ground
    ctx.fillStyle = "#873600";
    ctx.strokeStyle = "#000000";
    ctx.beginPath();
    ctx.moveTo(25, 375); //top left
    ctx.lineTo(25, 425); //bottom left
    ctx.lineTo(400, 425); //bottom right
    ctx.lineTo(400, 375); //top right
    ctx.lineTo(25, 375); //top line
    ctx.fill();
    ctx.stroke();

    //east face ground 
    ctx.fillStyle = "#872000";
    ctx.strokeStyle = "#000000";
    ctx.beginPath();
    ctx.moveTo(475, 325); //top right
    ctx.lineTo(475, 375); //bottom right
    ctx.lineTo(400, 425); //bottom line
    ctx.lineTo(400, 375); //top left
    ctx.lineTo(475, 325); //top right
    ctx.fill();
    ctx.stroke();

}

function house() {

    //front face
    ctx.fillStyle = "#2980B9";
    ctx.strokeStyle = "#000000";
    ctx.beginPath();
    ctx.moveTo(110, 365);
    ctx.lineTo(110, 200);
    ctx.lineTo(375, 200);
    ctx.lineTo(375, 365);
    ctx.lineTo(110, 365);
    ctx.fill();
    ctx.stroke();

    //east face
    ctx.fillStyle = "#1760B4";
    ctx.strokeStyle = "#000000";
    ctx.beginPath();
    ctx.moveTo(375, 200); //lower left
    ctx.lineTo(415, 180); //
    ctx.lineTo(415, 340);
    ctx.lineTo(375, 365);
    ctx.lineTo(375, 200);
    ctx.fill();
    ctx.stroke();

    //roof front face 
    ctx.fillStyle = "#B41717";
    ctx.strokeStyle = "#000000";
    ctx.beginPath();
    ctx.moveTo(95, 210);
    ctx.lineTo(160, 140);
    ctx.lineTo(395, 140);
    ctx.lineTo(365, 210);
    ctx.lineTo(365, 210);
    ctx.lineTo(95, 210);
    ctx.fill();
    ctx.stroke();

    //roof east face 
    ctx.fillStyle = "darkred";
    ctx.strokeStyle = "#000000";
    ctx.beginPath();
    ctx.moveTo(365, 210);
    ctx.lineTo(425, 190);
    ctx.lineTo(395, 140);
    ctx.lineTo(365, 210);
    ctx.fill();
    ctx.stroke();

    //door 
    ctx.fillStyle = "darkred";
    ctx.strokeStyle = "#000000";
    ctx.beginPath();
    ctx.moveTo(300, 365);
    ctx.lineTo(300, 295);
    ctx.lineTo(250, 295);
    ctx.lineTo(250, 365);
    ctx.lineTo(300, 365);
    ctx.fill();
    ctx.stroke();

    //doorknob
    ctx.fillStyle = "yellow";
    ctx.strokeStyle = "#000000";
    ctx.beginPath();
    ctx.arc(290, 335, 5, 0, 2 * Math.PI, false);
    ctx.fill();
    ctx.stroke();

    //walkway
    ctx.fillStyle = "gray";
    ctx.strokeStyle = "#000000";
    ctx.beginPath();
    ctx.moveTo(250, 365); //left point
    ctx.lineTo(240, 375); //left side
    ctx.lineTo(290, 375);
    ctx.lineTo(300, 365);
    ctx.fill();
    ctx.stroke();

    //window living room
    ctx.fillStyle = "blue";
    ctx.strokeStyle = "#000000";
    ctx.beginPath();
    ctx.moveTo(143, 347);
    ctx.lineTo(143, 295);
    ctx.lineTo(212, 295);
    ctx.lineTo(212, 347);
    ctx.lineTo(143, 347);
    ctx.fill();
    ctx.stroke();

    //window top left
    ctx.fillStyle = "blue";
    ctx.strokeStyle = "#000000";
    ctx.beginPath();
    ctx.moveTo(143, 275);
    ctx.lineTo(143, 225);
    ctx.lineTo(212, 225);
    ctx.lineTo(212, 275);
    ctx.lineTo(143, 275);
    ctx.fill();
    ctx.stroke();

    //window top right
    ctx.fillStyle = "blue";
    ctx.strokeStyle = "#000000";
    ctx.beginPath();
    ctx.moveTo(263, 275);
    ctx.lineTo(263, 225);
    ctx.lineTo(332, 225);
    ctx.lineTo(332, 275);
    ctx.lineTo(263, 275);
    ctx.fill();
    ctx.stroke();

    //chimney front
    ctx.fillStyle = "black";
    ctx.strokeStyle = "#000000";
    ctx.beginPath();
    ctx.moveTo(170, 130); //top left
    ctx.lineTo(170, 180); //left side line
    ctx.lineTo(200, 180); //bottom line
    ctx.lineTo(200, 130); //right side line
    ctx.lineTo(170, 130); //top side line
    ctx.fill();
    ctx.stroke();

    //chimney east
    ctx.fillStyle = "black";
    ctx.strokeStyle = "#000000";
    ctx.beginPath();
    ctx.moveTo(200, 130); //top left
    ctx.lineTo(215, 123); //top side line
    ctx.lineTo(215, 170); //right side line
    ctx.lineTo(200, 180); //
    ctx.fill();
    ctx.stroke();

    //chimney top
    ctx.fillStyle = "black";
    ctx.strokeStyle = "#000000";
    ctx.beginPath();
    ctx.moveTo(170, 130); //top left
    ctx.lineTo(185, 122); //left side
    ctx.lineTo(210, 122); //top side
    ctx.lineTo(200, 130); 
    ctx.fill();
    ctx.stroke();

}

function smoke1(){

    posY += -vy;
    posX += vx;

    if (posY < -15) posY = 100;  

        ctx.fillStyle = "aqua";
        ctx.fillRect(0,0, 220, 127);

        ctx.fillStyle = "rgba(0,0,0,0.5)";
        ctx.beginPath();
        ctx.arc(200, posY, 15, 0, Math.PI*2, true);
        ctx.fill();

}   
function smoke2(){

    posY2 += -vy;
    posX += vx;

    if (posY2 < -13) posY2 = 110;



        ctx.fillStyle = "rgba(0,0,0,0.5)";
        ctx.beginPath();
        ctx.arc(185, posY2, 10, 0, Math.PI*2, true);
        ctx.fill();
}
function smoke3(){

    posY3 += -vy;
    posX += vx;

    if (posY3 < -13) posY3 = 110;



        ctx.fillStyle = "rgba(0,0,0,0.5s)";
        ctx.beginPath();
        ctx.arc(210, posY3, 6, 0, Math.PI*2, true);
        ctx.fill();
}

function animate() {
    smoke1();

    var speed = document.getElementById('speedCont').value;

    window.setTimeout(animate, speed);  

}  
function animate2() {
    smoke2();

    var speed = document.getElementById('speedCont').value;

    window.setTimeout(animate2, speed); 

}  
function animate3() {
    smoke3();

    var speed = document.getElementById('speedCont').value;

    window.setTimeout(animate3, speed); 

}  
/**   if (a == 1) {
        ctx.clearRect(0, 0, 260, 105);
        smoke();
        a++;
    } else if (a == 2) {
        ctx.clearRect(0, 0, 260, 105);
        smokeMed();
        a++;
    } else if (a == 3) {
        ctx.clearRect(0, 0, 260, 105);
        smokeBig();
        a = 1;
    } else {
        ctx.clearRect(0, 0, 260, 105);
    }
    window.setTimeout(animate2, speed);
}
**/

window.onload = function all() {

    foundation();
    house();
    animate();
    animate2();
    animate3();
}

window.addEventListener("load", all, false);
//window.setInterval(animate2, 1000);
//window.setTimeout(animate2, speed);

的CSS:

#canvas {
    background-color: aqua;
    border: 1px solid black;
    margin-bottom: 10px ;
}
body {
    background-color: gray;
}

input[type=range] {
    -webkit-appearance: none;
    border: 3px solid black;
    width: 500px;
    border-radius: 20px;
}
input[type=range]::-webkit-slider-runnable-track {
    width: 500px;
    height: 10px;
    background: #ddd;
    border: none;
    border-radius: 20px;
}
input[type=range]::-webkit-slider-thumb {
    -webkit-appearance: none;
    border: 3px solid black;
    height: 30px;
    width: 30px;
    border-radius: 50%;
    background: red;
    margin-top: -8px;
}
input[type=range]:focus {
    outline: none;
}
input[type=range]:focus::-webkit-slider-runnable-track {
    background: #ccc;
}

2 个答案:

答案 0 :(得分:0)

flickr与多个setTimeout函数有关。由于您删除了smoke1()上烟雾的先前状态,因此一旦更改速度,就会出现差异。如果您只使用一个setTimeout,它应该在您的特定情况下正常工作。这里以JSFIDDLE为例。

答案 1 :(得分:0)

Backbuffers&amp; requestAnimationFrame

对DOM的所有渲染都是双缓冲的。始终对backBuffer进行渲染,它不会被绘制到屏幕上。当场景完全渲染时,应该在屏幕上显示。

当函数退出(返回空闲)时,DOM假定您已完成所有渲染以及显示结果的内容。这是将后备缓冲区移动到屏幕可以看到的位置。

如果你只渲染部分场景并退出DOM,则不知道你还没有完成渲染,并且会按原样呈现后备缓冲区。屏幕刷新速度比写入后备缓冲区的速度慢得多。当DOM将后缓冲器呈现给屏幕时,显示硬件可以在将像素扫描到显示硬件上的任何点处。结果是不一致的闪烁和剪切。

您可以通过仅使用一个函数来完成所有渲染来解决问题。问题仍然是当函数退出时,它可能在显示扫描的任何点。你仍会受到一些剪切。

与显示硬件保持同步并防止任何闪烁和剪切使用requestAnimationFrame(在SO中有无穷无尽的答案)来进行所有渲染。

DOM将requestAnimationFrame调用的所有回调函数视为特殊函数,并将延迟任何DOM视觉更改从后备缓冲区移动到屏幕,直到显示硬件处于垂直刷新阶段。此时,自上次垂直刷新以来的所有更改都从后缓冲区移动到屏幕。 (这适用于所有视觉效果,而不仅仅是画布)。

修复代码

您想要一个固定的更新速度。

var speed = 100;
var nextUpdateTime;
function updateAll(time){
    if(nextUpdateTime=== undefined){
       nextUpdateTime= time - speed; // first update now
    }
    if(time >= nextUpdateTime){
       nextUpdateTime= time + (speed - (time - nextUpdateTime)); // get time of next update
       smoke1();
       smoke2();
       smoke3()
    }
    requestAnimationFrame(updateAll);
}
requestAnimationFrame(updateAll);

如果您希望每个FX都有自己的速度,则必须创建一个speed和nextUpdateTime值的数组。请求的动画帧通常以60fps运行。时间始终是请求的回调函数的第一个参数。

最好以60fps

运行

我建议您不要使用较慢的更新速度(100毫秒是10fps),而是修改代码以减慢动画速度,以便在加速到60fps时顺畅运行

以下内容将修改您的动画以播放相同但帧率更高的动画。

var speed = 100;    // the old frame update delay
var frameRate = 60; // requestAnimationFrame frame rate

// vx and vy where the update delta move vectors for the smoke
// that need to be adjusted for the new frame rate.
vx = (vx * (1000 / speed)) / frameRate;
vy = (vy * (1000 / speed)) / frameRate;
function updateAll(time){
    smoke1();
    smoke2();
    smoke3()
    requestAnimationFrame(updateAll);
}
requestAnimationFrame(updateAll);
  

注意如果您提供大量渲染工作,浏览器可能无法跟上。如果它不能在1/60秒内完成,并且在下一次垂直刷新之前不会显示任何内容,帧速率将从60fps降至30fps。   如果您怀疑会发生这种情况,那么您应该监控帧之间的时间并使用它来计算每帧的新烟雾位置。