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

@openlee/gif-maker

v1.0.1

Published

One-function GIF maker — images or video, auto-detected. Lightweight, zero-config.

Readme

@openlee/gif-maker

一个函数搞定 GIF 生成 —— 图片或视频,自动识别。同时支持 浏览器Node.js

const gif = await toGif(['frame1.png', 'frame2.png', 'frame3.png']);

传入参数即可,输入类型自动识别,运行平台自动适配,开箱即用。

特性

  • 一个函数toGif() 同时处理图片和视频,自动识别输入类型
  • 全平台 — 浏览器返回 Blob,Node.js 返回 Buffer,API 完全一致
  • 零配置 — 默认参数即可用,也支持细粒度调节
  • 透明 GIF — 两端都支持 GCE 透明度处理
  • 性能优化 — 全局调色板、O(1) 查找表、零拷贝快速路径
  • 可取消 — 支持 AbortSignal 中止长时间操作
  • 进度回调 — 按阶段报告进度
  • TypeScript — 完整类型定义

安装

npm install @openlee/gif-maker

Node.js:视频转 GIF 需要系统安装 ffmpeg。图片转 GIF 无需任何外部依赖。

浏览器:零外部依赖。视频使用原生 <video> + Canvas 提取帧。

快速开始

浏览器 — 图片转 GIF

import { toGif } from '@openlee/gif-maker';

// 从 File 对象(如 <input type="file" multiple>)
const files = document.querySelector('input').files;
const gifBlob = await toGif(Array.from(files));

// 从 URL 加载
const gif = await toGif([
  'https://example.com/frame1.png',
  'https://example.com/frame2.png',
  'https://example.com/frame3.png',
]);

// 展示结果
const url = URL.createObjectURL(gifBlob);
document.querySelector('img').src = url;

浏览器 — 视频转 GIF

import { toGif } from '@openlee/gif-maker';

// 从 <input type="file"> 选择视频文件
const videoFile = document.querySelector('input').files[0];
const gif = await toGif(videoFile, {
  startTime: 2,      // 从第 2 秒开始
  duration: 3,       // 截取 3 秒
  fps: 10,           // 每秒 10 帧
  width: 320,        // 输出宽度 320px
  quality: 'medium',
});

Node.js — 图片转 GIF

import { toGif } from '@openlee/gif-maker';

const gifBuffer = await toGif(['./frame1.png', './frame2.png', './frame3.png']);

// 保存到文件
import { writeFile } from 'node:fs/promises';
await writeFile('output.gif', gifBuffer);

Node.js — 视频转 GIF

import { toGif } from '@openlee/gif-maker';

// 需要系统安装 ffmpeg
const gif = await toGif('./video.mp4', {
  startTime: 5,
  duration: 2,
  fps: 15,
  width: 480,
  quality: 'high',
});

API

toGif(input, options?)

将图片或视频转换为 GIF。输入类型自动识别。

返回值:浏览器返回 Promise<Blob>,Node.js 返回 Promise<Buffer>

支持的输入类型

| 类型 | 浏览器 | Node.js | |------|--------|---------| | string(URL) | fetch() | fetch() | | string(文件路径) | 抛错 | fs.readFile() | | string(data URI) | Base64 解码 | Buffer 解码 | | Uint8Array | 直接使用 | 直接使用 | | Buffer | — | 直接使用 | | File / Blob | 直接使用(视频零拷贝) | — | | 以上类型的 Array | 图片序列 | 图片序列 |

单个输入自动识别为图片或视频。数组输入始终视为图片序列。

配置项

| 参数 | 类型 | 默认值 | 说明 | |------|------|--------|------| | delay | number | 100 | 帧间隔毫秒数(仅图片,视频忽略) | | quality | 'low' \| 'medium' \| 'high' | 'medium' | 输出质量 | | width | number | 自动 | 输出宽度(px) | | height | number | 自动 | 输出高度(px) | | fit | 'cover' \| 'stretch' \| 'original' | 'cover' | 源图到目标尺寸的适配方式 | | startTime | number | 0 | 视频起始时间(秒) | | duration | number | 全部 | 视频截取时长(秒) | | fps | number | 10 | 视频帧率 | | loop | number | 0 | 循环次数(0 = 无限循环) | | onProgress | (info) => void | — | 进度回调 | | signal | AbortSignal | — | 取消操作 |

质量档位

| 档位 | 抖动 | 说明 | |------|------|------| | low | 无 | 最快,文件最小,色彩平涂 | | medium | Floyd-Steinberg | 质量与速度平衡 | | high | Floyd-Steinberg | 最佳色彩精度,速度较慢 |

适配模式

| 模式 | 说明 | |------|------| | cover | 居中裁剪到目标尺寸(保持宽高比,可能裁掉边缘) | | stretch | 拉伸填充(可能变形) | | original | 不缩放,居中放置在最大帧尺寸的透明画布上 |

进度回调

toGif(inputs, {
  onProgress({ phase, current, total }) {
    console.log(`${phase}: ${current}/${total}`);
  }
});

阶段:'image-decode' | 'video-extract' | 'encode'

取消操作

const controller = new AbortController();

toGif(inputs, { signal: controller.signal });

// 随时取消
controller.abort();

isVideo(data, mime?, ext?)

检测输入数据是否为视频格式。按优先级依次检查:魔数字节(MP4/WebM/AVI/MOV/MKV)→ MIME 类型 → 文件扩展名。

import { isVideo } from '@openlee/gif-maker';

isVideo(uint8Array);              // 检查魔数字节
isVideo(data, 'video/mp4');       // 检查 MIME 类型
isVideo(data, undefined, '.mp4'); // 检查扩展名

工作原理

架构

输入 → 自动识别 → 帧提取 → GIF 编码 → 输出
         │           │          │
      图片/视频    RGBA 帧     二进制 GIF

平台差异

| | 浏览器 | Node.js | |---|---|---| | 图片处理 | Canvas API + createImageBitmap | jimp(纯 JS) | | 视频提取 | <video> + Canvas(原生) | ffmpeg(stdin 管道) | | GIF 编码 | gifenc(~8KB 内联) | gif-encoder-2 | | 返回值 | Blob | Buffer | | 透明标记色 | (0xFE, 0x01, 0xFE) | (0, 0, 0)(黑色) |

性能优化

浏览器编码器包含多项性能优化:

  1. 全局调色板quantize() 对所有帧只执行一次(而非逐帧执行)
  2. O(1) 查找表 — RGB565 → 调色板索引映射,替代逐像素最近色搜索
  3. 单趟扫描 — 透明度检测 + 像素采样在同一次遍历中完成
  4. 零拷贝快速路径 — 无透明帧跳过标记色转换
  5. 扁平调色板 — 平行类型数组避免热循环中的二维数组间接寻址
  6. Buffer 复用 — 索引缓冲区跨帧共享
  7. 内联 clamp — 消除抖动热循环中的函数调用开销
  8. MessageChannel 让渡 — 比 setTimeout(0) 更快的时间分片

在线示例

项目包含一个纯前端 Demo,位于 demo/ 目录:

cd demo && npm install
npm run dev    # → http://localhost:3000

环境要求

| | 浏览器 | Node.js | |---|---|---| | 图片 | 任意现代浏览器 | Node.js >= 16 | | 视频 | 任意现代浏览器 | Node.js >= 16 + ffmpeg |

作者

Jas.lee[email protected]

许可证

MIT