GooseHyperGlassCDN2.0 — 接入文档

纯 WebGL 渲染的液态玻璃 UI 组件。一个 <script> 标签完事,零依赖。 自定义元素 <liquid-glass>,你写 HTML 它就画 WebGL。

GooseHyperGlassCDN2.0 · 由 Minecraft_goose 开发

AI 阅读说明

本文档内容已直接内嵌于 HTML 中,不依赖 JavaScript 渲染,理论上任何能抓取网页的工具均可直接阅读。但实测发现,部分 AI 的网页浏览能力受限于底层浏览器环境,无法正确获取本文档内容——如 DeepSeek、智谱清言 等仅支持基础网页访问的模型。

推荐使用具备完整网页解析能力的 AI 助手阅读: 豆包(Doubao-Seed-2.1-Turbo) · 元宝任务 · Kimi 系列 · OpenClaw · Agent 类工具

下载本文档原文(Markdown)


目录


1. 安装

在你的 HTML 文件 <head><body> 中加一行:

<script src="https://glass.goose.cc.cd/liquid-glass.js"></script>

就这一行。 没有 npm install、没有 webpack、没有 node_modules。 这个 JS 文件约 345KB(gzip 后约 95KB),注册了一个叫 <liquid-glass> 的自定义 HTML 标签。

之后你就可以像用 <div> 一样用 <liquid-glass> 了。


2. 第一个组件(60 秒上手)

把下面整段保存为 demo.html,双击浏览器打开:

<!doctype html>
<html>
<head><meta charset="utf-8"><title>liquid-glass demo</title></head>
<body style="margin:0;background:#0a0e17;display:flex;justify-content:center;padding:60px 0">

  <!-- 第 1 步:引入 JS -->
  <script src="https://glass.goose.cc.cd/liquid-glass.js"></script>

  <!-- 第 2 步:写标签,设宽高 -->
  <liquid-glass mode="toggle"
    style="width:380px;height:240px;border-radius:28px;overflow:hidden">
  </liquid-glass>

</body>
</html>

打开后你会看到什么: 一个深色背景上的液态玻璃开关。开关有半透明玻璃质感,手指拖动能看到"折射"效果(玻璃下面的壁纸被扭曲)。

为什么必须设宽高? <liquid-glass> 内部是 WebGL Canvas。Canvas 没有 CSS 宽高就不会渲染。给父容器设宽高(或直接用 style="width:...;height:..." 写在标签上)。

关键点:mode 属性决定画什么。 下面你会看到每种 mode 的完整示例。


3. 所有属性速查表

<liquid-glass> 标签支持的 HTML 属性:

属性 值类型 默认值 作用
mode 字符串 "bottom-tabs" 组件类型。取值见第 4 节
dark 布尔属性(写了就生效) 不写 = 浅色 深色主题:卡片变黑底、文字变白
wallpaper URL 字符串 或 "gradient" 透明(无壁纸) 玻璃的"背景内容"。不传默认透明,"gradient" 恢复渐变。见第 5 节
clock-sdf URL 字符串 magnifier 模式用,加载 SDF 时钟纹理
dpr 数字字符串 屏幕 DPR 限制渲染分辨率。"2" 表示最高 2x
corner-style "0" "1" "2" "1"(圆角) 容器圆角风格
blur-tap-cap 数字字符串 "17" 触屏模糊强度上限(1-33)
overlay-buttons 布尔属性 不写 显示右上角返回 + 主题切换按钮

注意事项:


4. 所有 mode 速查表

mode 字符串 画什么 视觉描述 推荐容器高度
toggle 开关 × 2 上下两个开关:上面透明折射壁纸,下面白色胶囊里一个 280px
single-toggle 开关 × 1 只有上面那个透明开关 200px
toggle-card 开关 × 1 只有白色胶囊里那个开关 200px
slider 滑块 × 2 上下两个滑块:上面透明,下面白色胶囊里 280px
single-slider 滑块 × 1 只有上面那个透明滑块 200px
slider-card 滑块 × 1 只有白色胶囊里那个滑块 200px
bottom-tabs 标签栏 × 2 上下两排:3tab + 4tab 260px
single-bottom-tabs 标签栏 × 1 只第一排(3 个 tab) 120px
bottom-tabs-2 标签栏 × 1 只第二排(4 个 tab) 120px
buttons 按钮列表 默认 4 个英文按钮,用 setButtons 自定义 按按钮数 × 80px
dialog 弹窗 标题 + 正文 + 取消/确定 280px
scroll-container 滚动卡片列表 多张玻璃卡片,可手指滑动 500px
lazy-scroll-container 滚动卡片列表(大) 100 张卡片 500px
magnifier 放大镜 需 clock-sdf 纹理 300px

"双份" vs "单份"是什么意思?

选哪个? 大多数场景,直接用"单份"(single-toggle / single-slider / single-bottom-tabs)即可。"双份"主要用于对比展示。


5. 壁纸

壁纸是玻璃折射的"背景内容"——玻璃组件画在壁纸上面,玻璃 blur 效果会让壁纸看起来像磨砂玻璃后面的画面。

四种壁纸方式

方式一:不传,默认透明

<liquid-glass mode="toggle" style="width:380px;height:200px"></liquid-glass>

默认无壁纸,画布透明——玻璃直接叠加在父容器的 CSS 背景上。优点:零配置,直接用父容器背景。

方式二:传图片 URL

<liquid-glass mode="toggle"
  wallpaper="https://你的图片URL/bg.jpg"
  style="width:380px;height:200px">
</liquid-glass>

图片按 background-size: cover 居中裁剪铺满画布——任意尺寸都行。

方式三:传 data: URL

<liquid-glass mode="toggle"
  wallpaper="data:image/jpeg;base64,/9j/4AAQ..."
  style="width:380px;height:200px">
</liquid-glass>

不走网络请求,适合内联壁纸。文件约 800KB(base64 后约 1MB)。

方式四:显式用渐变(保留原默认效果)

<liquid-glass mode="toggle" wallpaper="gradient"></liquid-glass>

如果不传 wallpaper 属性,默认是透明背景。设 wallpaper="gradient" 可恢复到旧版深蓝 → 深灰渐变效果。

CORS 警告

组件内部用 img.crossOrigin = 'anonymous' 加载壁纸图片。如果你的图片 URL 是跨域的,服务器必须返回 HTTP 头:

Access-Control-Allow-Origin: *

没有这个头 → 图片加载失败 → 玻璃后面一片黑。 控制台会打印 [liquid-glass] wallpaper load failed

怎么办:

多个 liquid-glass 共享壁纸

如果你页面有多个 <liquid-glass>,每个都要设 wallpaper。不想重复写?存到变量里:

<script>
  var WP = 'data:image/jpeg;base64,/9j/4AAQ...'; // 壁纸 data URL
</script>
<liquid-glass mode="toggle" id="a"></liquid-glass>
<liquid-glass mode="slider" id="b"></liquid-glass>
<script>
  document.getElementById('a').setAttribute('wallpaper', WP);
  document.getElementById('b').setAttribute('wallpaper', WP);
</script>

6. JS API 一览

强烈建议用 JS API 代替 HTML attribute 传入配置数据。 原因见第 17 节第 4 条

以下 4 个方法挂在 <liquid-glass> 元素实例上:

el.setTabs(config)

设置底部标签栏 tab 配置。

var el = document.getElementById('my-tabs');
requestAnimationFrame(function() {
  el.setTabs([
    // 第一行 3 个 tab
    [
      { icon: 'M10 20v-6h4v6...', label: '首页', viewport: 24 },
      { icon: 'M15.5 14h-.79...', label: '发现', viewport: 24 },
      { icon: 'M12 21.35l-...',    label: '收藏', viewport: 24 }
    ],
    // 第二行 4 个 tab(可选,仅 bottom-tabs 双份模式用得到)
    [
      { icon: 'M10 20v-6h4v6...', label: '首页', viewport: 24 },
      { icon: 'M15.5 14h-.79...', label: '发现', viewport: 24 },
      { icon: 'M12 21.35l-...',    label: '收藏', viewport: 24 },
      { icon: 'M12 12a5 5...',     label: '我的', viewport: 24 }
    ]
  ]);
});

谁用哪一行?

mode 读 config 的哪个下标
single-bottom-tabs config[0](第一行)
bottom-tabs-2 config[1](第二行)
bottom-tabs(双份) 两行都用

字段说明:

字段 必填 类型 默认 说明
icon string 飞机图标 path SVG <path d="..."> 的 d 值
label string "Tab 1" / "Tab 2" ... tab 下方文字
viewport 强烈建议 number 960 path 的坐标范围。Material Icons 填 24

viewport 的坑:默认 960 是飞行图标用的坐标范围。如果你用 Material Design icons(坐标范围 0-24),不传 viewport 会导致图标缩放为 24/960 = 2.5%,肉眼看不见。SVG path 图库见第 16 节

el.setButtons(config)

设置按钮组。

el.setButtons([
  { id: 't', label: '透明', style: 'transparent' },
  { id: 's', label: '表面', style: 'surface' },
  { id: 'b', label: '蓝色', style: 'blue' },
  { id: 'c', label: '自定义紫', style: [0.6, 0.2, 1, 0.8] }
]);

el.setDialog(config)

设置弹窗文案。

el.setDialog({
  title: '提示',
  body: '这是一条通知消息。',
  cancelText: '取消',
  okayText: '确定'
});

el.setScroll(config)

设置滚动容器卡片列表。

el.setScroll([
  { title: '标题', subtitle: '副标题', link: { text: '查看', href: 'https://...' } },
  { title: '只有标题' }
]);

7. 所有事件一览

所有事件都是 CustomEventbubbles: true(冒泡到 document)。

事件名 event.detail 内容 何时触发
lg-statechange {toggleOn, sliderValue, selectedTab, selectedTab2, dark, hideOverlayButtons, globalSeparableBlur} 用户拖拽开关/滑块/点击 tab 时
lg-buttontap {id} 按钮被点击,id 是 setButtons 传的 id
lg-dialogtap {action: "cancel""okay"} 弹窗取消/确定被点击
lg-linktap {index, href} 滚动容器卡片链接被点击
lg-navigate {dest, name} 内部导航(暂未用到)
lg-back 无 detail overlay-buttons 的返回按钮被点

监听示例:

document.addEventListener('lg-statechange', function(e) {
  console.log('toggleOn:', e.detail.toggleOn);       // true/false
  console.log('sliderValue:', e.detail.sliderValue); // 0-100
  console.log('selectedTab:', e.detail.selectedTab); // 0/1/2(第一排,single-bottom-tabs / bottom-tabs 用)
  console.log('selectedTab2:', e.detail.selectedTab2);// 0/1/2/3(第二排,bottom-tabs-2 / bottom-tabs 用)
  console.log('dark:', e.detail.dark);               // true/false
});

document.addEventListener('lg-buttontap', function(e) {
  console.log('点击按钮:', e.detail.id);
});

document.addEventListener('lg-dialogtap', function(e) {
  console.log('弹窗操作:', e.detail.action); // "cancel" 或 "okay"
});

document.addEventListener('lg-linktap', function(e) {
  console.log('链接点击: 第', e.detail.index, '个卡片, href=', e.detail.href);
});

8. 开关 toggle

完整可运行示例

保存为 toggle.html,双击打开:

<!doctype html>
<html>
<head><meta charset="utf-8"><title>开关 toggle</title></head>
<body style="margin:0;background:#0a0e17;display:flex;justify-content:center;align-items:center;min-height:100vh;flex-direction:column;gap:24px;font-family:sans-serif">

  <script src="https://glass.goose.cc.cd/liquid-glass.js"></script>

  <!-- 单份透明版 = 开关折射壁纸 -->
  <div style="color:#9fb3c8;font-size:13px">单份透明版(mode=single-toggle)</div>
  <liquid-glass mode="single-toggle"
    style="width:380px;height:200px;border-radius:28px;overflow:hidden">
  </liquid-glass>

  <!-- 单份白卡版 = 白色胶囊底板里的开关 -->
  <div style="color:#9fb3c8;font-size:13px;margin-top:12px">单份白卡版(mode=toggle-card)</div>
  <liquid-glass mode="toggle-card"
    style="width:380px;height:200px;border-radius:28px;overflow:hidden">
  </liquid-glass>

  <!-- 状态显示 -->
  <div id="status" style="color:#6fc0ec;font-size:14px">开关状态:-</div>

  <script>
    document.addEventListener('lg-statechange', function(e) {
      document.getElementById('status').textContent =
        '开关状态:' + (e.detail.toggleOn ? '开' : '关');
    });
  </script>

</body>
</html>

每个 mode 解释

mode 画什么 推荐高度 何时用
single-toggle 1 个透明玻璃开关,折射壁纸 200px 做 iOS 风格设置页开关
toggle-card 1 个白色胶囊卡片里的开关 200px 做 Material Design 风格开关
toggle 上面透明 + 下面白卡,上下排列 280px 对比展示/调试

状态读取

document.addEventListener('lg-statechange', function(e) {
  var isOn = e.detail.toggleOn;
  // true = 开关开了, false = 关了
});

两个开关联动

toggle(双份)模式下,两个开关是联动的——拖上面,下面的跟着动。它们共享同一个状态 toggleOn


9. 滑块 slider

保存为 slider.html

<!doctype html>
<html>
<head><meta charset="utf-8"><title>滑块 slider</title></head>
<body style="margin:0;background:#0a0e17;display:flex;justify-content:center;align-items:center;min-height:100vh;flex-direction:column;gap:24px;font-family:sans-serif">

  <script src="https://glass.goose.cc.cd/liquid-glass.js"></script>

  <div style="color:#9fb3c8;font-size:13px">单份透明版(mode=single-slider)</div>
  <liquid-glass mode="single-slider"
    style="width:380px;height:200px;border-radius:28px;overflow:hidden">
  </liquid-glass>

  <div style="color:#9fb3c8;font-size:13px;margin-top:12px">单份白卡版(mode=slider-card)</div>
  <liquid-glass mode="slider-card"
    style="width:380px;height:200px;border-radius:28px;overflow:hidden">
  </liquid-glass>

  <div id="val" style="color:#6fc0ec;font-size:14px">滑块值:-</div>

  <script>
    document.addEventListener('lg-statechange', function(e) {
      var v = e.detail.sliderValue;
      if (v !== undefined) {
        document.getElementById('val').textContent = '滑块值:' + v + ' / 100';
      }
    });
  </script>

</body>
</html>
mode 推荐高度
single-slider 200px
slider-card 200px
slider(双份) 280px

10. 底部标签栏 bottom-tabs

完整可运行示例 — 单排 3 tab

保存为 tabs.html

<!doctype html>
<html>
<head><meta charset="utf-8"><title>底部标签栏</title></head>
<body style="margin:0;background:#0a0e17;display:flex;justify-content:center;align-items:center;min-height:100vh;flex-direction:column;gap:16px;font-family:sans-serif">

  <script src="https://glass.goose.cc.cd/liquid-glass.js"></script>

  <div style="color:#9fb3c8;font-size:13px">透明版(3 tab)</div>
  <liquid-glass id="tabs1" mode="single-bottom-tabs"
    style="width:380px;height:120px;border-radius:24px;overflow:hidden">
  </liquid-glass>

  <div style="color:#9fb3c8;font-size:13px;margin-top:12px">白卡风(4 tab)</div>
  <liquid-glass id="tabs2" mode="bottom-tabs-2"
    style="width:380px;height:120px;border-radius:24px;overflow:hidden">
  </liquid-glass>

  <div id="tab-info" style="color:#6fc0ec;font-size:14px">选中的 tab:-</div>

  <script>
    // 3 tab 版:传一行
    requestAnimationFrame(function() {
      document.getElementById('tabs1').setTabs([[
        { icon:'M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z', label:'首页', viewport:24 },
        { icon:'M15.5 14h-.79l-.28-.27a6.5 6.5 0 1 0-.7.7l.27.28v.79l5 4.99L20.49 19zm-6 0A4.5 4.5 0 1 1 14 9.5 4.5 4.5 0 0 1 9.5 14z', label:'发现', viewport:24 },
        { icon:'M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z', label:'收藏', viewport:24 }
      ]]);

      // bottom-tabs-2 读 tabsConfig[1](第二行),所以传二维数组
      document.getElementById('tabs2').setTabs([
        [], // 第一行留空
        [
        { icon:'M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z', label:'首页', viewport:24 },
        { icon:'M15.5 14h-.79l-.28-.27a6.5 6.5 0 1 0-.7.7l.27.28v.79l5 4.99L20.49 19zm-6 0A4.5 4.5 0 1 1 14 9.5 4.5 4.5 0 0 1 9.5 14z', label:'发现', viewport:24 },
        { icon:'M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z', label:'收藏', viewport:24 },
        { icon:'M12 12a5 5 0 1 0-5-5 5 5 0 0 0 5 5zm0 2c-3.33 0-10 1.67-10 5v3h20v-3c0-3.33-6.67-5-10-5z', label:'我的', viewport:24 }
      ]]);
    });

    document.addEventListener('lg-statechange', function(e) {
      document.getElementById('tab-info').textContent =
        '选中:tab[' + e.detail.selectedTab + ']';
    });
  </script>

</body>
</html>

mode 和 config 对应关系

mode 用 config 的哪行 典型 tab 数
single-bottom-tabs config[0] 3
bottom-tabs-2 config[1] 4
bottom-tabs 两行都用 3 + 4

图标尺寸

每个 tab 的图标渲染在 24×24 像素区域内,上方是图标、下方是 12px 文字。图标 SVG path 的坐标范围用 viewport 指定——Material Icons 填 24


11. 按钮组 buttons

保存为 buttons.html

<!doctype html>
<html>
<head><meta charset="utf-8"><title>按钮组</title></head>
<body style="margin:0;background:#0a0e17;display:flex;justify-content:center;align-items:center;min-height:100vh;flex-direction:column;gap:16px;font-family:sans-serif">

  <script src="https://glass.goose.cc.cd/liquid-glass.js"></script>

  <!-- 方式一:4 个按钮在同一个 frame 里(组件内部垂直排列) -->
  <div style="color:#9fb3c8;font-size:13px">4 个按钮一组</div>
  <liquid-glass id="btns-all" mode="buttons"
    style="width:380px;height:400px;border-radius:28px;overflow:hidden">
  </liquid-glass>

  <div id="btn-log" style="color:#6fc0ec;font-size:14px">点击日志:-</div>

  <script>
    requestAnimationFrame(function() {
      document.getElementById('btns-all').setButtons([
        { id: 't', label: '透明', style: 'transparent' },
        { id: 's', label: '表面', style: 'surface' },
        { id: 'b', label: '蓝色', style: 'blue' },
        { id: 'o', label: '橙色', style: 'orange' }
      ]);
    });

    document.addEventListener('lg-buttontap', function(e) {
      document.getElementById('btn-log').textContent =
        '点击了:' + e.detail.id;
    });
  </script>

</body>
</html>

style 预设色

名称 tint 色 表面色 文字色
transparent 透明 透明
surface 透明 白 30%
blue #0088FF 透明
orange #FF8D28 透明
red #FF4D4F 透明
green #34C74B 透明
purple #9C27B0 透明

自定义颜色

{ id: 'c', label: '自定义', style: [0.2, 0.5, 1, 0.8] }
//                                R    G    B   A  (0-1)

每个按钮单独 frame

如果你想每个按钮独立放在不同容器里(而不是组件内部自动排列),创建多个 <liquid-glass>

<div id="btns-row" style="display:flex;flex-direction:column;gap:12px;align-items:center"></div>
<script>
  var configs = [
    { label:'透明', style:'transparent' },
    { label:'表面', style:'surface' },
    { label:'蓝色', style:'blue' },
    { label:'橙色', style:'orange' }
  ];
  var container = document.getElementById('btns-row');
  configs.forEach(function(c, i) {
    var frame = document.createElement('div');
    frame.style.cssText = 'width:240px;height:96px;border-radius:20px;overflow:hidden;background:#0a0e17';
    var el = document.createElement('liquid-glass');
    el.setAttribute('mode', 'buttons');
    el.style.cssText = 'width:100%;height:100%';
    frame.appendChild(el);
    container.appendChild(frame);
    requestAnimationFrame(function() {
      el.setButtons([{ id: 'btn'+i, label: c.label, style: c.style }]);
    });
  });
</script>

12. 弹窗 dialog

保存为 dialog.html

<!doctype html>
<html>
<head><meta charset="utf-8"><title>弹窗</title></head>
<body style="margin:0;background:#0a0e17;display:flex;justify-content:center;align-items:center;min-height:100vh;flex-direction:column;gap:16px;font-family:sans-serif">

  <script src="https://glass.goose.cc.cd/liquid-glass.js"></script>

  <div style="color:#9fb3c8;font-size:13px">弹窗示例(点击按钮不关闭)</div>
  <liquid-glass id="dlg" mode="dialog"
    style="width:380px;height:280px;border-radius:28px;overflow:hidden">
  </liquid-glass>

  <div id="dlg-log" style="color:#6fc0ec;font-size:14px">-</div>

  <script>
    var dlg = document.getElementById('dlg');
    requestAnimationFrame(function() {
      dlg.setDialog({
        title: '删除确认',
        body: '此操作不可撤销,确定要删除吗?',
        cancelText: '取消',
        okayText: '确认删除'
      });
    });

    // 监听点击
    document.addEventListener('lg-dialogtap', function(e) {
      document.getElementById('dlg-log').textContent =
        '点击了:' + e.detail.action;
    });

    // 阻止自动关闭:用 MutationObserver 在 mode 被改走后瞬间改回来
    new MutationObserver(function(mutations) {
      mutations.forEach(function(m) {
        if (m.type === 'attributes' && m.attributeName === 'mode') {
          if (m.target.getAttribute('mode') !== 'dialog') {
            requestAnimationFrame(function() {
              m.target.setAttribute('mode', 'dialog');
            });
          }
        }
      });
    }).observe(document.body, {
      attributes: true, subtree: true, attributeFilter: ['mode']
    });
  </script>

</body>
</html>

为什么需要 MutationObserver? 弹窗的取消/确定按钮被点击后,组件内部硬编码了关闭行为:this.setAttribute('mode', prevMode) 切回进入 dialog 之前的视图。e.preventDefault()CustomEvent 无效。MutationObserver 在 mode 被改走的瞬间拦截并恢复。

如果你想要正常的关闭行为(点了就关),删掉 MutationObserver 那段即可。


13. 滚动容器 scroll-container

保存为 scroll.html

<!doctype html>
<html>
<head><meta charset="utf-8"><title>滚动容器</title></head>
<body style="margin:0;background:#0a0e17;display:flex;justify-content:center;align-items:center;min-height:100vh;flex-direction:column;gap:16px;font-family:sans-serif">

  <script src="https://glass.goose.cc.cd/liquid-glass.js"></script>

  <div style="color:#9fb3c8;font-size:13px">滚动容器(手指滑动)</div>
  <liquid-glass id="sc" mode="scroll-container"
    style="width:380px;height:500px;border-radius:28px;overflow:hidden">
  </liquid-glass>

  <div id="sc-log" style="color:#6fc0ec;font-size:14px">-</div>

  <script>
    var sc = document.getElementById('sc');
    requestAnimationFrame(function() {
      sc.setScroll([
        { title: '天气预报', subtitle: '2026-07-22', link: { text: '查看详情', href: '#' } },
        { title: '系统通知', subtitle: '2026-07-21' },
        { title: '活动提醒', subtitle: '2026-07-20', link: { text: '立即参与', href: '#' } },
        { title: '版本更新', subtitle: 'v2.3.0' },
        { title: '账户安全', subtitle: '2026-07-18', link: { text: '查看', href: '#' } },
        { title: '优惠券到账', subtitle: '2026-07-17' },
        { title: '订单发货', subtitle: '2026-07-16', link: { text: '追踪物流', href: '#' } }
      ]);
    });

    document.addEventListener('lg-linktap', function(e) {
      document.getElementById('sc-log').textContent =
        '点击链接:第 ' + e.detail.index + ' 个卡片 → ' + e.detail.href;
    });
  </script>

</body>
</html>

注意:mode 是 "scroll-container",不是 "scroll" 写错了会渲染成默认的底部标签栏视图。


14. 主题

liquid-glass 支持浅色/深色两种主题,通过 dark 属性控制。

静态设置

<!-- 浅色主题(默认,不写 dark) -->
<liquid-glass mode="toggle" style="width:380px;height:200px"></liquid-glass>

<!-- 深色主题 -->
<liquid-glass mode="toggle" dark style="width:380px;height:200px"></liquid-glass>

浅色 = 白底卡片 + 黑色文字。深色 = 黑底卡片 + 白色文字。

所有组件都受主题影响: 开关/滑块的白卡底色(白↔黑)、标签栏容器色、按钮组默认背景色、弹窗遮罩色、滚动容器卡片底色,全部跟着 dark 走。只有壁纸(透明背景版那部分)不受主题影响——它永远折射壁图片。

动态切换(JS)

<liquid-glass id="theme-demo" mode="toggle" dark style="width:380px;height:200px"></liquid-glass>
<button onclick="toggleTheme()">切换主题</button>

<script>
  var el = document.getElementById('theme-demo');
  function toggleTheme() {
    if (el.hasAttribute('dark')) {
      el.removeAttribute('dark'); // 变浅色
    } else {
      el.setAttribute('dark', ''); // 变深色
    }
  }
</script>

setAttribute('dark', '') / removeAttribute('dark') 会触发组件内部重绘,无需手动调用任何方法。

overlay-buttons(内置切换按钮)

<liquid-glass mode="toggle" dark overlay-buttons style="width:380px;height:200px"></liquid-glass>

加上 overlay-buttons 属性后,组件右上角会渲染两个 Canvas 内按钮:

按钮 图标 功能
返回 ← 箭头 派发 lg-back 事件,组件内部切回上一视图
主题切换 太阳/月亮 切换 dark 属性的有无(浅↔深)

两个按钮在 WebGL Canvas 内部渲染(不是 HTML 元素),带玻璃质感和触摸反馈。点击主题切换按钮后,组件自动 addAttribute/removeAttribute('dark') 并重绘。

如果只想单独显示主题切换按钮(不要返回按钮):

<liquid-glass mode="toggle" theme-button style="width:380px;height:200px"></liquid-glass>

theme-button 属性只显示主题切换按钮,不影响返回按钮的显示。两个属性可组合:

属性 返回按钮 主题切换
都不设 隐藏 隐藏
overlay-buttons 显示 显示
theme-button 隐藏 显示
两个都设 显示 显示

读取当前主题状态

var el = document.querySelector('liquid-glass');
console.log('当前主题:', el.hasAttribute('dark') ? '深色' : '浅色');

el.addEventListener('lg-statechange', function(e) {
  console.log('主题变为:', e.detail.dark ? '深色' : '浅色');
});

多组件独立主题

每个 <liquid-glass> 的主题是独立的。A 深色不影响 B 浅色:

<liquid-glass mode="toggle" dark style="width:380px;height:200px"></liquid-glass>       <!-- 深色 -->
<liquid-glass mode="toggle" style="width:380px;height:200px"></liquid-glass>            <!-- 浅色 -->

主题对壁纸的影响

壁纸图片本身不变色——深色模式下壁纸还是那张图。但白卡底色会翻转为黑卡底色,文字颜色也跟着翻转。


15. 渲染参数调优

<!-- 限制渲染分辨率最高 2x(省 GPU,手机上效果明显) -->
<liquid-glass mode="toggle" dpr="2"></liquid-glass>

<!-- 直角风格容器 -->
<liquid-glass mode="bottom-tabs" corner-style="0"></liquid-glass>

<!-- 胶囊风格容器 -->
<liquid-glass mode="bottom-tabs" corner-style="2"></liquid-glass>

<!-- 触屏时玻璃模糊更重 -->
<liquid-glass mode="slider" blur-tap-cap="30"></liquid-glass>
参数 作用 范围 默认
dpr 渲染分辨率上限。"1" 强制 1x(可察觉轻微模糊) ≥0.5 屏幕 DPR
corner-style "0"直角 "1"圆角 "2"胶囊 0/1/2 "1"
blur-tap-cap 手指按住时玻璃模糊强度 1-33 17
overlay-buttons 右上角显示「返回」「主题切换」按钮 布尔属性 不显示
theme-button 右上角单独显示「主题切换」按钮,不影响返回按钮 布尔属性 不显示

16. SVG 图标速查表

直接复制粘贴到 icon 字段,全部 viewport: 24

图标 名称 path
🏠 首页 M10 20v-6h4v6h5v-8h3L12 3 2 12h3v8z
🔍 搜索 M15.5 14h-.79l-.28-.27a6.5 6.5 0 1 0-.7.7l.27.28v.79l5 4.99L20.49 19zm-6 0A4.5 4.5 0 1 1 14 9.5 4.5 4.5 0 0 1 9.5 14z
❤️ 心形 M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z
👤 人形 M12 12a5 5 0 1 0-5-5 5 5 0 0 0 5 5zm0 2c-3.33 0-10 1.67-10 5v3h20v-3c0-3.33-6.67-5-10-5z
星星 M12 17.27 18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z
✈️ 飞行 M400 552 L147 653 q-24 10 -45.5 -4.5 T80 608 v-22 q0 -12 5.5 -23 t15.5 -18 l299 -209 v-176 q0 -33 23.5 -56.5 T480 80 q33 0 56.5 23.5 T560 160 v176 l299 209 q10 7 15.5 18 t5.5 23 v22 q0 26 -21.5 40.5 T813 653 L560 552 v144 l103 72 q8 6 12.5 14.5 T680 801 v24 q0 20 -16.5 32.5 T627 864 l-147 -44 l-147 44 q-20 6 -36.5 -6.5 T280 825 v-24 q0 -10 4.5 -18.5 T297 768 l103 -72 v-144 Z

飞行图标 viewport = 960(默认值即正确,不传 viewport),其余图标 viewport = 24。

如何获取更多图标

  1. 打开 https://fonts.google.com/icons
  2. 选图标 → 点右上角下载 → 选 SVG
  3. 用文本编辑器打开 .svg 文件
  4. 找到 <path d="..."> ,复制引号内的内容
  5. 贴到 icon 字段,加 viewport: 24

17. 常见报错与排查

1. WARNING: Too many active WebGL contexts

原因: 浏览器限制 8-16 个 WebGL context。你创建了太多 <liquid-glass> 元素而没销毁旧的。

解决:remove() 旧元素再创建新的:

// 错:直接 innerHTML = '' → 元素移除但 WebGL context 可能没释放
container.innerHTML = '';

// 对:逐个 remove 触发 disconnectedCallback → dispose
var old = container.querySelectorAll('liquid-glass');
for (var i = 0; i < old.length; i++) old[i].remove();
container.innerHTML = '';

2. Uncaught ReferenceError: xxx is not defined

原因: 组件 JS 内部变量作用域 bug(如 s2TrackW / t1TrackY / SLIDER_KNOB_HIT_H)。当前版本已全部修复。 如果你还在用旧版 liquid-glass.js,更新到最新。

3. [liquid-glass] wallpaper load failed

原因: 壁纸图片跨域且服务端没返回 Access-Control-Allow-Origin 头。

解决: 图片放同域,或用 data: URL,或服务端加 CORS 头。

4. JS API vs HTML 属性

问题:setAttribute('tabs', JSON.stringify(...)) 设 tab 配置,渲染出来还是默认的 "Tab 1/2/3" + 飞机图标。

原因: Web Component 生命周期时序。modeattributeChangedCallback 触发 _rebuild 时,tabs 属性可能还没解析完,导致 _tabsConfignull

解决:一律用 JS API。 不要传 HTML 属性 tabs / buttons / dialog / scroll。用 el.setTabs() / el.setButtons() / el.setDialog() / el.setScroll()

// 错
el.setAttribute('tabs', JSON.stringify([[...]]));

// 对
requestAnimationFrame(function() {
  el.setTabs([[...]]);
});

5. 图标不显示或极小

原因: viewport 没传,默认 960,Material Icons 需要 24。

现象: 图标存在但肉眼几乎看不见(缩放为 2.5%)。

解决:viewport: 24

6. 容器不渲染(空白)

原因: <liquid-glass> 没有 CSS 宽高。Canvas 尺寸 = CSS 尺寸 × DPR,0 宽高 = 不渲染。

解决:

<!-- 错 -->
<liquid-glass mode="toggle"></liquid-glass>

<!-- 对 -->
<liquid-glass mode="toggle" style="width:380px;height:200px"></liquid-glass>

7. mode="scroll" 不渲染滚动容器

原因: mode 字符串是 "scroll-container"(带连字符),不是 "scroll"

现象:mode="scroll" → 组件回退到默认 mode "bottom-tabs" → 显示 3 个 tab。

解决: mode="scroll-container"


18. 页面架构建议

场景 A:展示页(多个静态组件)

每个组件一个 <liquid-glass>,全写在一个 HTML 里。限制:总共不超过 6-8 个(WebGL context 上限)。

<script src="https://glass.goose.cc.cd/liquid-glass.js"></script>

<liquid-glass mode="single-toggle"  style="width:380px;height:200px"></liquid-glass>
<liquid-glass mode="single-slider"  style="width:380px;height:200px"></liquid-glass>
<liquid-glass mode="single-bottom-tabs" id="tabs" style="width:380px;height:120px"></liquid-glass>
<liquid-glass mode="buttons"        id="btns" style="width:380px;height:400px"></liquid-glass>

<script>
  requestAnimationFrame(function() {
    document.getElementById('tabs').setTabs([[...]]);
    document.getElementById('btns').setButtons([...]);
  });
</script>

场景 B:SPA / 组件切换(Tab 式)

一个容器,点按钮切换 mode,每次切换先销毁旧元素

<script src="https://glass.goose.cc.cd/liquid-glass.js"></script>

<div id="nav">
  <button data-mode="single-toggle">开关</button>
  <button data-mode="single-slider">滑块</button>
</div>
<div id="stage" style="width:380px;height:200px;border-radius:28px;overflow:hidden;background:#0a0e17"></div>

<script>
  var stage = document.getElementById('stage');
  document.getElementById('nav').addEventListener('click', function(e) {
    var btn = e.target.closest('button');
    if (!btn) return;

    // 销毁旧的
    var old = stage.querySelectorAll('liquid-glass');
    for (var i = 0; i < old.length; i++) old[i].remove();
    stage.innerHTML = '';

    // 创建新的
    var el = document.createElement('liquid-glass');
    el.setAttribute('mode', btn.getAttribute('data-mode'));
    el.style.cssText = 'width:100%;height:100%';
    stage.appendChild(el);
  });
</script>

场景 C:壁纸预加载

如果你有一个大壁纸(比如 data: URL 约 800KB),想避免每个组件各自加载一次,可以提前存到全局变量:

<script>
  // 预定义壁纸
  window.LG_WALLPAPER = 'data:image/jpeg;base64,/9j/4AAQ...';
</script>

<liquid-glass id="a" mode="toggle" style="width:380px;height:200px"></liquid-glass>
<liquid-glass id="b" mode="slider" style="width:380px;height:200px"></liquid-glass>

<script>
  var wp = window.LG_WALLPAPER;
  document.getElementById('a').setAttribute('wallpaper', wp);
  document.getElementById('b').setAttribute('wallpaper', wp);
</script>

GooseHyperGlassCDN2.0 · Minecraft_goose 开发 · 2026-07-22 基于: Kyant0/AndroidLiquidGlass(Android 原项目)· martin65536/liquid-glass-webgl(Web 移植) CDN 地址: https://glass.goose.cc.cd/liquid-glass.js 文件大小: ~345KB(gzip ~95KB) 浏览器要求: 支持 WebGL 1.0 + Custom Elements v1 + ES2020(Chrome 80+ / Edge 80+ / Safari 14+ / Firefox 80+)

下载本文档原文(Markdown)
基于 Kyant0/AndroidLiquidGlass · martin65536/liquid-glass-webgl 二开 · GitHub