<script>
  function globalError(msg) {
    var el = document.createElement('h1');
    document.body.appendChild(el);
    el.style.color = 'red';
    el.textContent = msg;
    throw new Error();
  }
</script>
<script nomodule>

globalErr('Your browser doesn\'t have modules!')

</script>
<aside class="head">Time to parse ~600kb GIF with 121 frames</aside>
<ul id="out">Working...</ul>
<aside>
  Ignore any partial GIF rendering—this is just how GIFs are represented internally<br />
  For more info, see <a href="https://github.com/samthor/fastgif">fastgif</a>
</aside>
<p><a href="?qqq" id="rerun">Rerun demo</a> or try loading the <a href="https://samthor.github.io/fastgif/fastgif.html" target="_blank">standalone demo</a></p>
body {
  min-height: 100vh;
  font-family: Lato, Arial, Helvetica, Sans-Serif;
  display: flex;
  flex-flow: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  line-height: 1.5em;
  
  .head {
    font-weight: 600;
  }
}
#out {
  list-style: none;
  margin: 0;
  display: flex;
  align-items: center;
  justify-content: center;

  > div {
    margin: 1em;

    h2 {
      margin-bottom: 0.25em;
    }

    canvas {
      background: #eee;
      padding: 12px;
      border-radius: 4px;
      max-width: ~"calc(50vw - 2em - 24px)";
    }
  }
}

View Compiled
import * as fastgif from 'https://cdn.rawgit.com/samthor/fastgif/0ffb3c19/fastgif.js';
fastgif.setDebug();

const src = 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/208665/large.gif';

if (!window.WebAssembly) {
  globalErr('Your browser doesn\'t have WebAssembly!')
}

function fetchToBuffer(url) {
  return window.fetch(url).then((response) => response.arrayBuffer());
}

async function render(all, stat, label) {
  const canvas = document.createElement('canvas');
  if (all.length === 0) {
    throw new Error('can\'t play image with no frames');
  }
  canvas.width = all[0].imageData.width;
  canvas.height = all[0].imageData.height;

  const holder = document.createElement('div');
  const heading = document.createElement('h2');
  const v = new Number(stat).toFixed(2);
  heading.textContent = `${label}: ${v}ms`;

  holder.appendChild(heading);
  holder.appendChild(canvas);
  out.appendChild(holder);

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

  let frame = 0;
  while (true) {
    ctx.putImageData(all[frame].imageData, 0, 0);
    await new Promise((resolve) => window.setTimeout(resolve, all[frame].delay));

    if (++frame === all.length) {
      frame = 0;
    }
  }
}

async function run() {
  const buf = await fetchToBuffer(src);

  const times = {};
  const log = async (method, fn) => {
    const start = performance.now();
    const out = await fn();
    const duration = performance.now() - start;
    times[method] = duration;
    return out;
  };

  // wasm
  const wasmFrames = await log('wasm', async () => {
    const wasmDecoder = new fastgif.Decoder();
    return await wasmDecoder.decode(buf);
  });

  // gifuct
  const gifuctFrames = await log('gifuct', async () => {
    console.time('gifuct-new')
    const gifuct = new GIF(buf);
    console.timeEnd('gifuct-new')
    console.time('gifuct-frames');
    const gifuctRaw = gifuct.decompressFrames(true);
    const out = gifuctRaw.map((raw) => {
      return {
        imageData: new ImageData(raw.patch, raw.dims.width, raw.dims.height),
        delay: raw.delay,
      };
    });
    console.timeEnd('gifuct-frames');
    return out;
  });

  // render all
  out.textContent = '';
  return Promise.race([
    render(wasmFrames, times['wasm'], 'wasm'),
    render(gifuctFrames, times['gifuct'], 'gifuct'),
  ]);
}

run().catch((err) => console.error(err));

rerun.href += "?" + Math.random();

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdn.rawgit.com/matt-way/gifuct-js/de68974e/dist/gifuct-js.min.js