npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

react-animated-favicon

v1.1.2

Published

Zero-dependency React library for animated GIF favicons, entirely client-side

Readme

react-animated-favicon

npm version npm downloads npm license GitHub stars

Zero-dependency React library for animated GIF favicons, fully client-side.

react-animated-favicon decodes GIF frames in the browser, renders them on a hidden canvas, and swaps <link rel="icon"> with data: URLs on each tick. No server rendering required.

Installation

npm install react-animated-favicon

Quick Start

import { AnimatedFavicon } from "react-animated-favicon";

export function App() {
  return (
    <AnimatedFavicon
      url="https://example.com/loader.gif"
      fallbackUrl="/favicon.ico"
    />
  );
}

Hook Usage

import { useAnimatedFavicon } from "react-animated-favicon";

export function FaviconControls() {
  const {
    currentFrame,
    frameCount,
    isPlaying,
    isLoading,
    error,
    play,
    pause,
    stop,
    goToFrame,
  } = useAnimatedFavicon("https://example.com/loader.gif", {
    autoPlay: true,
    maxFps: 15,
    fallbackUrl: "/favicon.ico",
  });

  if (isLoading) return <p>Loading favicon...</p>;
  if (error) return <p>Failed to load animated favicon.</p>;

  return (
    <div>
      <p>
        Frame {currentFrame + 1} / {frameCount}
      </p>
      <button onClick={play} disabled={isPlaying}>
        Play
      </button>
      <button onClick={pause} disabled={!isPlaying}>
        Pause
      </button>
      <button onClick={stop}>Stop</button>
      <button onClick={() => goToFrame(0)}>First frame</button>
    </div>
  );
}

Component + Context API

import {
  GifFaviconProvider,
  useGifFaviconContext,
} from "react-animated-favicon";

function Controls() {
  const { isPlaying, play, pause } = useGifFaviconContext();
  return (
    <button onClick={isPlaying ? pause : play}>
      {isPlaying ? "Pause" : "Play"}
    </button>
  );
}

export function App() {
  return (
    <GifFaviconProvider
      url="https://example.com/loader.gif"
      options={{ fallbackUrl: "/favicon.ico", useWorker: true }}
    >
      <Controls />
    </GifFaviconProvider>
  );
}

Utilities (No React Dependency)

import { preloadGif, frameToDataUrl } from "react-animated-favicon";

const frames = await preloadGif("https://example.com/loader.gif");
const firstFramePngDataUrl = frameToDataUrl(frames[0], 32);

Useful for preloading, transformations, or generating a visual fallback from frame 0.

API

useAnimatedFavicon(url, options?)

Aliases are also exported:

  • useGifFavicon
  • GifFavicon (AnimatedFavicon alias)
  • GifFaviconProvider (AnimatedFaviconProvider alias)
  • useGifFaviconContext

Returns:

  • frames: GifFrame[]
  • currentFrame: number
  • frameCount: number
  • isPlaying: boolean
  • isLoading: boolean
  • error: Error | null
  • play(): void
  • pause(): void
  • stop(): void
  • goToFrame(index: number): void
  • destroy(): void

Options

| Option | Type | Default | Description | | ------------------ | ------------------ | ----------- | ---------------------------------------------------------------- | | autoPlay | boolean | true | Start animating immediately after load. | | fps | number | undefined | Force fixed FPS, overriding GIF delay values. | | maxFps | number | 15 | Hard cap for playback speed. | | maxFrames | number | 60 | Truncate decoded frames beyond this count. | | maxFileSize | number | 5242880 | Warn (via onWarning) if GIF exceeds this size in bytes. | | fallbackUrl | string | undefined | Static favicon URL used on failures or unsupported environments. | | pauseOnHidden | boolean | true | Pause animation while tab is hidden. | | restoreOnUnmount | boolean | true | Restore original favicon when unmounted/destroyed. | | useWorker | boolean | false | Offload fetch/parse to a Web Worker when available. | | onLoad | (frames) => void | — | Called after frames are decoded. | | onError | (err) => void | — | Called on network/parse/runtime failures. | | onWarning | (msg) => void | — | Called for non-fatal issues (size/browser fallback). | | onFrameChange | (index) => void | — | Called on each frame tick. |

GifFrame

interface GifFrame {
  index: number;
  patch: Uint8ClampedArray;
  dims: { width: number; height: number; top: number; left: number };
  delay: number;
}

Web Worker Support

Set useWorker: true to parse GIFs off the main thread:

useAnimatedFavicon(url, { useWorker: true });
  • Falls back to main-thread parsing when Worker features are unavailable.
  • Uses transferable ArrayBuffers for efficient frame transfer.
  • OffscreenCanvas can be used as progressive enhancement on supported browsers.

SSR and Next.js

The library is SSR-safe: all DOM work runs in useEffect.
For Next.js App Router, use the component inside a client boundary:

"use client";

import { AnimatedFavicon } from "react-animated-favicon";

export default function ClientFavicon() {
  return (
    <AnimatedFavicon
      url="https://example.com/loader.gif"
      fallbackUrl="/favicon.ico"
    />
  );
}

CORS

GIF fetching follows browser CORS rules. If the GIF host blocks cross-origin requests, parsing fails and onError runs. Use a CORS-enabled source or your own proxy.

Example Cloudflare Worker proxy:

export default {
  async fetch(request) {
    const url = new URL(request.url);
    const target = url.searchParams.get("url");
    if (!target) return new Response("Missing ?url=", { status: 400 });

    const res = await fetch(target, {
      headers: { "User-Agent": "react-animated-favicon-proxy" },
    });

    const headers = new Headers(res.headers);
    headers.set("Access-Control-Allow-Origin", "*");
    headers.set("Cache-Control", "public, max-age=3600");

    return new Response(res.body, {
      status: res.status,
      statusText: res.statusText,
      headers,
    });
  },
};

Then pass the proxied URL to the library.

Browser Support

| Browser | Support | Notes | | ------------- | ----------- | ----------------------------------------- | | Chrome 90+ | Full | Reliable up to ~15 FPS. | | Firefox 88+ | Full | Reliable up to ~10 FPS. | | Edge 90+ | Full | Chromium behavior. | | Safari 15+ | Partial | Rapid updates may be ignored. | | Safari < 15 | No | Falls back to fallbackUrl. | | Mobile Chrome | Best effort | Favicon visibility depends on UI surface. | | Mobile Safari | No | Falls back to fallbackUrl. |

Performance and Memory

  • Default pauseOnHidden: true avoids background tab timer throttling.
  • Frame RAM is roughly width * height * 4 bytes per frame.
  • For large GIFs, lower memory use with maxFrames and/or a lower fps.
  • Recommended upper playback bound is about 15 FPS for stable favicon updates.

TypeScript

Type declarations are published with the package.
Common exports:

  • UseAnimatedFaviconOptions
  • UseAnimatedFaviconResult
  • GifFrame
  • ParseResult

Contributing

npm install
npm run lint
npm test
npm run build

Project includes an example app for local verification.