尝试使用更新的src绘制图像时画布闪烁

时间:2017-09-13 12:02:11

标签: javascript html5 canvas html5-canvas

在画布发生变化时获取画布的图像

var canvas = document.getElementById('myCanvas');

socket.emit('updateCanvasImage', canvas.toDataURL());

在其他地方的新画布上绘制图像

var canvas = document.getElementById('myCanvasImg');

var context = canvas.getContext('2d');

var image = new Image();

image.onload = function() {
    context.drawImage(this, 0, 0, canvas.width, canvas.height);
};

socket.on('updateCanvasImage', function (img) {
    image.src = img;
});

当套接字更改image.src

时,画布会闪烁

这里有很多这样的问题,但没有一个解决方案对我有用。

如何解决这个问题?

2 个答案:

答案 0 :(得分:0)

不要使用事件来呈现内容

不要使用事件重绘画布。图像内容以固定速率呈现给显示器,而大多数事件未同步到显示速率,显示速率和事件速率之间的不匹配可能导致闪烁。

requestAnimationFrame

当您反复更新任何可视内容时,无论是画布还是其他DOM内容,都应使用requestAnimationFrame来调用渲染功能。然后,此功能应该为下一个显示帧准备好所有内容。

当渲染函数返回时,更改将保留在后备缓冲区中,直到显示硬件准备好显示下一帧。

删除闪烁

因此,为了解决您的问题,请创建一个与显示速率相关联的渲染功能。

var image = new Image();    
var update = true; // if true redraw
function renderFunction(){
    if(update){  // only raw if needed
       update = false;
       context.drawImage(image, 0, 0, canvas.width, canvas.height);

    }
    requestAnimationFrame(renderFunction);
}
requestAnimationFrame(renderFunction);

然后在事件中,只需获取新图像状态并在准备绘制时标记更新

image.onload = () => update = true;    
socket.on('updateCanvasImage', src => {update = false; image.src = src});

对拖动事件执行相同操作

这将确保您永远不会有任何闪烁,并且您还可以检查图像更新是否比延迟更快到达,从而降低图像更新速率。

双缓冲画布

有很多时候,从一个或多个不同的来源,视频,相机,绘图命令(来自鼠标,触摸,代码)或图像流更新画布内容。

在这些情况下,最好使用第二个画布,将其保留在屏幕外(在RAM中)并用作显示源。这使得显示画布只是一个视图,与内容无关。

创建第二个画布;

function createCanvas(width, height){
    const myOffScreenCanvas = document.createElement("canvas");
    myOffScreenCanvas.width = width;
    myOffScreenCanvas.height = height;
    // attach the context to the canvas for easy access and to reduce complexity.
    myOffScreenCanvas.ctx = myOffScreenCanvas.getContext("2d"); 
    return myOffScreenCanvas;
 }

然后在渲染功能中你可以显示它

var background = createCanvas(1024,1024); 
var scale = 1; // the current scale 
var origin = {x : 0, y : 0}; // the current origin
function renderFunction(){
    // set default transform
    ctx.setTransform(1,0,0,1,0,0);

    // clear
    ctx.clearRect(0,0,canvas.width,canvas.height);

    // set the current view
    ctx.setTransform(scale,0,0,scale,origin.x,origin.y);

    // draw the offscreen canvas
    ctx.drawImage(background, 0, 0);

    requestAnimationFrame(renderFunction);
}
requestAnimationFrame(renderFunction);

因此,您的图像加载会吸引到屏幕外的画布

image.onload = () => background.ctx.drawImage(0, 0, background.width, background.height);    
socket.on('updateCanvasImage', src => image.src = src);

您的鼠标拖动事件只需要更新画布视图。渲染功能将使用更新的视图渲染下一帧。您还可以添加缩放和旋转。

const mouse  = {x : 0, y : 0, oldX : 0, oldY : 0, button : false}
function mouseEvents(e){
    mouse.oldX = mouse.x;
    mouse.oldY = mouse.y;
    mouse.x = e.pageX;
    mouse.y = e.pageY;
    mouse.button = e.type === "mousedown" ? true : e.type === "mouseup" ? false : mouse.button;
    if(mouse.button){
        origin.x += mouse.x - mouse.oldX;
        origin.y += mouse.y - mouse.oldY;
    }
}
["down","up","move"].forEach(name => document.addEventListener("mouse" + name, mouseEvents)); 

答案 1 :(得分:0)

每当你更改HTMLImageElement的src时,它的内容都会被清除,当画布尝试渲染它时,它不能。

因此,您将体验没有任何图像的帧(闪烁),直到新设置的媒体被加载并解析(fiddle reproducing the issue)

如果没有看到您的代码,很难为您提供正确的解决方案,但简单的结构可能是:

  • 让current =当前加载的图像,可以进行动画循环/拖动事件。
  • on(socket.updateCanvasImage,让newImage =你设置新src的新图片)。
  • on(newImage.load,current = new Image)。

通过这种简单的结构,您可以避免闪烁。

var ctx = canvas.getContext('2d');

function animLoop(time){ // draws continously
  ctx.clearRect(0,0,canvas.width,canvas.height);
  ctx.drawImage(current, 0,0, canvas.width, canvas.height);
  ctx.fillText(time, 20,20);
  requestAnimationFrame(animLoop);
  }
var current = new Image();

function loadImage(){
  var img = new Image(); // if you really want to optimize your code for memory impact, you could declare it only once out of the function...
  img.onload = function(){
    current = this; // update the image to be rendered with the new & loaded one
    setTimeout(loadImage, 2000); // start loading a new one in 2 sec (will be rendered even later)
    }
  img.onerror = loadImage;
  img.src = 'https://upload.wikimedia.org/wikipedia/commons' + urls[++url_index % urls.length]+'?'+Math.random();
  }

var url_index = 0;
var urls = [
  //Martin Falbisoner [CC BY-SA 4.0 (http://creativecommons.org/licenses/by-sa/4.0)], via Wikimedia Commons
 '/2/2d/Okayama_Castle%2C_November_2016_-02.jpg',
 //Diego Delso [CC BY-SA 4.0 (http://creativecommons.org/licenses/by-sa/4.0)], via Wikimedia Commons
 '/9/9b/Gran_Mezquita_de_Isfah%C3%A1n%2C_Isfah%C3%A1n%2C_Ir%C3%A1n%2C_2016-09-20%2C_DD_34-36_HDR.jpg',
 //Dietmar Rabich / Wikimedia Commons / “Münster, LVM, Skulptur -Körper und Seele- -- 2016 -- 5920-6” / CC BY-SA 4.0, via Wikimedia Commons
 '/5/53/M%C3%BCnster%2C_LVM%2C_Skulptur_-K%C3%B6rper_und_Seele-_--_2016_--_5920-6.jpg',
 //By Charlesjsharp (Own work, from Sharp Photography, sharpphotography) [CC BY-SA 4.0 (http://creativecommons.org/licenses/by-sa/4.0)], via Wikimedia Commons
 '/4/4b/Campo_flicker_(Colaptes_campestris)_female.JPG'
 ];
loadImage();
animLoop();
<canvas id="canvas" width="500" height="500"></canvas>

修改
这仅适用于chrome ,Firefox不会表现得那样,实际上只在我们调用drawImage时才开始解析图像。这将在此期间保持画布的绘图。如果这是一个问题,您可以尝试使用ImageBitmap对象降低此值,但是使用我在演示中使用的大图像时,此停止仍然存在...

var ctx = canvas.getContext('2d');

function animLoop(time){ // draws continously
  ctx.clearRect(0,0,canvas.width,canvas.height);
  ctx.drawImage(current, 0,0, canvas.width, canvas.height);
  ctx.fillText(time, 20,20);
  requestAnimationFrame(animLoop);
  }
var current = new Image();

function loadImage(){
  var img = new Image();
  img.crossOrigin = 'anonymous';
  img.onload = function(){
    createImageBitmap(this, 0,0,this.width, this.height).then(function(bmp){
      current = bmp; // update the image to be rendered with an ImageBitmap
      }).catch(e=>console.log(e))
    setTimeout(loadImage, 2000); // start loading a new one in 2 sec (will be rendered even later)
    }
  img.onerror = loadImage;
  img.src = 'https://upload.wikimedia.org/wikipedia/commons' + urls[++url_index % urls.length]+'?'+Math.random();
  }

var url_index = 0;
var urls = [
  //Martin Falbisoner [CC BY-SA 4.0 (http://creativecommons.org/licenses/by-sa/4.0)], via Wikimedia Commons
 '/2/2d/Okayama_Castle%2C_November_2016_-02.jpg',
 //Diego Delso [CC BY-SA 4.0 (http://creativecommons.org/licenses/by-sa/4.0)], via Wikimedia Commons
 '/9/9b/Gran_Mezquita_de_Isfah%C3%A1n%2C_Isfah%C3%A1n%2C_Ir%C3%A1n%2C_2016-09-20%2C_DD_34-36_HDR.jpg',
 //Dietmar Rabich / Wikimedia Commons / “Münster, LVM, Skulptur -Körper und Seele- -- 2016 -- 5920-6” / CC BY-SA 4.0, via Wikimedia Commons
 '/5/53/M%C3%BCnster%2C_LVM%2C_Skulptur_-K%C3%B6rper_und_Seele-_--_2016_--_5920-6.jpg',
 //By Charlesjsharp (Own work, from Sharp Photography, sharpphotography) [CC BY-SA 4.0 (http://creativecommons.org/licenses/by-sa/4.0)], via Wikimedia Commons
 '/4/4b/Campo_flicker_(Colaptes_campestris)_female.JPG'
 ];
loadImage();
animLoop();
<canvas id="canvas" width="500" height="500"></canvas>

<强>重新编辑:
由于您所做的是屏幕共享,因此您可能还需要考虑WebRTC以及canvas.captureStream而不是发送静止图像。