How to Detect When an Animated GIF Is Fully Downloaded

Displaying an animated GIF before it finishes downloading often causes choppy frame rates, delayed loops, or visible rendering artifacts. This guide covers how web developers can detect when an animated GIF has completely downloaded using native JavaScript techniques—including the standard Image() constructor, the Fetch API with Blobs, and download progress tracking—ensuring smooth playback upon display.

Method 1: The Image() Constructor and onload Event

The most common way to preload an image is by creating an off-screen HTML image element in memory and listening for its load event. The browser fires this event only after the entire file has been downloaded.

function preloadGif(url, callback) {
  const img = new Image();

  img.onload = () => {
    callback(null, img);
  };

  img.onerror = () => {
    callback(new Error(`Failed to load GIF from ${url}`));
  };

  img.src = url;

  // Handle cached images where onload might not trigger reliably
  if (img.complete) {
    callback(null, img);
  }
}

// Usage
preloadGif('https://example.com/animation.gif', (err, loadedImg) => {
  if (err) {
    console.error(err);
    return;
  }
  document.getElementById('gif-container').appendChild(loadedImg);
});

When using this approach, keep the target element hidden (e.g., via CSS display: none or by keeping it in memory) until onload fires. Once the callback triggers, the image can be inserted into the DOM or set as an active source without frame stutter.

Method 2: Fetch API and Object URLs

For strict control, the fetch() API guarantees that every byte of the file has been received before the promise resolves. Converting the response into a Blob ensures the browser has the full resource in memory.

async function loadGifAsBlob(url, targetImgElement) {
  try {
    const response = await fetch(url);
    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }

    const blob = await response.blob();
    const objectUrl = URL.createObjectURL(blob);

    targetImgElement.src = objectUrl;
    targetImgElement.style.display = 'block';

    // Revoke the object URL after the image element loads to free memory
    targetImgElement.onload = () => {
      URL.revokeObjectURL(objectUrl);
    };
  } catch (error) {
    console.error('Error downloading GIF:', error);
  }
}

// Usage
const displayImage = document.getElementById('my-gif');
loadGifAsBlob('https://example.com/animation.gif', displayImage);

This method eliminates ambiguity around browser rendering pipelines because the <img> tag only receives the data once the download is 100% complete.

Method 3: Tracking Download Progress with XMLHttpRequest

When dealing with large animated GIFs, you may want to display a progress bar or loading spinner while the file downloads. The XMLHttpRequest API provides an onprogress event that tracks received bytes against total bytes.

function loadGifWithProgress(url, onProgress, onComplete) {
  const xhr = new XMLHttpRequest();
  xhr.open('GET', url, true);
  xhr.responseType = 'blob';

  xhr.onprogress = (event) => {
    if (event.lengthComputable) {
      const percentComplete = (event.loaded / event.total) * 100;
      onProgress(percentComplete);
    }
  };

  xhr.onload = () => {
    if (xhr.status === 200) {
      const blobUrl = URL.createObjectURL(xhr.response);
      onComplete(null, blobUrl);
    } else {
      onComplete(new Error(`Failed to load: ${xhr.statusText}`));
    }
  };

  xhr.onerror = () => onComplete(new Error('Network error'));

  xhr.send();
}

// Usage
loadGifWithProgress(
  'https://example.com/animation.gif',
  (percent) => {
    console.log(`Loading: ${percent.toFixed(2)}%`);
  },
  (err, blobUrl) => {
    if (!err) {
      const img = document.getElementById('my-gif');
      img.src = blobUrl;
    }
  }
);

Key Considerations