custom-svga
v2.3.0
Published
A lightweight, tree-shakable SVGA animation player for Vue 3 & modern web. Isomorphic inline worker parsing with main-thread MockWorker fallback, in-flight sprite/text replacement. Zero UI framework deps.(面向 Vue3 与现代浏览器的轻量 SVGA 动画播放器。同构内联 Worker 解析 + 主线程
Maintainers
Readme
custom-svga
轻量、可摇树的 SVGA 动画播放器,面向 Vue3、现代浏览器与移动端。
⭐ 如果这个项目对你有帮助,欢迎在 GitHub 上点亮 Star 支持!
极致轻量 · 移动端更高效 · 同构内联 Worker 解析 · 动态精灵/文本替换 · 零 UI 框架依赖- 同构内联 Worker 解析:真实 Web Worker 可用时自动在 Worker 内解析;不支持/受限时主线程
MockWorker(沙箱new Function)复用同一份代码降级,零配置。 - 构建期内联:解析代码在打包时编译为字符串内联进主包,单入口、无独立 worker 产物。
- 动态精灵/文本替换、海报、正/逆序、缩放适配、帧区间/播放至指定帧、循环与结束行为。
- 零 UI 框架依赖,仅构建期依赖
fflate;esm ~40KB / gzip ~13KB(小于官方 svga.lite)。
极致轻量 · 为移动端而生
- 体积极小、冷启动快:单包 gzip 仅 ~13KB、零运行时依赖,不引入任何 UI 框架与重型运行时,首帧可快速拉起,特别适合移动端低端机与包体敏感场景。
- 移动端更省电省内存:
- 多实例共享一个 RAF,按时间预算公平轮转,单实例卡顿不拖垮整页(
useSharedRAF); - Path2D 预缓存 / 帧位图缓存 / 动态文本位图化,把每帧重计算与重复光栅化降到最低,显著降低 GPU/CPU 负载;
- 视口不可见时自动暂停播放(
useIntersectionObserver),滚动列表外动画零开销。
- 多实例共享一个 RAF,按时间预算公平轮转,单实例卡顿不拖垮整页(
- 按需装配:解析与渲染均为函数式、可摇树(
sideEffects: false),按打包工具自动剔除未用代码,最终体积进一步收缩。
安装
npm i custom-svga
# 或 CDN <script src="https://unpkg.com/custom-svga"></script> → window.CustomSvga快速开始(Vue3)
<script setup>
import { ref, onBeforeUnmount } from 'vue'
import { Player, Parser } from 'custom-svga'
const canvas = ref(null)
let player = null
async function load() {
player?.destroy()
player = new Player(canvas.value, { loop: 0, scaleMode: 'AspectFit' })
player.onReady(() => player.play())
const movie = await Parser.loadUrl('https://example.com/anim.svga')
player.setVideoItem(movie)
}
onBeforeUnmount(() => player?.destroy())
</script>
<template><canvas ref="canvas"></canvas></template>Player
构造函数
new Player(element, config?)
// element: HTMLCanvasElement | HTMLDivElement(div 容器模式会自动创建并管理画布)方法
| 方法 | 说明 |
|---|---|
| setVideoItem(MovieData \| VideoUrlSource) | 装载动画数据(Parser 解析结果,或 { url, ...解析选项 } 对象——内部自动 Parser.loadUrl,解析选项平铺:useWorker/cache/useImageBitmap) |
| play(config?) | 开始/恢复播放,可带本次覆盖配置 |
| playRange(start, end, config?) | 播放指定帧区间 |
| pause() | 暂停,保持当前帧 |
| stop() | 停止并应用配置的结束行为(fillMode:末帧/首帧/清空) |
| skipRender(skip?) | 切换/设置是否跳过渲染(帧时间轴仍推进);不传参则切换 |
| stepToFrame(frame, andPlay?) | 跳转到指定帧 |
| stepToPercentage(p, andPlay?) | 跳转到进度(0~1) |
| setImage(img \| url, key) | 动态替换图层图片 |
| setText(text \| DynamicText, key) | 动态替换图层文本 |
| clearDynamic(key?) | 清除动态替换,不传 key 则清空全部 |
| renderPoster(url) | 外链图片作静态海报渲染到画布 |
| resize() | 按 DOM 容器与 DPR 校准画布尺寸 |
| clear() | 清空当前动画数据与画布(保留实例,可再 setVideoItem) |
| destroy() | 彻底销毁实例(画布/监听/内存),需重新 new Player |
事件
| 事件 | 回调参数 | 触发时机 |
|---|---|---|
| onReady | MovieData? | 数据就绪 |
| onError | Error | 运行/加载异常 |
| onStart / onPause / onStop | - | 对应生命周期 |
| onLoopEnd | - | 每轮循环结束(含最后一轮) |
| onEnd | - | 播放彻底结束(仅当配置有限 loop 达到上限停摆时触发;loop: 0 无限循环与手动 stop/pause 不触发) |
| onProcess | frame: number | 每推进一帧 |
| onPoster | PosterEventInfo | 海报展示/隐藏切换 |
Parser
静态方法(多个调用共用同一共享 Worker 线程):
Parser.load(source, options?) // source: url | File | Blob
Parser.loadUrl(url, options?)
Parser.loadFile(file, options?)
Parser.parseBuffer(buffer, options?)
Parser.delete(key) // 清除单个缓存
Parser.clear() // 清空全部缓存(保留共享 Worker)
Parser.destroy() // 清空全部缓存并释放共享 Worker(下次解析按需重建)实例化(每个实例持有独立 Worker,解析配置在实例化时一次性声明,方法不再逐次传 options):
const parser = new Parser({ useWorker: true, cache: true, autoClose: false })
await parser.loadUrl(url) // 与静态签名一致,但走独立 Worker 线程
await parser.load(file) // loadUrl / loadFile / parseBuffer 同理
parser.delete(key) / parser.clear() / parser.destroy()内存/文件信息(独立导出,内部复用全局缓存):
import { queryMemoryInfo } from 'custom-svga'
await queryMemoryInfo(url, true) // log=true 打印表格options / ParserConfig:useWorker(默认 true,false 强制主线程 MockWorker 解析)、useImageBitmap(默认 false,解码为 ImageBitmap)、cache(默认 true,LRU 缓存 20 条)。缓冲解析(loadFile/parseBuffer)需显式传 movieDataId 才会写入缓存(匿名缓冲无稳定键,不缓存以免污染 LRU);实例化时 movieDataId 也是实例级缓存键。实例额外支持 autoClose(默认 false):为 true 时每次解析成功即自动释放当前 Worker(适合低频/一次性解析,用完即释放线程)。
PlayConfig
| 配置 | 默认 | 说明 |
|---|---|---|
| loop | 0 | 循环次数,0=无限 |
| fps | 源文件 | 覆盖帧率 |
| fillMode | forwards | 播放结束行为:forwards末帧 / backwards首帧 / clear清空 |
| startFrame / endFrame | - | 播放区间 |
| direction | forward | 播放方向:forward / backward(逆序) |
| scaleMode | AspectFit | SCALE_MODE:AspectFit / AspectFill / Fill |
| useIntersectionObserver | true | 视口不可见优化 |
| invisibleBehavior | pause | 不可见时 pause(停走)或 skip(仅跳绘制,帧仍走) |
| useSharedRAF | true | 多实例共享一个 RAF |
| cacheFrames / maxCacheFrames | false / 100 | 帧缓存(位图化),适合大量 Shape/Clip |
| defaultPoster | - | 默认静态海报(未播放/出错/clear 时显示) |
| autoPlay | false | setVideoItem 装载完成后是否立即自动播放 |
同构 Worker 说明
- 解析代码唯一一份(
svga.worker),打包时由构建插件编译为 IIFE 字符串内联。 useWorker: true且浏览器支持 →new Worker(new Blob([code]))真子线程解析。- Worker 不可用 / 创建失败 →
new MockWorker(code):沙箱new Function('self', 'globalThis', 'postMessage', 'importScripts', code)主线程同构解析。 - 解析带 30s 超时与
onerror自愈兜底,避免 Promise 悬挂。静态方法共用共享 Worker;new Parser()每个实例持有独立 Worker,useWorker模式切换会重建 Worker。 - ⚠️ MockWorker 依赖
new Function,严格 CSP(无unsafe-eval)环境下无法降级;真实 Worker 需允许blob:。
动态替换示例
player.setText({ text: 'Hello', size: '18px', color: '#fff' }, 'avatar') // 替换图层 avatar 为文本
await player.setImage('https://example.com/face.png', 'avatar') // 或替换为图片
player.clearDynamic('avatar') // 清除单个
player.clearDynamic() // 清空全部常见问题
- 停止后没回到首帧?:
stop()按配置的fillMode决定末帧/首帧/清空;如需固定首帧请设fillMode:'backwards'。 - 暂停与停止区别:
pause()停在当前帧;stop()结束并应用fillMode。 - 体积:esm/cjs/umd 单文件,fflate 已内联进 worker 字符串,运行时不需额外依赖。
License
MIT © DreamLife(Zhou)
如果觉得好用,欢迎 Star ⭐,也欢迎提交 Issue / PR。
