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 类工具
目录
- 1. 安装
- 2. 第一个组件(60 秒上手)
- 3. 所有属性速查表
- 4. 所有 mode 速查表
- 5. 壁纸
- 6. JS API 一览
- 7. 所有事件一览
- 8. 开关 toggle
- 9. 滑块 slider
- 10. 底部标签栏 bottom-tabs
- 11. 按钮组 buttons
- 12. 弹窗 dialog
- 13. 滚动容器 scroll-container
- 14. 主题
- 15. 渲染参数调优
- 16. SVG 图标速查表
- 17. 常见报错与排查
- 18. 页面架构建议
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 |
布尔属性 | 不写 | 显示右上角返回 + 主题切换按钮 |
注意事项:
dark没有值,写了就生效:<liquid-glass dark>。不用写dark="true"。overlay-buttons同理:<liquid-glass overlay-buttons>。tabs/buttons/dialog/scroll这四个属性虽然存在,但不要用。用 JS API 代替,原因见第 17 节第 4 条。
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 "单份"是什么意思?
- "双份" = 组件在同一画布里画了两遍。上面是"透明背景版"(玻璃直接折射壁纸),下面是"白卡版"(有白色胶囊底板)。比如
toggle画两个开关上下排列。 - "单份" = 只画其中一份。比如
single-toggle只画透明版,toggle-card只画白卡版。
选哪个? 大多数场景,直接用"单份"(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。
怎么办:
- 图片放同域(跟你的 HTML 同域名)→ 无 CORS 问题
- 或用 Cloudflare R2(自带 CORS 头)
- 或直接用
data:URL(但文件较大,800KB 左右)
多个 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 配置。
- 参数类型:
Array<Array<{icon?, label?, viewport?}>>(二维数组,外层每一行 tab) - 何时调用: 在元素插入 DOM 后,
requestAnimationFrame内
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)
设置按钮组。
- 参数类型:
Array<{id?, label?, style?}> - style 预设:
transparent/surface/blue/orange/red/green/purple - style 自定义:
[r, g, b, a]RGBA 数组(0-1)
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)
设置弹窗文案。
- 参数类型:
{title?, body?, cancelText?, okayText?}
el.setDialog({
title: '提示',
body: '这是一条通知消息。',
cancelText: '取消',
okayText: '确定'
});
el.setScroll(config)
设置滚动容器卡片列表。
- 参数类型:
Array<{title, subtitle?, link?}> link是{text, href?}
el.setScroll([
{ title: '标题', subtitle: '副标题', link: { text: '查看', href: 'https://...' } },
{ title: '只有标题' }
]);
7. 所有事件一览
所有事件都是 CustomEvent,bubbles: 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。
如何获取更多图标
- 打开 https://fonts.google.com/icons
- 选图标 → 点右上角下载 → 选 SVG
- 用文本编辑器打开
.svg文件 - 找到
<path d="...">,复制引号内的内容 - 贴到
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 生命周期时序。mode 的 attributeChangedCallback 触发 _rebuild 时,tabs 属性可能还没解析完,导致 _tabsConfig 为 null。
解决:一律用 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+)