Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
JavaScript animated GIF detection!
function isAnimatedGif(src, cb) {
var request = new XMLHttpRequest();
request.open('GET', src, true);
request.responseType = 'arraybuffer';
request.addEventListener('load', function () {
var arr = new Uint8Array(request.response),
i, len, length = arr.length, frames = 0;
// make sure it's a gif (GIF8)
if (arr[0] !== 0x47 || arr[1] !== 0x49 ||
arr[2] !== 0x46 || arr[3] !== 0x38)
{
cb(false);
return;
}
//ported from php http://www.php.net/manual/en/function.imagecreatefromgif.php#104473
//an animated gif contains multiple "frames", with each frame having a
//header made up of:
// * a static 4-byte sequence (\x00\x21\xF9\x04)
// * 4 variable bytes
// * a static 2-byte sequence (\x00\x2C) (some variants may use \x00\x21 ?)
// We read through the file til we reach the end of the file, or we've found
// at least 2 frame headers
for (i=0, len = length - 9; i < len, frames < 2; ++i) {
if (arr[i] === 0x00 && arr[i+1] === 0x21 &&
arr[i+2] === 0xF9 && arr[i+3] === 0x04 &&
arr[i+8] === 0x00 &&
(arr[i+9] === 0x2C || arr[i+9] === 0x21))
{
frames++;
}
}
// if frame count > 1, it's animated
cb(frames > 1);
});
request.send();
}
@atk

This comment has been minimized.

Copy link

commented Jun 29, 2012

Smaller solution (inside xhr callback):

var img = request.response;
if (!/^GIF8[79]a/) { cb(false); } else {
var frames = 0;
img.replace(/\x00\x21\xF9\x04\x00[\x2c\x21]/g, function(){ frames++ });
cb(frames>1);
}

In addition, this only works if the domain origin of the image is the same as the page you are loading the script from.

@sathio

This comment has been minimized.

Copy link

commented Jul 12, 2013

looks like it's never satisfying the for conditions if the GIF is not animated.
can't understand why

@olapeter

This comment has been minimized.

Copy link

commented Oct 31, 2013

It is because multiple comma-separated conditions in a for loop like this

for (i=0, len = length - 9; i < len, frames < 2; ++i)

will evaluate both checks, but use only the last one for determining whether to run the loop again. So unless more than one frame is found, i will just grow and frames < 2 never satisfy.

In this case, better to use || (or have an inner loop)

@marckubischta

This comment has been minimized.

Copy link

commented Jul 9, 2014

Implemented @olapeter's suggestion here, only using && since the logic is inverted between the comment and the if statement.

Also updated to support variable length extension control blocks.

@marckubischta

This comment has been minimized.

Copy link

commented Jul 10, 2014

N.B. the img.replace(/\x00\x21\xF9\x04\x00[\x2c\x21]/g solution suffers the same problem as the original code here -- if the ECB isn't 4 bytes this regex won't find the frame (see the wikipedia article examples).

@okramovic

This comment has been minimized.

Copy link

commented Aug 19, 2018

great rubust solution from @marckubischta ! thanks for that.
anyone able to come up with detecting animation for webp image format?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.