@openlee/gif-maker
v1.0.1
Published
One-function GIF maker — images or video, auto-detected. Lightweight, zero-config.
Maintainers
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-makerNode.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)(黑色) |
性能优化
浏览器编码器包含多项性能优化:
- 全局调色板 —
quantize()对所有帧只执行一次(而非逐帧执行) - O(1) 查找表 — RGB565 → 调色板索引映射,替代逐像素最近色搜索
- 单趟扫描 — 透明度检测 + 像素采样在同一次遍历中完成
- 零拷贝快速路径 — 无透明帧跳过标记色转换
- 扁平调色板 — 平行类型数组避免热循环中的二维数组间接寻址
- Buffer 复用 — 索引缓冲区跨帧共享
- 内联 clamp — 消除抖动热循环中的函数调用开销
- 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
