@taole/giftstage
v0.1.21
Published
High-performance WebGPU/WebGL2 gift animation player unifying SVGA, VAP, and AlphaVideo
Readme
GiftStage
统一的 Web 礼物播放框架,支持:
SVGAVAP / VAPX- 透明视频
AlphaVideo
底层支持:
WebGPUWebGL2WebGL1兼容兜底
适用场景:
- 直播间礼物
- 大批同屏动画
- 需要统一接入
SVGA / VAP / 透明视频的业务场景
npm 包信息
- 包名:
@taole/giftstage - npm:
npm i @taole/giftstage - ESM 导入:
import { GiftStage } from '@taole/giftstage';- CommonJS 导入:
const { GiftStage } = require('@taole/giftstage');特性
- 统一 API:
addGift()即可播放三类礼物 SVGA支持 Worker 解析、atlas 缓存、slot 替换VAP / AlphaVideo支持实验性的WebCodecs和稳定的HTMLVideoElement双路径- 同源视频播放实例、纹理、资源复用
- 内存 LRU 缓存,默认
128MB IndexedDB磁盘 LRU 缓存,默认2048MBx / y支持百分比定位width / height支持按礼物原始尺寸自动补全
安装
npm install安装发布包:
npm i @taole/giftstage开发
npm run dev构建
npm run buildDemo 构建:
npm run build:demo快速开始
import { GiftStage } from '@taole/giftstage';
const container = document.getElementById('app')!;
const stage = new GiftStage({
container,
preferWebGPU: true,
});
await stage.ready;
await stage.addGift({
type: 'svga',
source: 'https://example.com/demo.svga',
x: 100,
y: 100,
loop: 0,
});核心 API
new GiftStage(options)
创建礼物舞台。
常用配置:
container: HTMLElement挂载容器。resolution?: number指定 canvas backing store 分辨率倍率。默认跟随devicePixelRatio。antialias?: boolean是否开启抗锯齿。preferWebGPU?: boolean是否优先使用WebGPU。forceWebGL1?: boolean强制走WebGL1,用于兼容性验证。preferWebCodecs?: boolean实验性特性。默认关闭;显式传true时才会尝试用WebCodecs播放VAP / AlphaVideo。shareIdenticalVideoPlayback?: boolean是否复用近同时起播的同源视频播放实例。默认开启。sharedVideoPlaybackWindowMs?: number同源视频共享播放窗口,默认100ms。webCodecsVideoAtlas?: { width: number; height: number }实验性WebCodecs视频共享 atlas 配置。webCodecsDecodeInWorker?: boolean实验性WebCodecs选项,控制是否在 Worker 中执行解码。webCodecsMaxDecodedFrames?: number实验性WebCodecs选项,控制保留的最大解码帧数。webCodecsDecodeAheadFrames?: number实验性WebCodecs选项,控制前向解码帧数。svgaParseInWorker?: boolean是否在 Worker 中解析 SVGA。maxConcurrentParse?: number资源下载 / 解析 / 图像解码的并发数。 不传时自动跟随navigator.hardwareConcurrency。onError?: (error: Error) => void统一错误回调。
await stage.ready
等待底层后端和运行环境初始化完成。建议在第一次 addGift() 前等待。
stage.addGift(options)
插入一个礼物,返回:
Promise<GiftHandle>;GiftHandle API
addGift() 返回的句柄可用于控制单个礼物:
gift.idgift.typegift.pause()gift.resume()gift.destroy()- 立即移除当前礼物
gift.animate(stepOrSteps?)- 创建链式动画并返回
GiftAnimationChain
- 创建链式动画并返回
GiftAnimationChain 支持:
.to(step)/.then(step)- 追加动画步骤(两者等价)
.delay(milliseconds)- 在链中插入等待,不改变位置、缩放和透明度
.onComplete((gift) => void)- 整条链执行完成后的回调
.start(mode?)mode: 'immediate' | 'afterGiftComplete'immediate:立即开始(默认)afterGiftComplete:等待礼物主播放结束后再执行链
.cancel()- 取消当前礼物正在执行的链式动画
stage.removeGift(id)
按 ID 移除礼物。
stage.removeAll()
移除全部礼物。
stage.pause()
暂停舞台。
stage.resume()
恢复舞台。
stage.destroy()
销毁舞台并释放资源。
addGift() 参数
通用参数
type: 'svga' | 'vap' | 'alphaVideo'source: string | ArrayBufferx: number | \${number}%``y: number | \${number}%``zIndex?: numberwidth?: numberheight?: numberuseOriginalSize?: booleanobjectFit?: 'contain' | 'cover' | 'fill'loop?: number0表示无限循环
opacity?: number- 全局透明度,范围
0 ~ 1,默认1
- 全局透明度,范围
clearsAfterStop?: boolean- 播放结束后,是否自动移除礼物
- 默认
true,传false时会停留最后一帧,方便后续继续调用gift.animate(...).start()
mute?: booleanonComplete?: (gift) => void
层级规则:
zIndex在SVGA / VAP / AlphaVideo三种礼物间通用- 数值越大,渲染越靠上
- 同层级下,后
addGift()的礼物会覆盖先添加的礼物
宽高默认行为
width / height 现在是可选参数,规则如下:
- 两个都传:按传入值显示
- 只传
width:height按礼物原始宽高比自动补全 - 只传
height:width按礼物原始宽高比自动补全 - 两个都不传:默认按当前画布尺寸做等比
contain - 如果
objectFit: 'cover'且两个都不传:按整个画布作为显示区域做等比cover - 也就是会在画布内尽可能放大或缩小,并保持礼物原始宽高比
- 如果
useOriginalSize: true,且礼物原始尺寸本身小于画布,则优先使用礼物原始尺寸 - 如果
useOriginalSize: true,但礼物原始尺寸超出画布,则仍会按画布尺寸等比缩小
例如:
await stage.addGift({
type: 'svga',
source: 'https://example.com/demo.svga',
x: '50%',
y: '50%',
loop: 0,
});这时会按舞台尺寸做等比适配,并保持礼物原始宽高比。
如果希望礼物铺满整个画布,并按中心裁剪超出的部分,可以使用 cover:
await stage.addGift({
type: 'svga',
source: 'https://example.com/demo.svga',
x: '50%',
y: '50%',
objectFit: 'cover',
loop: 0,
});当 x: '50%'、y: '50%' 且未传 width / height 时,显示区域会居中放在整个画布上;cover 会保持礼物原始宽高比铺满该区域,并从中心裁剪溢出的部分。
如果你希望“小礼物保持原始尺寸,大礼物再缩小”,可以这样:
await stage.addGift({
type: 'svga',
source: 'https://example.com/demo.svga',
x: '50%',
y: '50%',
useOriginalSize: true,
loop: 0,
});百分比坐标
x / y 支持百分比,例如:
await stage.addGift({
type: 'svga',
source: 'https://example.com/demo.svga',
x: '50%',
y: '50%',
width: 300,
height: 300,
loop: 0,
});语义是:
x: '50%'按容器宽度的50%定位,并减去自身一半宽度y: '50%'按容器高度的50%定位,并减去自身一半高度
也就是接近:
left: 50%;
top: 50%;
transform: translate(-50%, -50%);如果是数值,则继续按原来的像素坐标语义处理。
三类礼物示例
播放 SVGA
await stage.addGift({
type: 'svga',
source: 'https://example.com/demo.svga',
x: 20,
y: 20,
width: 300,
height: 300,
loop: 1,
});播放 VAP
await stage.addGift({
type: 'vap',
source: 'https://example.com/demo.mp4',
config: 'https://example.com/demo.json',
x: 20,
y: 20,
width: 400,
height: 220,
loop: 0,
});播放透明视频
await stage.addGift({
type: 'alphaVideo',
source: 'https://example.com/demo.mp4',
x: 20,
y: 20,
width: 400,
height: 220,
loop: 0,
});动画控制
链式动画
addGift() 返回的 GiftHandle 支持 animate()。一次 to() 是组合动画,同一步里可以同时移动、缩放和改变透明度;多个 to() / then() 会按顺序播放:
const gift = await stage.addGift({
type: 'svga',
source: 'https://example.com/demo.svga',
x: '50%',
y: '50%',
width: 300,
height: 300,
loop: 0,
});
await gift
.animate()
.to({
flyTo: { x: 200, y: 240 },
scaleTo: 0.8,
opacity: 0.6,
duration: 500,
})
.then({
flyTo: { x: 600, y: 320 },
scaleTo: 1.1,
opacity: 1,
duration: 700,
})
.delay(300)
.then({
opacity: 0,
duration: 400,
})
.onComplete((g) => {
console.log('chain complete', g.id);
})
.start();也可以传入单步或数组:
gift.animate({ flyTo: { x: 300, y: 300 }, scaleTo: 0.5, opacity: 0.3 }).start();
gift
.animate([
{ flyTo: { x: 300, y: 300 }, duration: 400 },
{ scaleTo: 1, opacity: 1, duration: 300 },
])
.start();Slot 替换
SVGA
使用 svgaSlots:
await stage.addGift({
type: 'svga',
source: 'https://example.com/demo.svga',
x: 0,
y: 0,
width: 400,
height: 400,
svgaSlots: {
avatar: { image: avatarImage },
title: {
text: 'Hello',
color: '#ff0000',
fontSize: 28,
mode: 'dynamic',
},
},
});支持:
TexImageSource- 文本配置
- 图片配置
SVGA 文本配置会按目标 frame 自动适配字号,避免文字超出槽位:
mode: 'dynamic':默认行为。生成文字自身尺寸的纹理,渲染时按自身逻辑尺寸居中到 frame 内,不会被拉伸。mode: 'replace':生成和 frame 一样大的纹理,按替换图逻辑铺满 frame。fontSize?: number:期望字号;如果文字放不下,会自动缩小。minFontSize?: number/maxFontSize?: number:限制自适应字号范围。padding?: number:文字纹理内边距,默认2。scale?: number:文字纹理栅格倍率,SVGA 默认3,用于保持清晰度。fontStyle?: string | { font?: string; color?: string }:字体样式,SVGA / VAP 都支持。
fontStyle 可以直接传 canvas font 字符串:
svgaSlots: {
title: {
text: 'Hello',
fontStyle: 'bold 40px Arial',
},
}也可以传对象:
vapSlots: {
welcome01: {
text: '欢迎进入房间',
fontStyle: {
font: 'bold 40px Arial',
color: '#ffffff',
},
},
}对象形式目前生效字段是 font 和 color。如果同时传了外层 color 和 fontStyle.color,以 fontStyle.color 为准。VAP 文本如果没有传 fontStyle,会回退使用 VAP 配置里的 src.fontStyle。
VAP / VAPX
使用 vapSlots:
await stage.addGift({
type: 'vap',
source: 'https://example.com/demo.mp4',
config: 'https://example.com/demo.json',
x: 0,
y: 0,
width: 400,
height: 400,
vapSlots: {
welcome01: '欢迎进入房间',
avatar_left: 'https://example.com/avatar.png',
},
});支持:
- 文本字符串
- 图片 URL
TexImageSource- 结构化文本 / 图片对象
后端策略
默认回退顺序:
WebGPUWebGL2WebGL1
说明:
WebGPU仅在浏览器支持相关必要能力时启用WebGL1主要用于兼容兜底,不以性能最优为目标- demo 中可手动强制切换到
WebGL1
缓存策略
内存缓存
- 默认
128MB - LRU 淘汰
缓存内容包括:
- 原始资源字节
- 文本 / JSON
SVGAworker payloadSVGAatlas 资产
IndexedDB 磁盘缓存
- 默认
2048MB - LRU 淘汰
- 读取命中会刷新最近访问时间
- 超出预算时按最久未使用记录淘汰
异常情况处理:
IndexedDB不可用、事务失败、写入失败、容量不足时,会自动降级- 损坏的
SVGA磁盘缓存会自动删除并重新生成
项目结构
主要目录:
src/核心源码docs/设计与汇总文档publish/发布脚本static/demo 依赖资源tests/测试
对外导出
入口文件:
主要导出:
GiftStagecreateBackendWebGPUBackendWebGL2BackendWebGL1BackendparseSVGAbuildAtlasparseVAPConfig- 相关类型定义
Demo
Demo 入口:
可用于验证:
SVGA / VAP / AlphaVideo播放- slot 替换
WebGPU / WebGL2 / WebGL1切换- 大批同屏压测
