-1

Why does html5 canvas become very slow when I draw 2000 images and more ?
How can I optimise it? Here's a demo that I've made, disable the "safe mode" by left clicking on the canvas and start moving your mouse until you get ~2000 images drawn

var img = new Image()
img.src = "http://i.imgur.com/oVOibrL.png";

img.onload = Draw;

var canvas = $("canvas")[0];
var ctx    = canvas.getContext("2d")
var cnv    = $("canvas");
var draw = [];

$("canvas").mousemove(add)   

function add(event) {
    draw.push({x: event.clientX, y: event.clientY})
}

 canvas.width = cnv.width();
 canvas.height = cnv.height();

var safe = true;

cnv.contextmenu(function(e) { e.preventDefault() })

cnv.mousedown(function(event) {
    if(event.which == 1) safe = !safe;
    if(event.which == 3) draw = []
});

function Draw() {
   
    
    ctx.clearRect(0, 0, canvas.width, canvas.height);
  
  requestAnimationFrame(Draw);
  
    for(var i of draw) {
        ctx.drawImage(img, i.x, i.y)
    }   
  
  if(safe && draw.length > 300) draw = []
  
  ctx.fillText("Images count: "+ draw.length,10, 50); 
  ctx.fillText("Left click to toggle the 300 images limit",10, 70); 
  ctx.fillText("Right click to clear canvas",10, 90); 
  
}

Draw(); 
body {
  margin: 0;
  padding: 0;
  width: 100%;
  height: 100%;
  position: absolute;
}

canvas {
  position: absolute;
  width: 100%;
  height: 100%;
  z-index: 99999999999;
  cursor: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas></canvas>

Codepen: http://codepen.io/anon/pen/PpeNme

JeePing
  • 449
  • 1
  • 6
  • 15

1 Answers1

0

The simple way with the actual code :

Don't redraw all your images at this rate.

Since in your example, the images are static, you actually don't need to redraw everything every frame : Just draw the latest ones.
Also, if you've got other drawings occurring (e.g your texts), you may want to use an offscreen canvas for only the images, that you'll redraw on the onscreen canvas + other drawings.

var img = new Image()
img.src = "http://i.imgur.com/oVOibrL.png";

img.onload = Draw;

var canvas = $("canvas")[0];
var ctx = canvas.getContext("2d")
var cnv = $("canvas");
var draw = [];

$("canvas").mousemove(add)

function add(event) {
  draw.push({
    x: event.clientX,
    y: event.clientY
  })
}

canvas.width = cnv.width();
canvas.height = cnv.height();
// create an offscreen clone of our canvas for the images
var imgCan = canvas.cloneNode();
var imgCtx = imgCan.getContext('2d');

var drawn = 0; // a counter to know how much image we've to draw

var safe = true;

cnv.contextmenu(function(e) {
  e.preventDefault()
})

cnv.mousedown(function(event) {
  if (event.which == 1) safe = !safe;
  if (event.which == 3) draw = []
});

function Draw() {
  // clear the visible canvas
  ctx.clearRect(0, 0, canvas.width, canvas.height);

  requestAnimationFrame(Draw);
  if (draw.length) { // onmy if we've got some objects to draw
    for (drawn; drawn < draw.length; drawn++) { // only the latest ones
      let i = draw[drawn];
      // draw it on the offscreen canvas
      imgCtx.drawImage(img, i.x, i.y)
    }
  }
  // should not be needed anymore but...
  if (safe && draw.length > 300) {
    draw = [];
    drawn = 0; // reset our counter
    // clear the offscren canvas
    imgCtx.clearRect(0, 0, canvas.width, canvas.height);
  }
  // draw the offscreen canvas on the visible one
  ctx.drawImage(imgCan, 0, 0);
  // do the other drawings
  ctx.fillText("Images count: " + draw.length, 10, 50);
  ctx.fillText("Left click to toggle the 300 images limit", 10, 70);
  ctx.fillText("Right click to clear canvas", 10, 90);

}

Draw();
body {
  margin: 0;
  padding: 0;
  width: 100%;
  height: 100%;
  position: absolute;
}

canvas {
  position: absolute;
  width: 100%;
  height: 100%;
  z-index: 99999999999;
  cursor: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<canvas></canvas>

Now, if you need these images to be dynamic (i.e move at every frame), you may consider using imageDatas.

You can see this original post by user @Loktar, which explains how to parse your drawn image imageData, and then redraw it pixel per pixel on the visible canvas' imageData. You can also see this follow-up Q/A, which provides a color implementation of Loktar's idea.

On small images, this actually improves drastically the performances, but it has the huge inconvenient to not support alpha channel multiplication. You will only have fully transparent and fully opaque pixels. An other cons is that it may be harder to implement, but this is just your problem ;-)

Community
  • 1
  • 1
Kaiido
  • 123,334
  • 13
  • 219
  • 285
  • Hello, thanks for your answer. Actually, the doge demo works fine, but I've forgot to precise that my images are dynamic in the real thing i'm developping, so I've looked to the post you linked to, looks fine but seems hard to implement. Isn't there a library or something that is implementing this? Thanks – JeePing Mar 24 '17 at 20:58