主题
示例
1. 礼物滚动榜(已安装挂件 · 纯 HTML + JS)
最简形态:一个 manifest.json + 一个 index.html,引入 SDK 后监听礼物事件。
1.1 gift-roll/manifest.json
json
{
"manifestVersion": 1,
"id": "gift-roll",
"name": "礼物滚动榜",
"version": "1.0.0",
"author": "主播酱官方",
"description": "把礼物记录做成自动滚动的列表",
"sdkVersion": 1,
"defaultSize": { "width": 380, "height": 560 },
"minSize": { "width": 200, "height": 200 },
"thumbnail": "thumbnail.png",
"permissions": ["gifts"],
"config": [
{ "key": "title", "type": "text", "label": "标题",
"default": "礼物榜", "group": "基础" },
{ "key": "max", "type": "slider", "label": "最多条数",
"default": 20, "min": 5, "max": 50, "step": 1, "group": "基础" },
{ "key": "showIcon", "type": "switch", "label": "显示礼物图标",
"default": true, "group": "基础" }
],
"theme": [
{ "key": "accent", "type": "color", "label": "强调色",
"default": "#63e2b7", "alpha": true }
]
}1.2 gift-roll/index.html
html
<!doctype html>
<html lang="zh">
<head>
<meta charset="utf-8" />
<style>
html, body {
margin: 0; height: 100%;
background: transparent; overflow: hidden;
font-family: system-ui, sans-serif; color: #fff;
}
h3 {
margin: 0; padding: 10px 14px; font-size: 18px;
color: var(--zbj-accent);
}
ul { list-style: none; margin: 0; padding: 0 14px; }
li {
display: flex; align-items: center; gap: 8px;
padding: 6px 0; animation: enter .35s ease;
}
li img { width: 28px; height: 28px; border-radius: 6px; }
.name { font-weight: 600; }
.gift { opacity: .85; }
@keyframes enter { from { opacity: 0; transform: translateX(-12px); } }
</style>
</head>
<body>
<h3 id="title"></h3>
<ul id="list"></ul>
<script src="/widget-sdk/zbj-widget-sdk.js"></script>
<script>
ZBJ.ready(() => {
const titleEl = document.getElementById("title");
const listEl = document.getElementById("list");
function applyConfig() {
titleEl.textContent = ZBJ.config.title;
}
applyConfig();
ZBJ.onConfigChange(applyConfig);
ZBJ.on("gift", (e) => {
const li = document.createElement("li");
if (ZBJ.config.showIcon && e.giftIcon) {
const img = document.createElement("img");
img.src = e.giftIcon;
li.appendChild(img);
}
const name = document.createElement("span");
name.className = "name";
name.textContent = e.nickname;
const gift = document.createElement("span");
gift.className = "gift";
gift.textContent = `送出 ${e.giftName} ×${e.giftCount}`;
li.append(name, gift);
listEl.prepend(li);
while (listEl.children.length > Number(ZBJ.config.max)) {
listEl.lastChild.remove();
}
});
});
</script>
</body>
</html>注意 h3 的 color: var(--zbj-accent):这是 CSS 变量桥 的用法——theme.accent 字段的值会自动写入 --zbj-accent,挂件无需写 JS 即可响应主题变更。
2. 弹幕墙(零 JS · 仅 HTML + CSS + SDK)
如果挂件功能只是「按配置改样式」,可以完全不写自定义 JS:
2.1 danmaku-wall/manifest.json
json
{
"manifestVersion": 1,
"id": "danmaku-wall",
"name": "弹幕墙",
"version": "1.0.0",
"author": "主播酱官方",
"sdkVersion": 1,
"defaultSize": { "width": 480, "height": 720 },
"config": [
{ "key": "fontSize", "type": "slider", "label": "字号",
"default": 16, "min": 12, "max": 48, "step": 1 }
],
"theme": [
{ "key": "bg", "type": "color", "label": "背景色",
"default": "rgba(0,0,0,0.4)", "alpha": true },
{ "key": "text", "type": "color", "label": "文字颜色",
"default": "#ffffff" }
]
}2.2 danmaku-wall/index.html
html
<!doctype html>
<html lang="zh">
<head>
<meta charset="utf-8" />
<style>
html, body { margin: 0; background: transparent; overflow: hidden; }
body {
padding: 12px;
background: var(--zbj-bg);
color: var(--zbj-text);
font-size: calc(var(--zbj-fontSize) * 1px);
font-family: system-ui, sans-serif;
}
ul { list-style: none; margin: 0; padding: 0; }
li { padding: 4px 0; }
</style>
</head>
<body>
<ul id="list"></ul>
<script src="/widget-sdk/zbj-widget-sdk.js"></script>
<script>
ZBJ.ready(() => {
const list = document.getElementById("list");
ZBJ.on("chat", (e) => {
const li = document.createElement("li");
li.textContent = `${e.nickname}:${e.content}`;
list.prepend(li);
while (list.children.length > 30) list.lastChild.remove();
});
});
</script>
</body>
</html>3. 开发者模式
无需安装即可调试本地挂件。
用 Vite 起一个空项目,把
index.html与manifest.json放在public/下:my-widget/ ├── manifest.json ├── public/ │ └── index.html ├── package.json └── vite.config.tsindex.html中引用绝对地址加载 SDK:html<script src="http://127.0.0.1:9855/widget-sdk/zbj-widget-sdk.js"></script>pnpm dev启动 dev server(如默认端口5173)。在主播酱 → 创意工坊弹窗 → 「开发者」Tab 填入:
- dev server URL:
http://localhost:5173 - 本地
manifest.json路径:D:/.../my-widget/manifest.json
- dev server URL:
注册后挂件会出现在列表,可像已安装挂件一样添加到画布。修改源码 → Vite HMR 刷新 iframe → 实时看到效果。
修改
manifest.json时,客户端自动监听变化并刷新配置面板 schema,无需重启。
4. RPC:拉礼物列表生成默认选项
如果配置面板需要把当前平台的礼物作为下拉选项,可以使用 ZBJ.invoke("getGifts"):
js
ZBJ.ready(async () => {
const gifts = await ZBJ.getGifts();
// gifts: { id, name, price, icon }[]
for (const g of gifts) {
console.log(g.name, g.price);
}
});RPC 在 10 秒内未收到响应会自动 reject;若挂件运行时未声明 permissions: ["gifts"],调用同样能成功(当前版本未强制校验),但建议显式声明以便后续迁移到强制模式时无需修改 manifest。详见 manifest 规范 §4。
5. 实例持久化:累计礼物总额
ZBJ.storage 把数据保存到 element.widgetState,随场景一起持久化。
js
ZBJ.ready(() => {
let total = ZBJ.storage.get("total") ?? 0;
render(total);
ZBJ.on("gift", (e) => {
total += e.diamondCount * e.giftCount;
ZBJ.storage.set("total", total);
render(total);
});
});
function render(n) {
document.getElementById("total").textContent = `累计:${n}`;
}同一挂件在场景内被多次添加时,每个 ZBJ.instanceId 不同,storage 互相隔离。只在 mode === "edit" 下落盘;预览窗与虚拟摄像头窗下 set 仅在会话内有效,详见 开发约定 §8。