lumiglass 1.1.1 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/css/glass-lg.css +2 -7
- package/js/glass-liquid-engine.js +622 -643
- package/js/glass-webgl.js +7 -0
- package/js/glass.js +1 -1
- package/package.json +7 -3
|
@@ -1,673 +1,652 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
1
|
+
/* ============================================================
|
|
2
|
+
Lumi Glass · glass-liquid-engine.js (自研实现)
|
|
3
|
+
------------------------------------------------------------
|
|
4
|
+
本文件为完全自研的 WebGL 液态玻璃渲染引擎,不包含任何第三方代码。
|
|
5
|
+
对外契约:
|
|
6
|
+
window.LiquidGlassEngine = { LiquidGlass, default }
|
|
7
|
+
new LiquidGlass(canvas, opts)
|
|
8
|
+
opts: { refraction, dispersion, frost, bezel, mergeK, rim, mode, liquid }
|
|
9
|
+
实例: .failed / .scan(root) / .set(opts) / .setSource(src,{live}) / .destroy()
|
|
10
|
+
静态: LiquidGlass.PRESETS
|
|
11
|
+
DOM 契约(与 glass-webgl.js 的标记约定一致):
|
|
12
|
+
[data-glass=preset] [data-frost] [data-tint] [data-layer] [data-grow] [data-merge]
|
|
13
|
+
[data-glass-solid](v2 仅收集不动样式,solid 仍由 DOM 自绘)
|
|
14
|
+
渲染思路(自研):
|
|
15
|
+
1. 壁纸 canvas 作为纹理上传;CPU 侧 drawImage+filter 生成 1/4 分辨率模糊图
|
|
16
|
+
2. 片元着色器对每个玻璃形状求圆角矩形 SDF,min / 平滑并集合成场
|
|
17
|
+
3. 场梯度作折射法线:近边缘按指数衰减位移采样背景,RGB 三通道位移量
|
|
18
|
+
不同产生色散;霜化 = 锐利图与模糊图按形状 frost 插值
|
|
19
|
+
4. 边缘高光:SDF 等值带 × (法线·光向),暗色模式换冷色并减半
|
|
20
|
+
5. 两层合成:layer0(面板)先压在壁纸上,layer1(控件)再压在其上
|
|
21
|
+
6. 滚动帧零 DOM 读取:元素位置缓存文档坐标,滚动帧纯算术平移
|
|
22
|
+
已知限制(相对 v1):
|
|
23
|
+
- data-glass-solid 不入 shader 折射,仍由 DOM 自绘(视觉差异极小)
|
|
24
|
+
- 无按压弹簧/形变动画(liquid 参数暂未消费)
|
|
25
|
+
============================================================ */
|
|
26
|
+
(function () {
|
|
27
|
+
'use strict';
|
|
28
|
+
|
|
29
|
+
var MAXG = 48;
|
|
30
|
+
|
|
31
|
+
/* 形状预设。键名与 data-glass 值对应,数值为自研调校。 */
|
|
32
|
+
var PRESETS = {
|
|
33
|
+
button: { grow: 0.14, frost: 0.15, tint: 0, merge: 0, layer: 0 },
|
|
34
|
+
bar: { grow: 0.03, frost: 0.45, tint: 0, merge: 0, layer: 0 },
|
|
35
|
+
card: { grow: 0, frost: 1, tint: 0.2, merge: 0, layer: 0 },
|
|
36
|
+
sheet: { grow: 0, frost: 1, tint: 0.28, merge: 1, layer: 0 },
|
|
37
|
+
knob: { grow: 0.22, frost: 0.06, tint: 0, merge: 0, layer: 1 },
|
|
38
|
+
blob: { grow: 0.1, frost: 0.1, tint: 0, merge: 1, layer: 0 }
|
|
18
39
|
};
|
|
19
|
-
|
|
20
|
-
var
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
//
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
40
|
+
|
|
41
|
+
var VS =
|
|
42
|
+
'attribute vec2 p;varying vec2 vUv;' +
|
|
43
|
+
'void main(){vUv=vec2(p.x*.5+.5,.5-p.y*.5);gl_Position=vec4(p,0.,1.);}';
|
|
44
|
+
|
|
45
|
+
var FS = [
|
|
46
|
+
'precision highp float;',
|
|
47
|
+
'#define MAXG ' + MAXG,
|
|
48
|
+
'varying vec2 vUv;',
|
|
49
|
+
'uniform sampler2D uTex;', // 壁纸原图
|
|
50
|
+
'uniform sampler2D uBlur;', // 壁纸模糊图
|
|
51
|
+
'uniform vec2 uRes;', // 画布 CSS 尺寸
|
|
52
|
+
'uniform vec4 uFit;', // 纹理 UV = vUv * uFit.xy + uFit.zw
|
|
53
|
+
'uniform vec4 uG[' + MAXG + '];', // cx, cy, hw, hh(px,y 向下)
|
|
54
|
+
'uniform vec4 uA[' + MAXG + '];', // radius, layer, frost, tintAmt
|
|
55
|
+
'uniform vec4 uB[' + MAXG + '];', // merge, tintR, tintG, tintB
|
|
56
|
+
'uniform int uGN;',
|
|
57
|
+
'uniform float uBezel;',
|
|
58
|
+
'uniform float uStrength;',
|
|
59
|
+
'uniform float uDisp;',
|
|
60
|
+
'uniform float uMergeK;',
|
|
61
|
+
'uniform float uDark;',
|
|
62
|
+
'uniform float uRim;',
|
|
63
|
+
'uniform vec2 uPtr;', // 指针位置(画布 px)
|
|
64
|
+
'uniform float uPtrOn;', // 指针是否在页面上
|
|
65
|
+
|
|
66
|
+
/* 圆角矩形 SDF(经典公式,公有数学) */
|
|
67
|
+
'float sdRR(vec2 p, vec2 h, float r){',
|
|
68
|
+
' r = min(r, min(h.x, h.y));',
|
|
69
|
+
' vec2 q = abs(p) - h + r;',
|
|
70
|
+
' return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - r;',
|
|
71
|
+
'}',
|
|
72
|
+
|
|
73
|
+
/* SDF 解析梯度:指向最近表面的单位向量 */
|
|
74
|
+
'vec2 sdGrad(vec2 pw, vec2 h, float r){',
|
|
75
|
+
' r = min(r, min(h.x, h.y));',
|
|
76
|
+
' vec2 q = abs(pw) - h + r;',
|
|
77
|
+
' vec2 s = vec2(pw.x >= 0.0 ? 1.0 : -1.0, pw.y >= 0.0 ? 1.0 : -1.0);',
|
|
78
|
+
' if (q.x > 0.0 && q.y > 0.0) return normalize(max(q, 0.0) * s);',
|
|
79
|
+
' if (q.x > 0.0) return vec2(s.x, 0.0);',
|
|
80
|
+
' if (q.y > 0.0) return vec2(0.0, s.y);',
|
|
81
|
+
' return (q.x > q.y) ? vec2(s.x, 0.0) : vec2(0.0, s.y);',
|
|
82
|
+
'}',
|
|
83
|
+
|
|
84
|
+
/* 多项式平滑并集(公有数学) */
|
|
85
|
+
'float smin(float a, float b, float k){',
|
|
86
|
+
' float h = clamp(0.5 + 0.5 * (b - a) / k, 0.0, 1.0);',
|
|
87
|
+
' return mix(b, a, h) - k * h * (1.0 - h);',
|
|
88
|
+
'}',
|
|
89
|
+
|
|
90
|
+
/* 场求值:返回带符号距离;带出最近形状的 frost/tint 与法线。
|
|
91
|
+
merge 形状彼此平滑并集(距离小于 uMergeK 时),其余取硬 min。
|
|
92
|
+
同时带出指针在该形状边框上的最近点 ptrPt 与该形状的指针接近度 ptrK,
|
|
93
|
+
供 surf() 在边框上画跟随光。 */
|
|
94
|
+
'float field(vec2 p, float layer, out vec2 grad, out float frost, out vec3 tintC, out float tintA,',
|
|
95
|
+
' out vec2 ptrPt, out float ptrK, out float ptrR){',
|
|
96
|
+
' float dHard = 1e4; float dMerge = 1e4;',
|
|
97
|
+
' vec2 gHard = vec2(0.0); vec2 gMerge = vec2(0.0);',
|
|
98
|
+
' float fH = 0.5; vec3 tH = vec3(0.5); float aH = 0.0;',
|
|
99
|
+
' float fM = 0.5; vec3 tM = vec3(0.5); float aM = 0.0;',
|
|
100
|
+
' vec4 gH = vec4(0.0); vec4 gM = vec4(0.0);',
|
|
101
|
+
' float rH = 0.0; float rM = 0.0;',
|
|
102
|
+
' vec4 gS = vec4(0.0); float rS = 0.0; float dS = 1e4;',
|
|
103
|
+
' ptrPt = p; ptrK = 0.0; ptrR = 90.0;',
|
|
104
|
+
' for (int i = 0; i < MAXG; i++){',
|
|
105
|
+
' if (i >= uGN) break;',
|
|
106
|
+
' if (abs(uA[i].y - layer) > 0.5) continue;',
|
|
107
|
+
' vec2 pw = p - uG[i].xy;',
|
|
108
|
+
' float di = sdRR(pw, uG[i].zw, uA[i].x);',
|
|
109
|
+
' vec2 gi = sdGrad(pw, uG[i].zw, uA[i].x);',
|
|
110
|
+
' if (uB[i].x > 0.5) {',
|
|
111
|
+
' if (dMerge > 9e3) {',
|
|
112
|
+
' dMerge = di; gMerge = gi; fM = uA[i].z; tM = uB[i].yzw; aM = uA[i].w;',
|
|
113
|
+
' gM = uG[i]; rM = uA[i].x;',
|
|
114
|
+
' } else if (di < uMergeK) {',
|
|
115
|
+
' float h = clamp(0.5 + 0.5 * (di - dMerge) / uMergeK, 0.0, 1.0);',
|
|
116
|
+
' gMerge = mix(gMerge, gi, h);',
|
|
117
|
+
' dMerge = mix(dMerge, di, h) - uMergeK * h * (1.0 - h);',
|
|
118
|
+
' fM = mix(fM, uA[i].z, h); tM = mix(tM, uB[i].yzw, h); aM = mix(aM, uA[i].w, h);',
|
|
119
|
+
' } else if (di < dHard) {',
|
|
120
|
+
' dHard = di; gHard = gi; fH = uA[i].z; tH = uB[i].yzw; aH = uA[i].w;',
|
|
121
|
+
' gH = uG[i]; rH = uA[i].x;',
|
|
122
|
+
' }',
|
|
123
|
+
' } else if (di < dHard) {',
|
|
124
|
+
' dHard = di; gHard = gi; fH = uA[i].z; tH = uB[i].yzw; aH = uA[i].w;',
|
|
125
|
+
' gH = uG[i]; rH = uA[i].x;',
|
|
126
|
+
' }',
|
|
127
|
+
' }',
|
|
128
|
+
' if (dMerge < dHard) { grad = gMerge; frost = fM; tintC = tM; tintA = aM; dS = dMerge; gS = gM; rS = rM; }',
|
|
129
|
+
' else { grad = gHard; frost = fH; tintC = tH; tintA = aH; dS = dHard; gS = gH; rS = rH; }',
|
|
130
|
+
/* 指针 → 该形状边框最近点:沿指针处法线走 |d| 即落在框上。
|
|
131
|
+
光斑半径随元素短边缩放:小按钮局部亮,大面板亮一段弧 */
|
|
132
|
+
' if (uPtrOn > 0.5 && dS < 9e3) {',
|
|
133
|
+
' vec2 pl = uPtr - gS.xy;',
|
|
134
|
+
' float dp = sdRR(pl, gS.zw, rS);',
|
|
135
|
+
' vec2 gp = sdGrad(pl, gS.zw, rS);',
|
|
136
|
+
' ptrPt = uPtr - gp * dp;',
|
|
137
|
+
' ptrR = clamp(min(gS.z, gS.w) * 2.4, 40.0, 300.0);',
|
|
138
|
+
' float reach = clamp(min(gS.z, gS.w) * 1.6, 72.0, 280.0);',
|
|
139
|
+
' ptrK = 1.0 - smoothstep(reach, reach * 2.5, max(dp, 0.0));',
|
|
140
|
+
' }',
|
|
141
|
+
' return dS;',
|
|
142
|
+
'}',
|
|
143
|
+
|
|
144
|
+
/* 单层玻璃面:输入该像素的场参数,输出 premultiplied 前的 (rgb, a)
|
|
145
|
+
px2uv: 屏幕 px → 源图 UV 的换算(cover 映射下 = uFit.xy / uRes) */
|
|
146
|
+
'vec4 surf(float d, vec2 n, float fr, vec3 tc, float ta, vec2 uv, vec2 suv,',
|
|
147
|
+
' vec2 p, vec2 ptrPt, float ptrK, float ptrR){',
|
|
148
|
+
' float hot = 0.0;',
|
|
149
|
+
' float a = 1.0 - smoothstep(-0.75, 0.0, d);',
|
|
150
|
+
/* 折射:边缘位移最大,向内部指数衰减;RGB 位移不同 → 色散 */
|
|
151
|
+
' float k = exp(d / max(uBezel, 1.0));',
|
|
152
|
+
' float str = k * uStrength;',
|
|
153
|
+
' vec2 px2uv = vec2(uFit.x / uRes.x, uFit.y / uRes.y);',
|
|
154
|
+
' vec2 offR = n * str * (1.0 + uDisp) * px2uv;',
|
|
155
|
+
' vec2 offG = n * str * px2uv;',
|
|
156
|
+
' vec2 offB = n * str * (1.0 - uDisp) * px2uv;',
|
|
157
|
+
' vec3 sharp;',
|
|
158
|
+
' sharp.r = texture2D(uTex, clamp(uv + offR, 0.001, 0.999)).r;',
|
|
159
|
+
' sharp.g = texture2D(uTex, clamp(uv + offG, 0.001, 0.999)).g;',
|
|
160
|
+
' sharp.b = texture2D(uTex, clamp(uv + offB, 0.001, 0.999)).b;',
|
|
161
|
+
/* 模糊图与屏幕 1:1 对应,位移按屏幕 UV 计 */
|
|
162
|
+
' vec3 bl = texture2D(uBlur, clamp(suv + n * str / uRes, 0.001, 0.999)).rgb;',
|
|
163
|
+
' vec3 col = mix(sharp, bl, clamp(fr, 0.0, 1.0));',
|
|
164
|
+
/* 调色:增饱和 → 暗/亮模式各自的亮度曲线 → 着色 */
|
|
165
|
+
' float lum = dot(col, vec3(0.299, 0.587, 0.114));',
|
|
166
|
+
' col = mix(vec3(lum), col, 1.2);',
|
|
167
|
+
' col = mix(col * 1.02 + 0.03, col * 0.78 + 0.015, uDark);',
|
|
168
|
+
' col = mix(col, tc, clamp(ta, 0.0, 1.0));',
|
|
169
|
+
/* 边缘高光:等值带 × 光向(左上来光)。亮色主题底色已亮,
|
|
170
|
+
高光减半防止粗白边;暗色保持全强度冷光 */
|
|
171
|
+
' float band = exp(-abs(d) / max(uBezel * 0.25, 1.0));',
|
|
172
|
+
' vec2 L = normalize(vec2(-0.55, -0.83));',
|
|
173
|
+
' float lit = clamp(dot(n, L) * 0.5 + 0.5, 0.0, 1.0);',
|
|
174
|
+
' vec3 rimCol = mix(vec3(1.0), vec3(0.75, 0.85, 1.0), uDark);',
|
|
175
|
+
/* 指针跟光:指针投影到本形状边框上的那个点,周围一段边框被点亮。
|
|
176
|
+
只加到边框带 band / 轮廓线 line 上,玻璃内部不铺光。 */
|
|
177
|
+
' if (uPtrOn > 0.5 && ptrK > 0.001) {',
|
|
178
|
+
' float dd = length(p - ptrPt) / ptrR;',
|
|
179
|
+
' hot = exp(-dd * dd * 1.8) * ptrK;',
|
|
180
|
+
' band = max(band, hot * 0.95 * exp(-abs(d) / 2.2));',
|
|
181
|
+
' }',
|
|
182
|
+
' float rimI = uRim * mix(0.45, 1.0, uDark) * (1.0 + 2.6 * hot);',
|
|
183
|
+
' col += rimCol * (rimI * band * (0.22 + 0.55 * lit));',
|
|
184
|
+
' col -= rimCol * (rimI * 0.25 * band * (1.0 - lit));',
|
|
185
|
+
/* 外沿 1px 亮线,托出轮廓;靠近指针处一起变亮 */
|
|
186
|
+
' float line = 1.0 - smoothstep(0.0, 1.2, abs(d + 0.6));',
|
|
187
|
+
' col += rimCol * line * mix(0.05, 0.12, uDark) * (1.0 + 2.0 * hot);',
|
|
188
|
+
' return vec4(col, a);',
|
|
189
|
+
'}',
|
|
190
|
+
|
|
191
|
+
'void main(){',
|
|
192
|
+
' vec2 p = vUv * uRes;',
|
|
193
|
+
' vec2 uv = vUv * uFit.xy + uFit.zw;',
|
|
194
|
+
' vec3 col = texture2D(uTex, uv).rgb;', // 底:壁纸
|
|
195
|
+
' float d0; vec2 n0; float fr0; vec3 tc0; float ta0; vec2 pt0; float pk0; float pr0;',
|
|
196
|
+
' d0 = field(p, 0.0, n0, fr0, tc0, ta0, pt0, pk0, pr0);',
|
|
197
|
+
' float d1; vec2 n1; float fr1; vec3 tc1; float ta1; vec2 pt1; float pk1; float pr1;',
|
|
198
|
+
' d1 = field(p, 1.0, n1, fr1, tc1, ta1, pt1, pk1, pr1);',
|
|
199
|
+
' if (d0 < 0.0) { vec4 s = surf(d0, n0, fr0, tc0, ta0, uv, vUv, p, pt0, pk0, pr0); col = mix(col, s.rgb, s.a); }',
|
|
200
|
+
' if (d1 < 0.0) { vec4 s = surf(d1, n1, fr1, tc1, ta1, uv, vUv, p, pt1, pk1, pr1); col = mix(col, s.rgb, s.a); }',
|
|
201
|
+
' gl_FragColor = vec4(col, 1.0);',
|
|
202
|
+
'}'
|
|
203
|
+
].join('\n');
|
|
204
|
+
|
|
205
|
+
function clamp(v, a, b) { return v < a ? a : v > b ? b : v; }
|
|
206
|
+
|
|
207
|
+
function parseRadius(cs) {
|
|
208
|
+
var v = cs.borderTopLeftRadius || '0';
|
|
209
|
+
var m = String(v).match(/([\d.]+)/);
|
|
210
|
+
return m ? parseFloat(m[1]) : 0;
|
|
57
211
|
}
|
|
58
212
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if (
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
if (rgba) return parseFloat(rgba[1]);
|
|
213
|
+
function alphaOf(color) {
|
|
214
|
+
if (!color || color === 'transparent') return 0;
|
|
215
|
+
var m = String(color).match(/rgba?\([^)]*,\s*([\d.]+)\s*\)$/);
|
|
216
|
+
if (m) return parseFloat(m[1]);
|
|
217
|
+
if (String(color).indexOf('#') === 0 && color.length >= 8) {
|
|
218
|
+
return parseInt(color.slice(7, 9), 16) / 255;
|
|
219
|
+
}
|
|
67
220
|
return 1;
|
|
68
221
|
}
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
if (
|
|
222
|
+
|
|
223
|
+
/* 遮挡判定:祖先有不透明底 / 背景图 / 自带磨砂 → 该玻璃不渲染 */
|
|
224
|
+
function blockedByAncestor(el) {
|
|
225
|
+
var p = el.parentElement;
|
|
226
|
+
while (p && p !== document.body) {
|
|
227
|
+
var cs = getComputedStyle(p);
|
|
228
|
+
var bf = cs.backdropFilter || cs.webkitBackdropFilter;
|
|
229
|
+
if ((bf && bf !== 'none') ||
|
|
230
|
+
(cs.backgroundImage && cs.backgroundImage !== 'none') ||
|
|
231
|
+
alphaOf(cs.backgroundColor) > 0.05) return true;
|
|
232
|
+
p = p.parentElement;
|
|
77
233
|
}
|
|
78
|
-
return
|
|
234
|
+
return false;
|
|
79
235
|
}
|
|
80
236
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
col=finish(col,px,g,x,inside,P,T);
|
|
144
|
-
return mix(base,col,clamp(.5-d,0.,1.)*T.w);}
|
|
145
|
-
vec3 top0(vec2 px){return solids(px,shade0(px),1.);}
|
|
146
|
-
void main(){
|
|
147
|
-
vec2 px=vUv*uRes;float d=field(px,1.);vec3 base=top0(px);
|
|
148
|
-
if(d<60.)base*=1.-shadow(px,1.)*(uDark>.5?.3:.14)*smoothstep(0.,2.,d);
|
|
149
|
-
if(d>=.5){gl_FragColor=vec4(base,1.);return;}
|
|
150
|
-
vec4 P;vec4 T;float hm;nearest(px,1.,P,T,hm);
|
|
151
|
-
float bez=min(uBezel,hm*.85);float inside=max(-d,0.);float x=clamp(inside/bez,0.,1.);
|
|
152
|
-
vec2 e=vec2(.75,0.);
|
|
153
|
-
vec2 g=vec2(field(px+e.xy,1.)-field(px-e.xy,1.),field(px+e.yx,1.)-field(px-e.yx,1.));g/=max(length(g),1e-4);
|
|
154
|
-
vec2 off=-g*uStrength*min(1.,hm/40.)*pow(1.-x,2.2);
|
|
155
|
-
vec3 col;col.r=top0(px+off*(1.+uDisp)).r;col.g=top0(px+off).g;col.b=top0(px+off*(1.-uDisp)).b;
|
|
156
|
-
col=finish(col,px,g,x,inside,P,T);
|
|
157
|
-
gl_FragColor=vec4(mix(base,col,clamp(.5-d,0.,1.)*T.w),1.);}`;
|
|
158
|
-
var colorCache = /* @__PURE__ */ new Map();
|
|
159
|
-
var colorCtx = null;
|
|
160
|
-
function parseColor(s) {
|
|
161
|
-
const hit = colorCache.get(s);
|
|
162
|
-
if (hit) return hit;
|
|
163
|
-
let out = [0, 0, 0, 0];
|
|
164
|
-
if (s && s !== "transparent") {
|
|
165
|
-
colorCtx != null ? colorCtx : colorCtx = Object.assign(document.createElement("canvas"), { width: 1, height: 1 }).getContext("2d", { willReadFrequently: true });
|
|
166
|
-
if (colorCtx) {
|
|
167
|
-
colorCtx.clearRect(0, 0, 1, 1);
|
|
168
|
-
colorCtx.fillStyle = s;
|
|
169
|
-
colorCtx.fillRect(0, 0, 1, 1);
|
|
170
|
-
const [r, g, b, a] = colorCtx.getImageData(0, 0, 1, 1).data;
|
|
171
|
-
out = a ? [r / 255, g / 255, b / 255, a / 255] : [0, 0, 0, 0];
|
|
237
|
+
function hexToRgb(hex) {
|
|
238
|
+
var m = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(String(hex || '').trim());
|
|
239
|
+
if (!m) return null;
|
|
240
|
+
var s = m[1];
|
|
241
|
+
if (s.length === 3) s = s[0] + s[0] + s[1] + s[1] + s[2] + s[2];
|
|
242
|
+
return [parseInt(s.slice(0, 2), 16) / 255,
|
|
243
|
+
parseInt(s.slice(2, 4), 16) / 255,
|
|
244
|
+
parseInt(s.slice(4, 6), 16) / 255];
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function cssColorToRgb(v) {
|
|
248
|
+
if (!v || v === 'none') return null;
|
|
249
|
+
var m = String(v).match(/rgba?\(\s*([\d.]+)[,\s]+([\d.]+)[,\s]+([\d.]+)/);
|
|
250
|
+
if (m) return [+m[1] / 255, +m[2] / 255, +m[3] / 255];
|
|
251
|
+
return hexToRgb(v);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function LiquidGlass(canvas, opts) {
|
|
255
|
+
opts = opts || {};
|
|
256
|
+
this.canvas = canvas;
|
|
257
|
+
this.o = {
|
|
258
|
+
refraction: opts.refraction != null ? opts.refraction : 18,
|
|
259
|
+
dispersion: opts.dispersion != null ? opts.dispersion : 0.05,
|
|
260
|
+
frost: opts.frost != null ? opts.frost : 12,
|
|
261
|
+
bezel: opts.bezel != null ? opts.bezel : 14,
|
|
262
|
+
mergeK: opts.mergeK != null ? opts.mergeK : 30,
|
|
263
|
+
rim: opts.rim != null ? opts.rim : 0.55,
|
|
264
|
+
mode: opts.mode || 'light',
|
|
265
|
+
liquid: !!opts.liquid
|
|
266
|
+
};
|
|
267
|
+
this.items = [];
|
|
268
|
+
this.solids = [];
|
|
269
|
+
this.src = null;
|
|
270
|
+
this.live = false;
|
|
271
|
+
this.dirty = true;
|
|
272
|
+
this.ready = false;
|
|
273
|
+
this.failed = false;
|
|
274
|
+
this.size = { w: 0, h: 0 };
|
|
275
|
+
this._sx = 0; this._sy = 0; this._fc = 0;
|
|
276
|
+
this._fit = [1, 1, 0, 0];
|
|
277
|
+
/* 指针:ptr = 目标位置,ptrS = 平滑后位置(制造跟随的拖尾感) */
|
|
278
|
+
this.ptr = { x: -9999, y: -9999, on: false };
|
|
279
|
+
this.ptrS = { x: -9999, y: -9999, on: false };
|
|
280
|
+
this._rc = null; this._rct = 0;
|
|
281
|
+
this.u = {};
|
|
282
|
+
this._gBuf = new Float32Array(MAXG * 4);
|
|
283
|
+
this._aBuf = new Float32Array(MAXG * 4);
|
|
284
|
+
this._bBuf = new Float32Array(MAXG * 4);
|
|
285
|
+
|
|
286
|
+
var gl = canvas.getContext('webgl', { alpha: false, antialias: false, depth: false, stencil: false })
|
|
287
|
+
|| canvas.getContext('experimental-webgl', { alpha: false });
|
|
288
|
+
if (!gl) { this.failed = true; return; }
|
|
289
|
+
this.gl = gl;
|
|
290
|
+
|
|
291
|
+
var pr = gl.createProgram();
|
|
292
|
+
var mk = function (type, srcText) {
|
|
293
|
+
var sh = gl.createShader(type);
|
|
294
|
+
gl.shaderSource(sh, srcText);
|
|
295
|
+
gl.compileShader(sh);
|
|
296
|
+
if (!gl.getShaderParameter(sh, gl.COMPILE_STATUS)) {
|
|
297
|
+
console.warn('LiquidGlassNext shader:', gl.getShaderInfoLog(sh));
|
|
298
|
+
return null;
|
|
172
299
|
}
|
|
300
|
+
return sh;
|
|
301
|
+
};
|
|
302
|
+
var vs = mk(gl.VERTEX_SHADER, VS), fs = mk(gl.FRAGMENT_SHADER, FS);
|
|
303
|
+
if (!vs || !fs) { this.failed = true; return; }
|
|
304
|
+
gl.attachShader(pr, vs); gl.attachShader(pr, fs);
|
|
305
|
+
gl.linkProgram(pr);
|
|
306
|
+
if (!gl.getProgramParameter(pr, gl.LINK_STATUS)) {
|
|
307
|
+
console.warn('LiquidGlassNext link:', gl.getProgramInfoLog(pr));
|
|
308
|
+
this.failed = true; return;
|
|
173
309
|
}
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
310
|
+
this.program = pr;
|
|
311
|
+
gl.useProgram(pr);
|
|
312
|
+
|
|
313
|
+
var b = gl.createBuffer();
|
|
314
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, b);
|
|
315
|
+
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), gl.STATIC_DRAW);
|
|
316
|
+
var loc = gl.getAttribLocation(pr, 'p');
|
|
317
|
+
gl.enableVertexAttribArray(loc);
|
|
318
|
+
gl.vertexAttribPointer(loc, 2, gl.FLOAT, false, 0, 0);
|
|
319
|
+
|
|
320
|
+
var self = this;
|
|
321
|
+
['uTex', 'uBlur', 'uRes', 'uFit', 'uGN', 'uBezel', 'uStrength', 'uDisp', 'uMergeK', 'uDark', 'uRim', 'uPtr', 'uPtrOn']
|
|
322
|
+
.forEach(function (n) { self.u[n] = gl.getUniformLocation(pr, n); });
|
|
323
|
+
['uG', 'uA', 'uB'].forEach(function (n) { self.u[n] = gl.getUniformLocation(pr, n + '[0]'); });
|
|
324
|
+
|
|
325
|
+
/* ---- 指针监听:只登记坐标,重活留到渲染帧,不在事件里读布局 ---- */
|
|
326
|
+
this._onMove = function (e) {
|
|
327
|
+
var t = (window.performance && performance.now) ? performance.now() : Date.now();
|
|
328
|
+
if (!self._rc || t - self._rct > 300) {
|
|
329
|
+
self._rc = canvas.getBoundingClientRect(); self._rct = t;
|
|
330
|
+
}
|
|
331
|
+
self.ptr.x = e.clientX - self._rc.left;
|
|
332
|
+
self.ptr.y = e.clientY - self._rc.top;
|
|
333
|
+
self.ptr.on = true;
|
|
334
|
+
};
|
|
335
|
+
this._onLeave = function () { self.ptr.on = false; };
|
|
336
|
+
this._onUp = function (e) {
|
|
337
|
+
if (e && e.pointerType && e.pointerType !== 'mouse') self.ptr.on = false;
|
|
338
|
+
};
|
|
339
|
+
window.addEventListener('pointermove', this._onMove, { passive: true });
|
|
340
|
+
window.addEventListener('pointerdown', this._onMove, { passive: true });
|
|
341
|
+
window.addEventListener('pointerup', this._onUp, { passive: true });
|
|
342
|
+
document.addEventListener('pointerleave', this._onLeave);
|
|
343
|
+
window.addEventListener('blur', this._onLeave);
|
|
344
|
+
|
|
345
|
+
this.tex = this._tex(0);
|
|
346
|
+
this.blurTex = this._tex(1);
|
|
347
|
+
gl.uniform1i(this.u.uTex, 0);
|
|
348
|
+
gl.uniform1i(this.u.uBlur, 1);
|
|
349
|
+
|
|
350
|
+
this.blurC = document.createElement('canvas');
|
|
351
|
+
|
|
352
|
+
this._raf = requestAnimationFrame(function loop() {
|
|
353
|
+
if (!self._dead) { self._frame(); self._raf = requestAnimationFrame(loop); }
|
|
354
|
+
});
|
|
177
355
|
}
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
356
|
+
|
|
357
|
+
LiquidGlass.prototype._tex = function (unit) {
|
|
358
|
+
var gl = this.gl, t = gl.createTexture();
|
|
359
|
+
gl.activeTexture(gl.TEXTURE0 + unit);
|
|
360
|
+
gl.bindTexture(gl.TEXTURE_2D, t);
|
|
361
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
362
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
363
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
364
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
365
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE,
|
|
366
|
+
new Uint8Array([128, 128, 128, 255]));
|
|
367
|
+
return t;
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
LiquidGlass.prototype.set = function (o) {
|
|
371
|
+
for (var k in (o || {})) this.o[k] = o[k];
|
|
372
|
+
this.dirty = true;
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
LiquidGlass.prototype.setSource = function (src, opt) {
|
|
376
|
+
opt = opt || {};
|
|
377
|
+
this.src = src;
|
|
378
|
+
this.live = opt.live != null ? !!opt.live
|
|
379
|
+
: (typeof HTMLVideoElement !== 'undefined' && src instanceof HTMLVideoElement);
|
|
380
|
+
this.dirty = true;
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
LiquidGlass.prototype.invalidate = function () { this.dirty = true; };
|
|
384
|
+
|
|
385
|
+
LiquidGlass.prototype.add = function (el, opts) {
|
|
386
|
+
for (var i = 0; i < this.items.length; i++) if (this.items[i].el === el) return this.items[i];
|
|
387
|
+
var d = el.dataset, pre = PRESETS[(opts && opts.preset) || d.glass] || PRESETS.bar;
|
|
388
|
+
var it = {
|
|
389
|
+
el: el,
|
|
390
|
+
grow: pre.grow, frost: pre.frost, tint: pre.tint, merge: pre.merge, layer: pre.layer,
|
|
391
|
+
dl: 0, dt: 0, dw: 0, dh: 0, rad: 0,
|
|
392
|
+
tintC: [1, 1, 1], hidden: false, blocked: false, parent: null, live: false
|
|
393
|
+
};
|
|
394
|
+
this.items.push(it);
|
|
395
|
+
return it;
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
LiquidGlass.prototype.remove = function (el) {
|
|
399
|
+
el.removeAttribute('data-glass-live');
|
|
400
|
+
this.items = this.items.filter(function (i) { return i.el !== el; });
|
|
401
|
+
this.solids = this.solids.filter(function (s) { return s.el !== el; });
|
|
402
|
+
};
|
|
403
|
+
|
|
404
|
+
/* 每帧打标:shader 本帧实际绘制的元素才挂 data-glass-live,
|
|
405
|
+
其余退回 CSS 自绘玻璃(backdrop-filter 直接糊引擎画布) */
|
|
406
|
+
LiquidGlass.prototype._syncLive = function () {
|
|
407
|
+
var items = this.items;
|
|
408
|
+
for (var i = 0; i < items.length; i++) {
|
|
409
|
+
var it = items[i], want = !it.blocked && !it.hidden && it._drawn === true;
|
|
410
|
+
if (it.live === want) continue;
|
|
411
|
+
it.live = want;
|
|
412
|
+
if (want) it.el.setAttribute('data-glass-live', '');
|
|
413
|
+
else it.el.removeAttribute('data-glass-live');
|
|
186
414
|
}
|
|
187
|
-
return null;
|
|
188
415
|
};
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
__publicField(this, "light", { x: -400, y: -600 });
|
|
207
|
-
__publicField(this, "fit", [1, 1, 0, 0]);
|
|
208
|
-
__publicField(this, "size", { w: 0, h: 0 });
|
|
209
|
-
__publicField(this, "src", null);
|
|
210
|
-
__publicField(this, "live", false);
|
|
211
|
-
__publicField(this, "dirty", false);
|
|
212
|
-
__publicField(this, "ready", false);
|
|
213
|
-
__publicField(this, "raf", 0);
|
|
214
|
-
__publicField(this, "frames", 0);
|
|
215
|
-
__publicField(this, "nextId", 1);
|
|
216
|
-
__publicField(this, "bG", new Float32Array(MAXG * 4));
|
|
217
|
-
__publicField(this, "bGP", new Float32Array(MAXG * 4));
|
|
218
|
-
__publicField(this, "bGT", new Float32Array(MAXG * 4));
|
|
219
|
-
__publicField(this, "bS", new Float32Array(MAXS * 4));
|
|
220
|
-
__publicField(this, "bSC", new Float32Array(MAXS * 4));
|
|
221
|
-
__publicField(this, "bSP", new Float32Array(MAXS * 4));
|
|
222
|
-
__publicField(this, "_onPtr", (e) => {
|
|
223
|
-
this.ptr = { x: e.clientX, y: e.clientY };
|
|
224
|
-
this.lastPtr = performance.now();
|
|
225
|
-
});
|
|
226
|
-
__publicField(this, "_onUp", () => this.items.forEach((i) => {
|
|
227
|
-
if (!i.held) i.pressed = false;
|
|
228
|
-
}));
|
|
229
|
-
__publicField(this, "_onTilt", (e) => {
|
|
230
|
-
if (e.gamma != null && e.beta != null && performance.now() - this.lastPtr > 3e3) {
|
|
231
|
-
this.tilt = { g: e.gamma, b: e.beta };
|
|
232
|
-
this.ptr = null;
|
|
416
|
+
|
|
417
|
+
LiquidGlass.prototype.scan = function (root) {
|
|
418
|
+
root = root || document;
|
|
419
|
+
var self = this;
|
|
420
|
+
var seen = [];
|
|
421
|
+
Array.prototype.forEach.call(root.querySelectorAll('[data-glass]'), function (el) {
|
|
422
|
+
seen.push(el);
|
|
423
|
+
for (var i = 0; i < self.items.length; i++) if (self.items[i].el === el) return;
|
|
424
|
+
self.add(el);
|
|
425
|
+
});
|
|
426
|
+
/* 父子关系:最近的带 data-glass 祖先。子元素画在 layer 1,叠在父层玻璃之上 */
|
|
427
|
+
this.items.forEach(function (it) {
|
|
428
|
+
var p = it.el.parentElement, found = null;
|
|
429
|
+
while (p && p !== document.body) {
|
|
430
|
+
if (p.hasAttribute && p.hasAttribute('data-glass')) {
|
|
431
|
+
for (var k = 0; k < self.items.length; k++) if (self.items[k].el === p) { found = self.items[k]; break; }
|
|
432
|
+
if (found) break;
|
|
233
433
|
}
|
|
234
|
-
|
|
235
|
-
__publicField(this, "_loop", () => {
|
|
236
|
-
/* 页面不可见时不绘制(纯省电,不影响观感) */
|
|
237
|
-
if (document.hidden) { this.raf = requestAnimationFrame(this._loop); return; }
|
|
238
|
-
this._frame();
|
|
239
|
-
this.raf = requestAnimationFrame(this._loop);
|
|
240
|
-
});
|
|
241
|
-
this.canvas = canvas;
|
|
242
|
-
this.o = { refraction: 28, dispersion: 0.1, frost: 14, bezel: 26, mergeK: 40, rim: 0.75, mode: "light", liquid: true, ...opts };
|
|
243
|
-
const gl = canvas.getContext("webgl", { antialias: false });
|
|
244
|
-
if (!gl || gl.isContextLost()) {
|
|
245
|
-
this.failed = true;
|
|
246
|
-
console.warn("LiquidGlass: WebGL unavailable");
|
|
247
|
-
return;
|
|
434
|
+
p = p.parentElement;
|
|
248
435
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
this.blurTex = this._tex(1);
|
|
283
|
-
gl.uniform1i(this.u.uTex, 0);
|
|
284
|
-
gl.uniform1i(this.u.uBlur, 1);
|
|
285
|
-
this.blurC = document.createElement("canvas");
|
|
286
|
-
window.addEventListener("pointermove", this._onPtr);
|
|
287
|
-
window.addEventListener("pointerup", this._onUp);
|
|
288
|
-
window.addEventListener("pointercancel", this._onUp);
|
|
289
|
-
window.addEventListener("deviceorientation", this._onTilt);
|
|
290
|
-
this.raf = requestAnimationFrame(this._loop);
|
|
291
|
-
}
|
|
292
|
-
_tex(unit) {
|
|
293
|
-
const gl = this.gl, t = gl.createTexture();
|
|
294
|
-
gl.activeTexture(gl.TEXTURE0 + unit);
|
|
295
|
-
gl.bindTexture(gl.TEXTURE_2D, t);
|
|
296
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
|
|
297
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
|
|
298
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
299
|
-
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
300
|
-
return t;
|
|
301
|
-
}
|
|
302
|
-
set(o) {
|
|
303
|
-
const f = this.o.frost;
|
|
304
|
-
Object.assign(this.o, o);
|
|
305
|
-
if (o.frost !== void 0 && o.frost !== f) this.dirty = true;
|
|
306
|
-
}
|
|
307
|
-
setSource(src, { live } = {}) {
|
|
308
|
-
this.src = src;
|
|
309
|
-
this.live = live != null ? live : typeof HTMLVideoElement !== "undefined" && src instanceof HTMLVideoElement;
|
|
310
|
-
this.dirty = true;
|
|
311
|
-
}
|
|
312
|
-
invalidate() {
|
|
313
|
-
this.dirty = true;
|
|
436
|
+
it.parent = found;
|
|
437
|
+
});
|
|
438
|
+
this.items = this.items.filter(function (i) {
|
|
439
|
+
if (i.el.isConnected && seen.indexOf(i.el) >= 0) return true;
|
|
440
|
+
i.el.removeAttribute('data-glass-live');
|
|
441
|
+
return false;
|
|
442
|
+
});
|
|
443
|
+
this.solids = this.solids.filter(function (s) {
|
|
444
|
+
return s.el.isConnected && s.el.hasAttribute('data-glass-solid');
|
|
445
|
+
});
|
|
446
|
+
Array.prototype.forEach.call(root.querySelectorAll('[data-glass-solid]'), function (el) {
|
|
447
|
+
for (var i = 0; i < self.solids.length; i++) if (self.solids[i].el === el) return;
|
|
448
|
+
self.solids.push({ el: el, layer: parseFloat(el.dataset.layer || '0') });
|
|
449
|
+
});
|
|
450
|
+
this._occl();
|
|
451
|
+
this.dirty = true;
|
|
452
|
+
};
|
|
453
|
+
|
|
454
|
+
LiquidGlass.prototype._occl = function () {
|
|
455
|
+
this.items.forEach(function (it) {
|
|
456
|
+
it.blocked = it.el.isConnected ? blockedByAncestor(it.el) : true;
|
|
457
|
+
});
|
|
458
|
+
};
|
|
459
|
+
|
|
460
|
+
LiquidGlass.prototype.destroy = function () {
|
|
461
|
+
this._dead = true;
|
|
462
|
+
if (this._raf) cancelAnimationFrame(this._raf);
|
|
463
|
+
if (this._onMove) {
|
|
464
|
+
window.removeEventListener('pointermove', this._onMove);
|
|
465
|
+
window.removeEventListener('pointerdown', this._onMove);
|
|
466
|
+
window.removeEventListener('pointerup', this._onUp);
|
|
467
|
+
document.removeEventListener('pointerleave', this._onLeave);
|
|
468
|
+
window.removeEventListener('blur', this._onLeave);
|
|
314
469
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
this.
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
this.solids = this.solids.filter((s) => keep(s.el, "data-glass-solid"));
|
|
323
|
-
rootEl.querySelectorAll("[data-glass]").forEach((el) => this.add(el));
|
|
324
|
-
rootEl.querySelectorAll("[data-glass-solid]").forEach((el) => this.addSolid(el));
|
|
325
|
-
this.refreshOcclusion();
|
|
470
|
+
document.documentElement.classList.remove('lg-ready');
|
|
471
|
+
this.items.forEach(function (i) { i.el.removeAttribute('data-glass-live'); });
|
|
472
|
+
var gl = this.gl;
|
|
473
|
+
if (gl) {
|
|
474
|
+
if (this.tex) gl.deleteTexture(this.tex);
|
|
475
|
+
if (this.blurTex) gl.deleteTexture(this.blurTex);
|
|
476
|
+
if (this.program) gl.deleteProgram(this.program);
|
|
326
477
|
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
LiquidGlass.prototype._frame = function () {
|
|
481
|
+
var gl = this.gl, cv = this.canvas;
|
|
482
|
+
var W = cv.clientWidth, H = cv.clientHeight;
|
|
483
|
+
if (!W || !H || document.hidden) return;
|
|
484
|
+
|
|
485
|
+
/* ---- 滚动帧零 DOM 读取:位置 = 文档坐标缓存 − scroll ---- */
|
|
486
|
+
var sx = window.scrollX, sy = window.scrollY;
|
|
487
|
+
var scrolling = sx !== this._sx || sy !== this._sy;
|
|
488
|
+
this._sx = sx; this._sy = sy;
|
|
489
|
+
|
|
490
|
+
var dpr = Math.min(window.devicePixelRatio || 1, 1.5);
|
|
491
|
+
var bw = Math.round(W * dpr), bh = Math.round(H * dpr);
|
|
492
|
+
if (cv.width !== bw || cv.height !== bh) { cv.width = bw; cv.height = bh; this.dirty = true; }
|
|
493
|
+
gl.viewport(0, 0, bw, bh);
|
|
494
|
+
if (W !== this.size.w || H !== this.size.h) { this.size = { w: W, h: H }; this.dirty = true; }
|
|
495
|
+
|
|
496
|
+
/* ---- 采集几何 → uniform 缓冲 ---- */
|
|
497
|
+
var gN = 0;
|
|
498
|
+
var gB = this._gBuf, aB = this._aBuf, bB = this._bBuf;
|
|
499
|
+
var items = this.items;
|
|
500
|
+
for (var i = 0; i < items.length; i++) items[i]._drawn = false;
|
|
501
|
+
|
|
502
|
+
/* solid 件(滑块轨道/进度条)不进 shader:引擎画布位于页面最底层,
|
|
503
|
+
任何带自身背景的容器都会遮住它。纯色件一律交回 DOM 自绘。 */
|
|
504
|
+
|
|
505
|
+
for (var i = 0; i < items.length && gN < MAXG; i++) {
|
|
506
|
+
var it = items[i], el = it.el;
|
|
507
|
+
if (!scrolling) it.blocked = blockedByAncestor(el);
|
|
508
|
+
if (it.blocked) continue;
|
|
509
|
+
if (!scrolling || !it.dw) {
|
|
510
|
+
var cs = getComputedStyle(el);
|
|
511
|
+
if (cs.display === 'none' || cs.visibility === 'hidden' || parseFloat(cs.opacity) < 0.05) {
|
|
512
|
+
it.hidden = true; continue;
|
|
354
513
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
514
|
+
it.hidden = false;
|
|
515
|
+
var r = el.getBoundingClientRect();
|
|
516
|
+
if (r.width < 2 || r.height < 2) { it.hidden = true; continue; }
|
|
517
|
+
it.dl = r.left + sx; it.dt = r.top + sy;
|
|
518
|
+
it.dw = r.width; it.dh = r.height;
|
|
519
|
+
it.rad = parseRadius(cs);
|
|
520
|
+
/* data-* 覆盖每帧重读(glass-webgl 可能在 scan 后打标/改标) */
|
|
521
|
+
var dset = el.dataset;
|
|
522
|
+
['tint', 'frost', 'layer', 'merge', 'grow'].forEach(function (k) {
|
|
523
|
+
if (dset[k] !== undefined) { var v = parseFloat(dset[k]); if (!isNaN(v)) it[k] = v; }
|
|
524
|
+
});
|
|
525
|
+
/* 着色解析:保留 CSS 颜色的 alpha(transparent → 不着色,由 DOM wash 兜底) */
|
|
526
|
+
var dark = this.o.mode === 'dark';
|
|
527
|
+
var tv = cs.getPropertyValue('--lg-tint').trim();
|
|
528
|
+
var tc = null, taMul = 0;
|
|
529
|
+
if (tv && tv !== 'transparent' && tv !== 'none') {
|
|
530
|
+
tc = cssColorToRgb(tv);
|
|
531
|
+
if (tc) taMul = alphaOf(tv);
|
|
361
532
|
}
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
if (existing) return existing;
|
|
367
|
-
const d = el.dataset, pre = PRESETS[opts.preset || d.glass] || PRESETS.bar;
|
|
368
|
-
const ov = {};
|
|
369
|
-
["tint", "frost", "layer", "merge", "grow", "squash"].forEach((k) => {
|
|
370
|
-
if (d[k] !== void 0) ov[k] = parseFloat(d[k]);
|
|
371
|
-
});
|
|
372
|
-
const it = {
|
|
373
|
-
...pre,
|
|
374
|
-
...ov,
|
|
375
|
-
...opts,
|
|
376
|
-
el,
|
|
377
|
-
sx: 1,
|
|
378
|
-
sy: 1,
|
|
379
|
-
vx: 0,
|
|
380
|
-
vy: 0,
|
|
381
|
-
v: { x: 0, y: 0 },
|
|
382
|
-
last: null,
|
|
383
|
-
pressed: false,
|
|
384
|
-
held: false,
|
|
385
|
-
scs: "",
|
|
386
|
-
t: 0,
|
|
387
|
-
id: this.nextId++,
|
|
388
|
-
parent: null,
|
|
389
|
-
blocked: false,
|
|
390
|
-
live: false,
|
|
391
|
-
down: () => {
|
|
392
|
-
it.pressed = true;
|
|
533
|
+
if (!tc) {
|
|
534
|
+
var bgv = cs.backgroundColor;
|
|
535
|
+
tc = cssColorToRgb(bgv);
|
|
536
|
+
if (tc) taMul = alphaOf(bgv);
|
|
393
537
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
this.items.push(it);
|
|
398
|
-
return it;
|
|
399
|
-
}
|
|
400
|
-
addSolid(el) {
|
|
401
|
-
if (this.solids.some((s) => s.el === el)) return;
|
|
402
|
-
this.solids.push({ el, layer: parseFloat(el.dataset.layer || "0"), opacity: el.style.opacity, live: false, parent: null, blocked: false });
|
|
403
|
-
}
|
|
404
|
-
press(el, on) {
|
|
405
|
-
const it = this.items.find((i) => i.el === el);
|
|
406
|
-
if (it) {
|
|
407
|
-
it.pressed = on;
|
|
408
|
-
it.held = on;
|
|
538
|
+
if (!tc) tc = dark ? [0.09, 0.09, 0.11] : [1, 1, 1];
|
|
539
|
+
it.tintC = tc;
|
|
540
|
+
it.tintA = taMul;
|
|
409
541
|
}
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
this.solids = this.solids.filter((s2) => s2.el !== el);
|
|
418
|
-
}
|
|
419
|
-
/** Stops rendering and releases GL resources. The canvas stays usable, so a remount
|
|
420
|
-
(React strict mode, HMR) can create a new engine on the same element. */
|
|
421
|
-
destroy() {
|
|
422
|
-
cancelAnimationFrame(this.raf);
|
|
423
|
-
document.documentElement.classList.remove("lg-ready");
|
|
424
|
-
window.removeEventListener("pointermove", this._onPtr);
|
|
425
|
-
window.removeEventListener("pointerup", this._onUp);
|
|
426
|
-
window.removeEventListener("pointercancel", this._onUp);
|
|
427
|
-
window.removeEventListener("deviceorientation", this._onTilt);
|
|
428
|
-
this.items.forEach((i) => this._detach(i));
|
|
429
|
-
this.solids.forEach((s) => {
|
|
430
|
-
if (s.live) s.el.style.opacity = s.opacity;
|
|
431
|
-
});
|
|
432
|
-
this.items = [];
|
|
433
|
-
this.solids = [];
|
|
434
|
-
const gl = this.gl;
|
|
435
|
-
if (gl && !this.failed) {
|
|
436
|
-
gl.deleteTexture(this.tex);
|
|
437
|
-
gl.deleteTexture(this.blurTex);
|
|
438
|
-
gl.deleteBuffer(this.buffer);
|
|
439
|
-
gl.deleteProgram(this.program);
|
|
440
|
-
gl.clearColor(0, 0, 0, 0);
|
|
441
|
-
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
542
|
+
if (it.hidden) continue;
|
|
543
|
+
|
|
544
|
+
var hw = it.dw / 2, hh = it.dh / 2;
|
|
545
|
+
var grow = it.grow || 0;
|
|
546
|
+
if (grow > 0) {
|
|
547
|
+
var pad = grow * Math.min(hw, hh);
|
|
548
|
+
hw += pad; hh += pad;
|
|
442
549
|
}
|
|
550
|
+
/* 中心永远取元素几何中心:grow 只向外扩边,不得移动中心
|
|
551
|
+
(否则玻璃整体向右下偏 pad px,表现为"内容不居中/外圈偏") */
|
|
552
|
+
var x = it.dl - sx + it.dw / 2, y = it.dt - sy + it.dh / 2;
|
|
553
|
+
if (x + hw < 0 || x - hw > W || y + hh < 0 || y - hh > H) continue;
|
|
554
|
+
|
|
555
|
+
var o4 = gN * 4;
|
|
556
|
+
/* 嵌套深度:子元素(父链上有其他玻璃)画在 layer 1,叠在父层之上 */
|
|
557
|
+
var depth = 0, anc = it.parent;
|
|
558
|
+
while (anc) { depth++; anc = anc.parent; }
|
|
559
|
+
if (depth > 1) continue;
|
|
560
|
+
gB[o4] = x; gB[o4 + 1] = y; gB[o4 + 2] = hw; gB[o4 + 3] = hh;
|
|
561
|
+
aB[o4] = Math.min(it.rad, Math.min(hw, hh));
|
|
562
|
+
aB[o4 + 1] = (depth >= 1 || it.layer >= 1) ? 1 : 0;
|
|
563
|
+
aB[o4 + 2] = clamp(it.frost, 0, 1);
|
|
564
|
+
aB[o4 + 3] = clamp(it.tint * (it.tintA != null ? it.tintA : 1), 0, 1);
|
|
565
|
+
bB[o4] = it.merge ? 1 : 0;
|
|
566
|
+
bB[o4 + 1] = it.tintC[0]; bB[o4 + 2] = it.tintC[1]; bB[o4 + 3] = it.tintC[2];
|
|
567
|
+
it._drawn = true;
|
|
568
|
+
gN++;
|
|
443
569
|
}
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
570
|
+
this._syncLive();
|
|
571
|
+
|
|
572
|
+
/* ---- 纹理上传(live 隔帧;脏时必传) ---- */
|
|
573
|
+
if (this.src && (this.dirty || (this.live && (this._fc % 2 === 0)))) this._upload();
|
|
574
|
+
|
|
575
|
+
gl.useProgram(this.program);
|
|
576
|
+
var f = this._fit;
|
|
577
|
+
gl.uniform4f(this.u.uFit, f[0], f[1], f[2], f[3]);
|
|
578
|
+
gl.uniform2f(this.u.uRes, W, H);
|
|
579
|
+
gl.uniform1i(this.u.uGN, gN);
|
|
580
|
+
gl.uniform1f(this.u.uBezel, this.o.bezel);
|
|
581
|
+
gl.uniform1f(this.u.uStrength, this.o.refraction);
|
|
582
|
+
gl.uniform1f(this.u.uDisp, this.o.dispersion);
|
|
583
|
+
gl.uniform1f(this.u.uMergeK, this.o.mergeK);
|
|
584
|
+
gl.uniform1f(this.u.uDark, this.o.mode === 'dark' ? 1 : 0);
|
|
585
|
+
gl.uniform1f(this.u.uRim, this.o.rim);
|
|
586
|
+
|
|
587
|
+
/* 指针平滑:进入页面时直接就位,移动时带一点跟手延迟 → 光沿边框滑动 */
|
|
588
|
+
var tp = this.ptr, sp = this.ptrS;
|
|
589
|
+
if (tp.on) {
|
|
590
|
+
if (!sp.on) { sp.x = tp.x; sp.y = tp.y; sp.on = true; }
|
|
453
591
|
else {
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
it.sx = it.sy = 1;
|
|
458
|
-
it.vx = it.vy = 0;
|
|
592
|
+
sp.x += (tp.x - sp.x) * 0.22;
|
|
593
|
+
sp.y += (tp.y - sp.y) * 0.22;
|
|
594
|
+
if (Math.abs(tp.x - sp.x) < 0.4 && Math.abs(tp.y - sp.y) < 0.4) { sp.x = tp.x; sp.y = tp.y; }
|
|
459
595
|
}
|
|
596
|
+
} else {
|
|
597
|
+
sp.on = false;
|
|
460
598
|
}
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
const A = W / H, B = sw / sh;
|
|
468
|
-
let fx = 1, fy = 1, ox = 0, oy = 0;
|
|
469
|
-
if (B > A) {
|
|
470
|
-
fx = A / B;
|
|
471
|
-
ox = (1 - fx) / 2;
|
|
472
|
-
} else {
|
|
473
|
-
fy = B / A;
|
|
474
|
-
oy = (1 - fy) / 2;
|
|
475
|
-
}
|
|
476
|
-
this.fit = [fx, fy, ox, oy];
|
|
477
|
-
try {
|
|
478
|
-
gl.activeTexture(gl.TEXTURE0);
|
|
479
|
-
gl.bindTexture(gl.TEXTURE_2D, this.tex);
|
|
480
|
-
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, s);
|
|
481
|
-
const bc = this.blurC, bw = Math.max(2, Math.round(W / 4)), bh = Math.max(2, Math.round(H / 4));
|
|
482
|
-
if (bc.width !== bw) bc.width = bw;
|
|
483
|
-
if (bc.height !== bh) bc.height = bh;
|
|
484
|
-
const x = bc.getContext("2d"), br = Math.max(this.o.frost / 4, 0.01), pad = br * 3;
|
|
485
|
-
x.filter = `blur(${br}px)`;
|
|
486
|
-
x.drawImage(s, ox * sw, oy * sh, fx * sw, fy * sh, -pad, -pad, bw + 2 * pad, bh + 2 * pad);
|
|
487
|
-
gl.activeTexture(gl.TEXTURE1);
|
|
488
|
-
gl.bindTexture(gl.TEXTURE_2D, this.blurTex);
|
|
489
|
-
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, bc);
|
|
490
|
-
this.ready = true;
|
|
491
|
-
document.documentElement.classList.add("lg-ready");
|
|
492
|
-
} catch (e) {
|
|
493
|
-
console.warn("LiquidGlass: source not usable (CORS?)", e);
|
|
494
|
-
}
|
|
495
|
-
this.dirty = false;
|
|
599
|
+
gl.uniform2f(this.u.uPtr, sp.x, sp.y);
|
|
600
|
+
gl.uniform1f(this.u.uPtrOn, sp.on ? 1 : 0);
|
|
601
|
+
if (gN) {
|
|
602
|
+
gl.uniform4fv(this.u.uG, gB);
|
|
603
|
+
gl.uniform4fv(this.u.uA, aB);
|
|
604
|
+
gl.uniform4fv(this.u.uB, bB);
|
|
496
605
|
}
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
gl.
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
} else if (this.tilt) {
|
|
538
|
-
tx = W / 2 + this.tilt.g / 30 * W;
|
|
539
|
-
ty = H / 2 + (this.tilt.b - 45) / 30 * H;
|
|
540
|
-
}
|
|
541
|
-
this.light.x += (tx - this.light.x) * 0.12;
|
|
542
|
-
this.light.y += (ty - this.light.y) * 0.12;
|
|
543
|
-
const dark = this.o.mode === "dark", liquid = this.o.liquid;
|
|
544
|
-
const vis = /* @__PURE__ */ new Map();
|
|
545
|
-
const cands = [];
|
|
546
|
-
for (const it of this.items) {
|
|
547
|
-
const el = it.el;
|
|
548
|
-
if (!el.isConnected || it.blocked) continue;
|
|
549
|
-
let r;
|
|
550
|
-
if (it.sco === void 0) it.sco = scrollParentOf(el);
|
|
551
|
-
const sdY = it.sco ? it.sco.scrollTop - it.sTop : 0;
|
|
552
|
-
const sdX = it.sco ? it.sco.scrollLeft - it.sLeft : 0;
|
|
553
|
-
if (it.dl !== void 0 && (scrolling || sdY || sdX)) {
|
|
554
|
-
r = { left: it.dl - sx - sdX, top: it.dt - sy - sdY, right: it.dl - sx - sdX + it.dw, bottom: it.dt - sy - sdY + it.dh, width: it.dw, height: it.dh };
|
|
555
|
-
} else {
|
|
556
|
-
r = el.getBoundingClientRect();
|
|
557
|
-
it.dl = r.left + sx; it.dt = r.top + sy; it.dw = r.width; it.dh = r.height;
|
|
558
|
-
if (it.sco) { it.sTop = it.sco.scrollTop; it.sLeft = it.sco.scrollLeft; }
|
|
559
|
-
it.ow = el.offsetWidth || r.width;
|
|
560
|
-
const cs0 = getComputedStyle(el);
|
|
561
|
-
it.vis = cs0.visibility === "hidden";
|
|
562
|
-
it.rad0 = len(cs0.borderTopLeftRadius, it.ow);
|
|
563
|
-
it.tv = cs0.getPropertyValue("--lg-tint").trim();
|
|
564
|
-
it.op = effectiveOpacity(el);
|
|
565
|
-
}
|
|
566
|
-
if (r.width < 1 || r.height < 1) continue;
|
|
567
|
-
if (r.bottom < cr.top - 40 || r.top > cr.bottom + 40 || r.right < cr.left - 40 || r.left > cr.right + 40) continue;
|
|
568
|
-
if (it.vis) continue;
|
|
569
|
-
vis.set(it.id, { it, r });
|
|
570
|
-
cands.push({ id: it.id, parent: (_b = (_a = it.parent) == null ? void 0 : _a.id) != null ? _b : null, area: r.width * r.height, knob: it.el.dataset.glass === "knob" ? 1 : 0 });
|
|
571
|
-
}
|
|
572
|
-
const chosen = selectShapes(cands, MAXG);
|
|
573
|
-
for (const it of this.items) this._setLive(it, chosen.has(it.id));
|
|
574
|
-
let n = 0;
|
|
575
|
-
for (const [id, depth] of chosen) {
|
|
576
|
-
const { it, r } = vis.get(id);
|
|
577
|
-
const el = it.el;
|
|
578
|
-
const op = it.op;
|
|
579
|
-
const cx = (r.left + r.width / 2 - cr.left) * k, cy = (r.top + r.height / 2 - cr.top) * k;
|
|
580
|
-
const hx = r.width / 2 * k / it.sx, hy = r.height / 2 * k / it.sy;
|
|
581
|
-
if (it.last) {
|
|
582
|
-
it.v.x = it.v.x * 0.6 + (cx - it.last.x) * 0.4;
|
|
583
|
-
it.v.y = it.v.y * 0.6 + (cy - it.last.y) * 0.4;
|
|
584
|
-
}
|
|
585
|
-
it.last = { x: cx, y: cy };
|
|
586
|
-
const g = it.pressed && liquid ? 1 + it.grow : 1, sq = liquid ? it.squash : 0;
|
|
587
|
-
const ax = Math.abs(it.v.x), ay = Math.abs(it.v.y);
|
|
588
|
-
const tX = g * (1 + Math.min(ax * 0.018 * sq, 0.35) - Math.min(ay * 9e-3 * sq, 0.18));
|
|
589
|
-
const tY = g * (1 + Math.min(ay * 0.018 * sq, 0.35) - Math.min(ax * 9e-3 * sq, 0.18));
|
|
590
|
-
if (liquid) {
|
|
591
|
-
it.vx = (it.vx + (tX - it.sx) * 0.22) * 0.7;
|
|
592
|
-
it.sx += it.vx;
|
|
593
|
-
it.vy = (it.vy + (tY - it.sy) * 0.22) * 0.7;
|
|
594
|
-
it.sy += it.vy;
|
|
595
|
-
} else {
|
|
596
|
-
it.sx = tX;
|
|
597
|
-
it.sy = tY;
|
|
598
|
-
it.vx = it.vy = 0;
|
|
599
|
-
}
|
|
600
|
-
const scs = Math.abs(it.sx - 1) < 2e-3 && Math.abs(it.sy - 1) < 2e-3 ? "" : `${it.sx.toFixed(4)} ${it.sy.toFixed(4)}`;
|
|
601
|
-
if (scs !== it.scs) {
|
|
602
|
-
el.style.scale = scs;
|
|
603
|
-
it.scs = scs;
|
|
604
|
-
}
|
|
605
|
-
const Hx = hx * it.sx, Hy = hy * it.sy, ow = it.ow || r.width;
|
|
606
|
-
const rad = Math.min(it.rad0 * (hx * 2 / ow) * Math.min(it.sx, it.sy), Hx, Hy);
|
|
607
|
-
const ds = el.dataset;
|
|
608
|
-
if (ds.tint !== void 0) it.tint = parseFloat(ds.tint);
|
|
609
|
-
if (ds.grow !== void 0) it.grow = parseFloat(ds.grow);
|
|
610
|
-
const tt = it.pressed && it.tintPressed !== void 0 ? it.tintPressed : it.tint;
|
|
611
|
-
it.t += (tt - it.t) * 0.2;
|
|
612
|
-
const tv = it.tv;
|
|
613
|
-
const custom = tv && tv !== "transparent" ? parseColor(tv) : null;
|
|
614
|
-
const tc = it.tintColor || (custom ? [custom[0], custom[1], custom[2]] : dark ? [0.07, 0.07, 0.09] : [1, 1, 1]);
|
|
615
|
-
const layer = Math.max(depth, it.layer >= 1 ? 1 : 0);
|
|
616
|
-
this.bG.set([cx, cy, Hx, Hy], n * 4);
|
|
617
|
-
this.bGP.set([rad, layer >= 1 ? 2 : it.merge ? 1 : 0, it.t, it.frost], n * 4);
|
|
618
|
-
this.bGT.set([tc[0], tc[1], tc[2], op], n * 4);
|
|
619
|
-
n++;
|
|
620
|
-
}
|
|
621
|
-
let m = 0;
|
|
622
|
-
for (const s of this.solids) {
|
|
623
|
-
const el = s.el;
|
|
624
|
-
let r;
|
|
625
|
-
if (scrolling && s.dl !== void 0) {
|
|
626
|
-
r = { left: s.dl - sx, top: s.dt - sy, width: s.dw, height: s.dh };
|
|
627
|
-
} else if (el.isConnected) {
|
|
628
|
-
r = el.getBoundingClientRect();
|
|
629
|
-
s.dl = r.left + sx; s.dt = r.top + sy; s.dw = r.width; s.dh = r.height;
|
|
630
|
-
s.ow = el.offsetWidth || r.width;
|
|
631
|
-
const cs0 = getComputedStyle(el);
|
|
632
|
-
s.solidSrc = el.dataset.solidColor || cs0.getPropertyValue("--lg-solid").trim() || cs0.backgroundColor;
|
|
633
|
-
s.rad0 = len(cs0.borderTopLeftRadius, s.ow);
|
|
634
|
-
} else {
|
|
635
|
-
r = null;
|
|
636
|
-
}
|
|
637
|
-
const draw = m < MAXS && !s.blocked && (!s.parent || s.parent.live) && !!r && r.width >= 1 && r.height >= 1;
|
|
638
|
-
if (draw !== s.live) {
|
|
639
|
-
s.live = draw;
|
|
640
|
-
el.style.opacity = draw ? "0" : s.opacity;
|
|
641
|
-
}
|
|
642
|
-
if (!draw) continue;
|
|
643
|
-
const hx = r.width / 2 * k, hy = r.height / 2 * k;
|
|
644
|
-
this.bS.set([(r.left + r.width / 2 - cr.left) * k, (r.top + r.height / 2 - cr.top) * k, hx, hy], m * 4);
|
|
645
|
-
this.bSC.set(parseColor(s.solidSrc), m * 4);
|
|
646
|
-
this.bSP.set([Math.min(s.rad0 * (hx * 2 / s.ow), hx, hy), s.layer, 0, 0], m * 4);
|
|
647
|
-
m++;
|
|
648
|
-
}
|
|
649
|
-
const u = this.u, o = this.o;
|
|
650
|
-
gl.uniform2f(u.uRes, W, H);
|
|
651
|
-
gl.uniform4fv(u.uFit, this.fit);
|
|
652
|
-
gl.uniform4fv(u.uG, this.bG);
|
|
653
|
-
gl.uniform4fv(u.uGP, this.bGP);
|
|
654
|
-
gl.uniform4fv(u.uGT, this.bGT);
|
|
655
|
-
gl.uniform1i(u.uGN, n);
|
|
656
|
-
gl.uniform4fv(u.uS, this.bS);
|
|
657
|
-
gl.uniform4fv(u.uSC, this.bSC);
|
|
658
|
-
gl.uniform4fv(u.uSP, this.bSP);
|
|
659
|
-
gl.uniform1i(u.uSN, m);
|
|
660
|
-
gl.uniform1f(u.uBezel, o.bezel);
|
|
661
|
-
gl.uniform1f(u.uStrength, o.refraction);
|
|
662
|
-
gl.uniform1f(u.uDisp, o.dispersion);
|
|
663
|
-
gl.uniform1f(u.uMergeK, Math.max(o.mergeK, 1e-3));
|
|
664
|
-
gl.uniform1f(u.uDark, dark ? 1 : 0);
|
|
665
|
-
gl.uniform1f(u.uRim, o.rim != null ? o.rim : 0.75);
|
|
666
|
-
gl.uniform2f(u.uPtr, this.light.x, this.light.y);
|
|
667
|
-
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
|
606
|
+
|
|
607
|
+
this._fc++;
|
|
608
|
+
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
|
609
|
+
this.dirty = false;
|
|
610
|
+
};
|
|
611
|
+
|
|
612
|
+
/* 上传壁纸原图 + CPU 模糊图(cover 铺满,uFit 记录屏幕→纹理映射) */
|
|
613
|
+
LiquidGlass.prototype._upload = function () {
|
|
614
|
+
var gl = this.gl, s = this.src;
|
|
615
|
+
try {
|
|
616
|
+
/* 源尺寸用位图尺寸(纹理 0-1 UV 对应位图;CSS 尺寸由 uFit 换算承担) */
|
|
617
|
+
var sw = s.videoWidth || s.width, sh = s.videoHeight || s.height;
|
|
618
|
+
if (!sw || !sh) return;
|
|
619
|
+
var W = this.size.w, H = this.size.h;
|
|
620
|
+
var sc = Math.max(W / sw, H / sh);
|
|
621
|
+
var dw = sw * sc, dh = sh * sc;
|
|
622
|
+
var dx = (W - dw) / 2, dy = (H - dh) / 2;
|
|
623
|
+
|
|
624
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
625
|
+
gl.bindTexture(gl.TEXTURE_2D, this.tex);
|
|
626
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, s);
|
|
627
|
+
|
|
628
|
+
/* 壁纸 1/4 分辨率 + CPU blur —— 玻璃霜化只看这张 */
|
|
629
|
+
var bc = this.blurC, bw = Math.max(2, Math.round(W / 4)), bh = Math.max(2, Math.round(H / 4));
|
|
630
|
+
if (bc.width !== bw) bc.width = bw;
|
|
631
|
+
if (bc.height !== bh) bc.height = bh;
|
|
632
|
+
var x = bc.getContext('2d');
|
|
633
|
+
x.filter = 'blur(' + Math.max(this.o.frost / 4, 0.5).toFixed(2) + 'px)';
|
|
634
|
+
x.clearRect(0, 0, bw, bh);
|
|
635
|
+
x.drawImage(s, 0, 0, sw, sh, dx / 4, dy / 4, dw / 4, dh / 4);
|
|
636
|
+
|
|
637
|
+
gl.activeTexture(gl.TEXTURE1);
|
|
638
|
+
gl.bindTexture(gl.TEXTURE_2D, this.blurTex);
|
|
639
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, bc);
|
|
640
|
+
|
|
641
|
+
this._fit = [W / dw, H / dh, -dx / dw, -dy / dh];
|
|
642
|
+
this.ready = true;
|
|
643
|
+
document.documentElement.classList.add('lg-ready');
|
|
644
|
+
} catch (e) {
|
|
645
|
+
console.warn('LiquidGlassNext: source upload failed', e);
|
|
668
646
|
}
|
|
647
|
+
this.dirty = false;
|
|
669
648
|
};
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
649
|
+
|
|
650
|
+
LiquidGlass.PRESETS = PRESETS;
|
|
651
|
+
window.LiquidGlassEngine = { LiquidGlass: LiquidGlass, default: LiquidGlass };
|
|
673
652
|
})();
|