Varrience Well, for my gif loader, I actually stack the images on top of one-another into a spritesheet and then turn that into the builtin cdo animations
window.loadGIF = function(url,speed,callback,callback2,failure) {
  var serverurl = server+"/gif?url="+url;
  var gif = {};
  gif.width = 1;
  gif.height = 1;
  gif.draw = function() {};
  loadImage(url,function(frame) {
    gif.width = frame.width;
    gif.height = frame.height;
    gif.draw = function(x,y,width,height) {
      image(frame,x,y,width,height);
    };
    loadImage(serverurl, function (sheetImage) {
      var spriteSheet = loadSpriteSheet(sheetImage, frame.width, frame.height, sheetImage.height/frame.height);
      gif.animation = loadAnimation(spriteSheet);
      gif.animation.offX = frame.width/2;
      gif.animation.offY = frame.height/2;
      gif.animation.frameDelay = speed || 4;
      gif.draw = function(x,y,width,height) {
        translate(x,y);
        scale(width/this.width,height/this.height);
        this.animation.draw();
        scale(this.width/width,this.height/height);
        translate(-x,-y);
      };
      if (typeof callback == 'function')  callback(gif);
    }, failure);
    if (typeof callback2 == 'function')  callback2();
  },failure);
  return gif;
};
Server
var gm = require("gm");
var request = require("request");
app.get("/gif", async (req, res) => {
  let url = req.query.url;
  if (!url) return;
  console.log("Starting: " + url);
  gm(request(url))
    .coalesce()
    .append()
    .toBuffer((err, buffer) => {
      if (err) {
        throw err;
      } else {
        console.log("Done with: " + url);
        res.set("Content-Type", "image/png");
        res.send(buffer);
        //fs.writeFile("/cache/"+encodeURIComponent(url)+".png",buffer)
      }
    });
});