JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 4
  • Score
    100M100P100Q94418F
  • License MIT

Detect whether an image (WebP, AVIF, APNG, GIF, JXL) is animated by inspecting its binary header.

Package Exports

  • @jiihpeeh/is-animated
  • @jiihpeeh/is-animated/avif
  • @jiihpeeh/is-animated/compose
  • @jiihpeeh/is-animated/gif
  • @jiihpeeh/is-animated/jxl
  • @jiihpeeh/is-animated/png
  • @jiihpeeh/is-animated/webp

Readme

is-animated

Detect whether an image is animated by inspecting its binary header.

Supports APNG, animated GIF, animated WebP, animated AVIF, and animated JPEG XL (JXL).
Pure JS, zero dependencies, works in browsers and Node.js.

Installation

npm install @jiihpeeh/is-animated

Usage

import { is_animated_blob, detect_format_blob } from 'is-animated';

const blob = await (await fetch('https://example.com/image.webp')).blob();

await is_animated_blob(blob);     // true | false
await detect_format_blob(blob);   // 'webp'

Only the first few KB are needed — the animation markers live in the file header.

API

is_animated_blob(blob: Blob, size?: number, formats?: string[]): Promise<boolean>

Convenience wrapper — reads the first size bytes from a Blob and checks for animation. size defaults to DEFAULT_HEADER_SIZE (4096). Pass an optional array to check only specific formats:

import { is_animated_blob, DEFAULT_HEADER_SIZE } from 'is-animated';
await is_animated_blob(blob);                        // check all formats
await is_animated_blob(blob, 4096, ['gif', 'webp']); // only GIF + WebP

detect_format_blob(blob: Blob, size?: number): Promise<'png' | 'gif' | 'webp' | 'avif' | 'jxl' | 'unknown'>

Reads the header from a Blob and returns the detected image format.

is_animated(data: Uint8Array, formats?: string[]): boolean

Low-level — accepts a Uint8Array header. Pass an optional array to restrict which formats are checked:

import { is_animated } from 'is-animated';

is_animated(data);                       // check all formats
is_animated(data, ['gif', 'webp']);      // only GIF + WebP
is_animated(data, ['jxl']);              // only JXL

Formats not listed are skipped. Unknown format names are silently ignored.

Format Detection method
APNG Scans for the acTL chunk after the PNG signature
GIF Counts image descriptors (0x2C) in the block structure
WebP Checks the VP8X chunk animation flag (bit 1) or looks for an ANIM chunk
AVIF Looks for moov or moof boxes in the ISOBMFF container
JXL Parses the codestream header to read the have_animation flag (raw codestream); counts jxlp boxes (container format)

detect_format(data: Uint8Array): 'png' | 'gif' | 'webp' | 'avif' | 'jxl' | 'unknown'

Low-level — detects format from raw bytes.

Individual format checkers

Each format's animated check is exported for direct use:

import {
  isAPNG,
  isAnimatedGIF,
  isAnimatedWebP,
  isAnimatedAVIF,
  isAnimatedJXL,
  isJXL,
} from 'is-animated';

Tree-shakable subpath imports

Import only the checkers you need — unused formats are dropped by bundlers:

import { isAnimatedGIF } from 'is-animated/gif';
import { isAnimatedWebP } from 'is-animated/webp';
import { isAnimatedAVIF } from 'is-animated/avif';
import { isAPNG } from 'is-animated/png';
import { isAnimatedJXL, isJXL } from 'is-animated/jxl';

Each subpath only bundles its own format code plus a minimal shared module.

compose(...checkers): (data: Uint8Array) => boolean

Combine multiple per-format checkers into a single function while keeping tree-shaking:

import { compose } from 'is-animated/compose';
import { isAnimatedGIF } from 'is-animated/gif';
import { isAnimatedWebP } from 'is-animated/webp';

const isAnimated = compose(isAnimatedGIF, isAnimatedWebP);
isAnimated(data); // true if either GIF or WebP is animated

Short-circuits — returns true on the first match.

Test

npm test

66 tests across 11 suites, including real encoded files generated by ffmpeg (lossy and lossless variants) and cjxl for JXL.

License

MIT