I'm trying to create a canvas project inspired by this gif where colors of an image are averaged and animated.Thus far I have all of the functionality working but it only performs well with a large cell size. My current thinking is that it's all of the .fillRects that are causing the issue because rendering is expensive. Am I correct in thinking this? Is there any way to chain these fillRects together and use one render each iteration like one would with paths as opposed to a new render for each fillRect?Other performance improvements are welcome as well.var imgCanvas = document.createElement('canvas'),    imgContext = imgCanvas.getContext('2d'),    pixelInterval = 5, // Rather than inspect every single pixel in the image inspect every 5th pixel    img = document.getElementById(imageToBeUsed), // The image to be averaged    cellSize = 100, // The size of square to average the color    cw, // Canvas width    ch; // Canvas heightvar cellCanvas = document.getElementById(cellCanvas),    cellContext = cellCanvas.getContext('2d');// Use a CORS proxy to allow the image to be usedimg.crossOrigin = Anonymous;img.src = https://crossorigin.me/ + img.src;img.onload = function() {  // Once the CORS enabled image loads, do our work  newImg(img);  animateColorAvg();}// Updates the image to be usedfunction newImg(imgElem) {  img = imgElem;    // Set the height and width of the canvas elements to that of the image  cw = imgCanvas.width = cellCanvas.width = img.naturalWidth || img.offsetWidth || img.width;  ch = imgCanvas.height = cellCanvas.height = img.naturalHeight || img.offsetHeight || img.height;    // Draw the image to our hidden canvas for calculations  imgContext.drawImage(img, 0, 0);}// Animate the movement of the cellsvar offset = 0,    playSpeed = 0, // Determines how often the canvas is updated   lastTime = Date.now();function animateColorAvg() {  if(Date.now() - lastTime > playSpeed) {  lastTime = Date.now();        offset += 5;    if(offset >= cellSize)      offset = 0;      gridifyAvgColors(offset); }  window.requestAnimationFrame(animateColorAvg);}// Creates a grid of average colors of the current imagefunction gridifyAvgColors(cellOffset) {  var numXCells = Math.ceil(cw / cellSize),      numYCells = Math.ceil(ch / cellSize);    // Start at the bottom right, go to top left  for(var i = numXCells; i >= 0; i--) {    for(var j = numYCells; j >= 0; j--) {      var x = i * cellSize - cellSize + cellOffset,          y = j * cellSize - cellSize + cellOffset,          color = getAvgColorAsRGB(x, y);            cellContext.fillStyle = rgb( + color.r + ,  + color.g + ,  + color.b + );      cellContext.fillRect(x, y, cellSize, cellSize);    }  }}// Gets the average color of the requested areafunction getAvgColorAsRGB(startX, startY) {  // Check to see if a cell placed at this location covers any part of the image  if(!(  (startX < cw       && startY < ch)      || (startX + cellSize >= 0       && startY + cellSize >= 0)     ))     return; // Don't calculate anything because it's not covering a part of the image    var cellSizeX = cellSize,      cellSizeY = cellSize;    // Account for negative starting values that still cover a part of the image  if(startX < 0) {    cellSizeX = 0 - startX;    startX = 0;  }  if(startY < 0) {    cellSizeY = 0 - startY;    startY = 0;  }    // Account for values that go outside of the image  if(startX + cellSizeX > cw && startX != cw)    cellSizeX = cw - startX;  if(startY + cellSizeY > ch && startY != ch)    cellSizeY = ch - startY;    var rgb = { r:0, g:0, b:0 },      count = 0,      i = -4,      data, length;  try {    data = imgContext.getImageData(startX, startY, cellSizeX, cellSizeY);  } catch(e) {    // Catch errors - usually due to cross domain security issues    console.log(e);    return;  }  data = data.data;  length = data.length;  while ((i += pixelInterval * 4) < length) {    count++;    rgb.r += data[i];    rgb.g += data[i + 1];    rgb.b += data[i + 2];  }    // Round the number values  rgb.r = Math.floor(rgb.r / count);  rgb.g = Math.floor(rgb.g / count);  rgb.b = Math.floor(rgb.b / count);  return rgb;}<canvas id=cellCanvas></canvas><img id=imageToBeUsed src=http://img2.timeinc.net/people/i/2013/pets/news/130304/kitten-3-600.jpg/>
Improving canvas performance for a color average animation
javascript;performance;canvas
After a suggestion by a friend, I realized it wasn't the .fillRect that was causing the performance issue, but rather the looped imgContext.getImageData(). I originally thought it wasn't this because it seemed to work fine with large cells (it seems dumb to think that now).After changing to a single getImageData() and converting the rest of the calculations to match, it now performs flawlessly!var imgCanvas = document.createElement('canvas'),    imgContext = imgCanvas.getContext('2d'),    pixelInterval = 5, // Rather than inspect every single pixel in the image inspect every 5^2 pixel    img = document.getElementById(imageToBeUsed), // The image to be averaged    cellSize = 20, // The size of square to average the color    cw, // Canvas width    ch; // Canvas heightvar cellCanvas = document.getElementById(cellCanvas),    cellContext = cellCanvas.getContext('2d');// Use a CORS proxy to allow the image to be usedimg.crossOrigin = Anonymous;img.src = https://crossorigin.me/ + img.src;img.onload = function() {  // Once the CORS enabled image loads, do our work  newImg(img);  animateColorAvg();}var data; // A 1D arrayvar numXCells,    numYCells;// Updates the image to be usedfunction newImg(imgElem) {  img = imgElem;    // Set the height and width of the canvas elements to that of the image  cw = imgCanvas.width = cellCanvas.width = img.naturalWidth || img.offsetWidth || img.width;  ch = imgCanvas.height = cellCanvas.height = img.naturalHeight || img.offsetHeight || img.height;    // Draw the image to our hidden canvas for calculations  imgContext.drawImage(img, 0, 0);    // Set the image data  try {    data = imgContext.getImageData(0, 0, cw, ch);  } catch(e) {    // Catch errors - usually due to cross domain security issues    console.log(e);    return;  }  data = data.data;}// Animate the movement of the cellsvar offset = -5,    playSpeed = 1000, // Determines how often the canvas is updated    lastTime = Date.now();function animateColorAvg() {  //if(Date.now() - lastTime > playSpeed) {  lastTime = Date.now();        offset += 5;    if(offset >= cellSize)      offset = 0;      gridifyAvgColors(offset);  //}  window.requestAnimationFrame(animateColorAvg);}// Creates a grid of average colors of the current imagefunction gridifyAvgColors(cellOffset) {  numXCells = Math.ceil(cw / cellSize);  numYCells = Math.ceil(ch / cellSize);    // Start at the bottom right, go to top left  for(var i = numXCells; i >= 0; i--) {    for(var j = numYCells; j >= 0; j--) {      var x = i * cellSize - cellSize + cellOffset,          y = j * cellSize - cellSize + cellOffset,          color = getAvgColorAsRGB(x, y);            cellContext.fillStyle = rgb( + color.r + ,  + color.g + ,  + color.b + );      cellContext.fillRect(x, y, cellSize, cellSize);    }  }}// Gets the average color of the requested areafunction getAvgColorAsRGB(startX, startY) { var rgb = { r:0, g:0, b:0 };    // Check to see if a cell placed at this location covers any part of the image  if(!(  (startX < cw       && startY < ch)      || (startX + cellSize >= 0       && startY + cellSize >= 0)  ))    return rgb; // Don't calculate anything because it's not covering a part of the image    var cellSizeX = cellSize,      cellSizeY = cellSize;    // Account for negative starting values that still cover a part of the image  if(startX < 0) {    cellSizeX = 0 - startX;    startX = 0;  }  if(startY < 0) {    cellSizeY = 0 - startY;    startY = 0;  }    // Account for values that go outside of the image  if(startX + cellSizeX > cw && startX != cw)    cellSizeX = cw - startX;  if(startY + cellSizeY > ch && startY != ch)    cellSizeY = ch - startY;    var count = 0;        for(var j = startY; j < startY + cellSizeY; j += pixelInterval) {    for(var i = startX; i < startX + cellSizeX; i += pixelInterval) { // Do this second for lookup perf reasons      count++;      var index = (i + j * numXCells * cellSize) * 4;      rgb.r += data[index];      rgb.g += data[index + 1];      rgb.b += data[index + 2];    }  }    // Round the number values  rgb.r = Math.floor(rgb.r / count);  rgb.g = Math.floor(rgb.g / count);  rgb.b = Math.floor(rgb.b / count);  return rgb;}<canvas id=cellCanvas></canvas><img id=imageToBeUsed src=http://img2.timeinc.net/people/i/2013/pets/news/130304/kitten-3-600.jpg/>P.S. There's no way to chain .fillRects, but that likely isn't the cause of the performance issue.