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

custom-vap

v1.1.0

Published

A lightweight, WebGL-powered video animation player (VAP) for Vue 3 & modern web. Supports real-time RGB/Alpha channel compositing, VAPX blend-layer image/text replacement, precise frame rendering (rVFC), and IndexedDB data persistence. ~30KB gzipped, zer

Readme

🎬 Custom VAP

A lightweight, WebGL-powered video animation player (VAP) for Vue 3 & modern web.

支持视频 Alpha 通道实时合成 · VAPX 融合层动态图片/文本替换 · 精确帧渲染 · TypeScript 全量类型

npm version npm downloads bundle size license TypeScript

⭐ 给个 Star 支持一下

如果这个项目对你有帮助,请点击右上角的 ⭐ Star 按钮,你的支持是我持续维护的动力!

Star this repo


✨ Features

| 特性 | 说明 | | --- | --- | | 🎞️ 视频 Alpha 合成 | 基于 WebGL 将 MP4 视频的 RGB 通道与 Alpha 通道实时合成,实现透明动效 | | 🧩 VAPX 融合层 | 支持在视频上叠加图片 / 文本插槽(slot),实现动态内容替换 | | 🎯 精确帧渲染 | 支持 requestVideoFrameCallback(rVFC)精准同步视频帧渲染,兼容环境自动回退 RAF | | 🖼️ 运行时图片替换 | 不重编 VAP 即可替换融合层图片图层(支持 URL 动态加载) | | 📝 动态文本注入 | 在动画播放中实时替换文本图层 | | 👆 插槽点击事件 | 支持指定插槽 / 全局插槽的点击回调 | | 💾 数据持久化 | 基于 IndexedDB 缓存 VAP 解析结果,减少重复网络请求 | | 👀 可见性监听 | 多实例共享 IntersectionObserver,支持移出视口时暂停 / 跳过渲染 | | 🎬 帧范围播放 | 支持播放任意帧区间并自定义循环次数 | | 🌐 多产物输出 | ESM / CJS / UMD 全场景覆盖(npm、script 标签、CDN) | | 🎯 TypeScript 一等公民 | 完整 .d.ts 类型定义,IDE 智能提示开箱即用 |


📥 Installation

# npm
npm install custom-vap

# yarn
yarn add custom-vap

# pnpm
pnpm add custom-vap

浏览器 CDN(无构建工具)

<!-- unpkg -->
<script src="https://unpkg.com/custom-vap@latest/dist/index.umd.js"></script>

<!-- jsDelivr -->
<script src="https://cdn.jsdelivr.net/npm/custom-vap@latest/dist/index.umd.js"></script>

<script>
  const vap = new CustomVap(document.getElementById('stage'), { autoplay: true });
  vap.load('/path/to/video.mp4');
</script>

🧪 演示 Demo

仓库内置 Vite 示例工程,可在线验证核心能力(含 VAPX 动态资源插槽测试)。

# 首次需安装 demo 依赖
npm install --prefix demo/vite-rollup-esm

# 启动开发服务(热更新,alias 直连包源码 src/,改动即时生效)
npm run dev --prefix demo/vite-rollup-esm

浏览器打开 http://localhost:5173/,顶栏可切换页面:

| 页面 | 说明 | | --- | --- | | /single 单例测试 | 加载 / 播放 / 暂停 / 停止 / 销毁 / 帧范围 / 结束帧模式 / 海报等;加载 VAPX 视频后底部出现「动态资源」面板,可对每个插槽填入图片 URL 或文本,点击「应用动态资源」即调 setImage / setText / setDynamicResources | | /stress 多实例压测 | 同时加载多个实例,验证多实例共享渲染调度与可见性优化 |

默认演示素材为远程 VAP 视频(vap0.mp4);本地文件可点击「选择本地文件」或拖拽到舞台直接播放。


🚀 Quick Start

Vue 3 组件用法

<template>
  <div ref="container" class="vap-stage"></div>
</template>

<script setup lang="ts">
import { ref, onMounted, onBeforeUnmount } from 'vue';
import { CustomVap } from 'custom-vap';

const container = ref<HTMLDivElement>();
let player: CustomVap | null = null;

onMounted(async () => {
  if (!container.value) return;

  player = new CustomVap(container.value, {
    autoplay: true,   // 加载后自动播放
    loop: 0,          // 无限循环
  });

  // 方式一:on(key, cb),支持同事件多监听
  player.on('start', () => console.log('播放开始'));
  player.on('process', ({ frame }) => console.log('当前帧:', frame));
  player.on('end', () => console.log('播放结束'));

  // 方式二:onLoad / onReady / onStart / onPause / onResume / onLoop /
  // onStop / onEnd / onError / onVisible / onDestroy 单监听(process 与
  // webglcontext* 不提供),重复调用自动替换旧回调,返回取消函数便于清理
  const offEnd = player.onEnd(({ frame }) => console.log('播放结束于第', frame, '帧'));

  await player.load('/path/to/video.mp4');
});

onBeforeUnmount(() => {
  player?.destroy();
});
</script>

<style scoped>
.vap-stage {
  width: 300px;
  height: 300px;
}
</style>

原生 JavaScript 用法

import { CustomVap } from 'custom-vap';

const player = new CustomVap(document.getElementById('stage'), {
  loop: 3,
});

await player.load('/path/to/video.mp4');
player.play();

传入 Canvas 元素

import { CustomVap } from 'custom-vap';

// 直接传入已有 canvas 元素
const player = new CustomVap(canvasEl, { loop: 0 });
await player.load('/path/to/video.mp4');
player.play();

📚 API 文档

new CustomVap(el, options?)

创建播放器实例。传入 div 时内部自动创建 canvas,传入 canvas 时直接使用该画布。

| 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | el | HTMLDivElement \| HTMLCanvasElement | ✅ | 挂载容器节点或 Canvas 元素 | | options | VapOptions | ❌ | 初始化配置 |

VapOptions 配置项

| 字段 | 类型 | 默认值 | 说明 | | --- | --- | --- | --- | | loop | number | 1 | 循环次数(0 或负数 = 无限循环) | | autoplay | boolean | false | 加载后自动播放 | | muted | boolean | false | 是否静音 | | volume | number | 1.0 | 音量(0~1) | | fps | number | - | 目标帧率,默认使用视频原始帧率 | | endFrameMode | 'Forward' \| 'Backward' \| 'Clearward' | 'Backward' | 播放结束行为:Backward 停在尾帧、Forward 回到首帧、Clearward 清空画布 | | dataPersistence | boolean | false | 是否开启 IndexedDB 数据持久化缓存 | | enableBlob | boolean | true | 是否将视频转为 Blob URL 播放 | | isPreciseFrame | boolean | true | 是否使用 rVFC 精确帧渲染(不支持时自动回退独立 RAF) | | isClickable | boolean | false | 是否启用 VAPX 融合层插槽点击事件 | | invisibleBehavior | 'pause' \| 'skip' | 'skip' | 移出视口时的行为:pause 自动暂停、skip 跳过渲染保持播放 |

CustomVap 实例方法

| 方法 | 说明 | | --- | --- | | load(url) | 加载并解析 VAP 视频,返回 Promise<this> | | play(loop?) | 播放动效,loop 可覆盖配置项,返回 Promise<this> | | playRange(startFrame, endFrame, loop?) | 播放指定帧范围,返回 Promise<this> | | pause() | 暂停播放 | | resume() | 恢复播放 | | stop() | 停止播放,按 endFrameMode 决定结束帧表现 | | destroy() | 彻底销毁实例,释放视频 / WebGL / 事件等资源 | | resize() | 手动重新计算画布物理尺寸(跟随容器与 DPR) | | setImage(tag, url) | 通过 URL 设置融合层图片资源,返回 Promise<this> | | setText(tag, text) | 设置融合层文本资源 | | setDynamicResources(headData) | 批量设置动态资源,支持 [tag] 通配匹配,返回 Promise<this> | | updateOptions(newOptions) | 动态合并更新配置 | | on(event, callback) | 监听事件(多监听并存),返回 this | | off(event, callback) | 移除事件监听,返回 this | | onLoad(callback) | 便捷单监听 load(重复调用自动替换),返回取消函数 | | onReady(callback) | 便捷单监听 ready(重复调用自动替换),返回取消函数 | | onStart(callback) | 便捷单监听 start(重复调用自动替换),返回取消函数 | | onLoop(callback) | 便捷单监听 loop(重复调用自动替换),返回取消函数 | | onPause(callback) | 便捷单监听 pause(重复调用自动替换),返回取消函数 | | onResume(callback) | 便捷单监听 resume(重复调用自动替换),返回取消函数 | | onStop(callback) | 便捷单监听 stop(重复调用自动替换),返回取消函数 | | onEnd(callback) | 便捷单监听 end(重复调用自动替换),返回取消函数 | | onError(callback) | 便捷单监听 error(重复调用自动替换),返回取消函数 | | onVisible(callback) | 便捷单监听 visible(重复调用自动替换),返回取消函数 | | onDestroy(callback) | 便捷单监听 destroy(重复调用自动替换),返回取消函数 | | onClick(srcTag, callback) | 注册指定插槽的点击事件 | | onAllClick(callback) | 注册所有插槽通用的点击事件 |

CustomVap 事件回调

| 事件 | 载荷 | 触发时机 | | --- | --- | --- | | load | { url, config } | 视频加载解析完成 | | ready | { currentTime } | 视频已就绪可播放(<video> canplaythrough) | | start | { currentTime } | 播放开始 | | process | { frame, currentTime } | 每渲染一帧触发 | | loop | { currentTime, loopCount } | 每轮循环回到起点重新播放(loopCount 为当前开始的第几遍,自 2 起) | | pause | { currentTime } | 播放暂停 | | resume | { currentTime } | 播放恢复 | | stop | { currentTime } | 播放停止 | | end | { currentTime, frame } | 播放结束 | | error | { error, message } | 加载 / 播放出错 | | webglcontextlost | { message } | WebGL 上下文丢失 | | webglcontextrestored | { message } | WebGL 上下文恢复 | | visible | { isVisible, playState } | 容器可见性变化 | | destroy | { currentTime, playState } | 实例销毁前触发(资源尚未释放) |


🎯 高级用法

1. 帧范围播放

// 只播放第 10~50 帧,循环 3 次
await player.playRange(10, 50, 3);

2. 动态替换图片图层

// 替换融合层指定插槽的图片
await player.setImage('avatar', 'https://example.com/new-avatar.png');

3. 动态注入文本

// 简单文本替换:第一个参数是插槽 tag,第二个是文本内容
player.setText('username', 'VIP 用户');

4. 批量设置动态资源

// 支持 [tag] 通配:资源里的 [username] 会匹配 headData 中的 username
await player.setDynamicResources({
  username: 'DreamLife',
  avatar: 'https://example.com/avatar.png',
});

5. 融合层插槽点击事件

const player = new CustomVap(container, { isClickable: true });

// 指定插槽点击
player.onClick('avatar', () => console.log('点击了 avatar 插槽'));

// 全局插槽点击(可拿到 srcId / srcTag / 点击区域坐标)
player.onAllClick(({ srcId, srcTag, frame }) => {
  console.log('点击插槽:', srcId, srcTag, frame);
});

6. 可见性优化

const player = new CustomVap(container, {
  invisibleBehavior: 'pause', // 移出视口时自动暂停,回到视口自动恢复
  // invisibleBehavior: 'skip', // 移出视口时跳过渲染但保持帧同步(默认)
});

7. 结束帧模式

const player = new CustomVap(container, {
  endFrameMode: 'Forward',   // 结束回到第一帧
  // endFrameMode: 'Backward',  // 结束停在最后一帧(默认)
  // endFrameMode: 'Clearward', // 结束清空画布
});

8. 动态更新配置

player.updateOptions({ muted: true, loop: 0 });

📊 特性一览

| 场景 | 说明 | | --- | --- | | 精确帧 | 支持 rVFC 的浏览器自动使用精准帧驱动,否则回退每个实例独立的 RAF | | 数据持久化 | 开启后解析结果自动写入 IndexedDB,二次加载秒开 | | 上下文丢失 | 自动监听 WebGL 上下文丢失 / 恢复,并重建着色器与纹理 | | 多实例共享 | 可见性观察器为多实例共享,节省开销;渲染循环各实例独立互不抢占 | | 自动播放拦截 | 浏览器拦截自动播放时,自动降级为静音播放 |


🌐 浏览器兼容性

| 特性 | 最低支持 | | --- | --- | | WebGL 渲染 | 所有支持 WebGL 1.0+ 的现代浏览器 | | rVFC 精确帧 | Chrome / Edge 87+,不支持时自动回退 RAF | | IntersectionObserver 可见性监听 | Chrome / Edge 51+,Firefox 55+,Safari 12.1+ | | IndexedDB 持久化 | 所有现代浏览器(不支持时自动跳过) |


🔧 常见问题

  • 请确认容器具有非零宽高,且容器尺寸在 load 时已就绪
  • 播放器会在 load / start 等生命周期自动执行 resize(),若容器尺寸变化可手动调用 player.resize()

播放器内置自动播放降级策略:当浏览器阻止有声自动播放时,会自动切换为静音模式重试。也可在初始化时直接设置 muted: true

const player = new CustomVap(container, { autoplay: true, muted: true });
// 播放第 1~30 帧,循环 2 次
await player.playRange(1, 30, 2);

无需手动处理。播放器已监听 webglcontextlost / webglcontextrestored 事件,恢复后自动重建着色器程序、缓冲与纹理,并触发对应事件通知。

const player = new CustomVap(container, {
  invisibleBehavior: 'pause', // 移出视口自动暂停,回到视口自动恢复
});

🤝 贡献

欢迎 PR 和 Issue!请确保:

  • 代码风格一致(TypeScript strict)
  • 提交前 npm run build:rollup 能成功
  • 重大变更请先开 Issue 讨论

📄 License

MIT © 2026 DreamLife(Zhou)

🙏 致谢

  • VAP - 腾讯视频云动画播放方案(VAP/VAPX)

⭐ 喜欢这个项目?在 GitHub 上给我们一个 Star 吧!你的支持就是最大的动力!

GitHub stars