Skip to content

示例

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>

注意 h3color: 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. 开发者模式

无需安装即可调试本地挂件。

  1. 用 Vite 起一个空项目,把 index.htmlmanifest.json 放在 public/ 下:

    my-widget/
    ├── manifest.json
    ├── public/
    │   └── index.html
    ├── package.json
    └── vite.config.ts
  2. index.html 中引用绝对地址加载 SDK:

    html
    <script src="http://127.0.0.1:9855/widget-sdk/zbj-widget-sdk.js"></script>
  3. pnpm dev 启动 dev server(如默认端口 5173)。

  4. 在主播酱 → 创意工坊弹窗 → 「开发者」Tab 填入:

    • dev server URL:http://localhost:5173
    • 本地 manifest.json 路径:D:/.../my-widget/manifest.json
  5. 注册后挂件会出现在列表,可像已安装挂件一样添加到画布。修改源码 → Vite HMR 刷新 iframe → 实时看到效果。

  6. 修改 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