liquid-gl 2.0.3 → 2.1.1

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.
Files changed (3) hide show
  1. package/README.md +4 -1
  2. package/liquidGL.js +364 -8
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  <a href="https://liquidgl.naughtyduk.com"><img src="https://raw.githubusercontent.com/naughtyduk/liquidGL/main/assets/liquidGL-npm-preview.gif" alt="liquidGL" width="100%" height="auto"/></a>
4
4
 
5
- ### v2.0.3
5
+ ### v2.1.1
6
6
 
7
7
  > [!NOTE]
8
8
  > `liquidGL` is free to use for both non-commercial and commercial purposes.
@@ -31,6 +31,8 @@
31
31
 
32
32
  **Features**
33
33
 
34
+ - **2.1.0 - Helper GUI** — A helper GUI is now available to adjust the liquidGL options in real-time during development, helping you to achieve the perfect aesthetic. To enable it, set `helper: true` in the options object when calling `liquidGL()`.
35
+
34
36
  - **Sticky positioning support** — `position: sticky` elements can now be glassified. Sticky lenses are measured every animation frame, so the glass pane tracks the element through its flowing and stuck phases, then releases with it at the end of its containing block — inside nested scroll containers as well as the main document.
35
37
 
36
38
  - **Built-in DOM snapshotter** — the `html2canvas` dependency has been replaced with an integrated rasteriser. Capture no longer depends on a third-party library.
@@ -227,6 +229,7 @@ Passing `gsap` lets `liquidGL` drive its render loop from the GSAP ticker and ke
227
229
  | `tiltFactor` | number | `5` | Depth of the tilt in degrees (0–25 recommended). |
228
230
  | `tiltEase` | number | `400` | Duration in ms for the tilt to settle, applied symmetrically on hover-in and hover-out. `0` applies the tilt instantly. |
229
231
  | `magnify` | number | `1` | Magnification factor of the lens (clamped 0.001–3.0). `1` is no magnification. |
232
+ | `helper` | boolean | `false` | Loads the helper GUI for live tweaking of the liquidGL options. |
230
233
  | `on.init` | function | `—` | Callback that runs once the first render completes. Receives the lens instance. |
231
234
 
232
235
  > The `target` parameter is required; all others are optional.
package/liquidGL.js CHANGED
@@ -4,7 +4,7 @@
4
4
  *
5
5
  * Author: NaughtyDuk© – https://liquidgl.naughtyduk.com
6
6
  * Licence: MIT
7
- * Version: v2.0.2
7
+ * Version: v2.1.0
8
8
  */
9
9
 
10
10
  const liquidGL = (() => {
@@ -2064,6 +2064,8 @@ const liquidGL = (() => {
2064
2064
  vec2 texel = 1.0 / u_textureResolution;
2065
2065
  vec4 refrCol;
2066
2066
 
2067
+ vec2 chroma = offset * u_aberration;
2068
+
2067
2069
  if (u_frost > 0.0) {
2068
2070
  float radius = u_frost * 4.0;
2069
2071
  vec4 sum = vec4(0.0);
@@ -2072,8 +2074,15 @@ const liquidGL = (() => {
2072
2074
  for (int i = 0; i < SAMPLES; i++) {
2073
2075
  float angle = random(v_uv + float(i)) * 6.283185;
2074
2076
  float dist = sqrt(random(v_uv - float(i))) * radius;
2075
- vec2 offset = vec2(cos(angle), sin(angle)) * texel * dist;
2076
- sum += texture2D(u_tex, sampleUV + offset);
2077
+ vec2 foff = vec2(cos(angle), sin(angle)) * texel * dist;
2078
+ if (u_aberration > 0.0) {
2079
+ sum.r += texture2D(u_tex, sampleUV + foff - chroma).r;
2080
+ sum.g += texture2D(u_tex, sampleUV + foff).g;
2081
+ sum.b += texture2D(u_tex, sampleUV + foff + chroma).b;
2082
+ sum.a += texture2D(u_tex, sampleUV + foff).a;
2083
+ } else {
2084
+ sum += texture2D(u_tex, sampleUV + foff);
2085
+ }
2077
2086
  }
2078
2087
  refrCol = sum / float(SAMPLES);
2079
2088
  } else {
@@ -2083,12 +2092,11 @@ const liquidGL = (() => {
2083
2092
  refrCol += texture2D(u_tex, sampleUV + vec2(0.0, texel.y));
2084
2093
  refrCol += texture2D(u_tex, sampleUV + vec2(0.0, -texel.y));
2085
2094
  refrCol /= 5.0;
2086
- }
2087
2095
 
2088
- if (u_aberration > 0.0) {
2089
- vec2 chroma = offset * u_aberration;
2090
- refrCol.r = texture2D(u_tex, sampleUV - chroma).r;
2091
- refrCol.b = texture2D(u_tex, sampleUV + chroma).b;
2096
+ if (u_aberration > 0.0) {
2097
+ refrCol.r = texture2D(u_tex, sampleUV - chroma).r;
2098
+ refrCol.b = texture2D(u_tex, sampleUV + chroma).b;
2099
+ }
2092
2100
  }
2093
2101
 
2094
2102
  if (refrCol.a < 0.1) {
@@ -4062,6 +4070,342 @@ const liquidGL = (() => {
4062
4070
  }
4063
4071
  }
4064
4072
 
4073
+ /* --------------------------------------------------
4074
+ * Helper GUI System
4075
+ * ------------------------------------------------*/
4076
+ let helperGUIs = [];
4077
+ let lilGuiLoaded = false;
4078
+ let lilGuiLoadPromise = null;
4079
+
4080
+ function loadLilGui() {
4081
+ if (lilGuiLoaded) {
4082
+ return Promise.resolve();
4083
+ }
4084
+ if (lilGuiLoadPromise) {
4085
+ return lilGuiLoadPromise;
4086
+ }
4087
+
4088
+ lilGuiLoadPromise = new Promise((resolve, reject) => {
4089
+ if (typeof lil !== "undefined") {
4090
+ lilGuiLoaded = true;
4091
+ injectHelperStyles();
4092
+ resolve();
4093
+ return;
4094
+ }
4095
+
4096
+ const script = document.createElement("script");
4097
+ script.src =
4098
+ "https://cdn.jsdelivr.net/npm/lil-gui@0.19.1/dist/lil-gui.umd.min.js";
4099
+ script.integrity =
4100
+ "sha384-2eNPNc7Cms+nVcpmQPotBpthLWCwjAGbkp0Y+3MUQqwPbmTpMFmbh2a230Gkns0x";
4101
+ script.crossOrigin = "anonymous";
4102
+ script.onload = () => {
4103
+ lilGuiLoaded = true;
4104
+ injectHelperStyles();
4105
+ resolve();
4106
+ };
4107
+ script.onerror = () => {
4108
+ reject(new Error("Failed to load lil-gui"));
4109
+ };
4110
+ document.head.appendChild(script);
4111
+ });
4112
+
4113
+ return lilGuiLoadPromise;
4114
+ }
4115
+
4116
+ function injectHelperStyles() {
4117
+ if (document.getElementById("liquidgl-helper-styles")) return;
4118
+
4119
+ const style = document.createElement("style");
4120
+ style.id = "liquidgl-helper-styles";
4121
+ style.textContent = `
4122
+ @media screen and (max-width: 768px) {
4123
+ .lil-gui.root.liquidgl-helper {
4124
+ width: 61vw;
4125
+ }
4126
+ }
4127
+
4128
+ .lil-gui.root.liquidgl-helper,
4129
+ .lil-gui.liquidgl-helper .lil-gui {
4130
+ --background-color: rgb(9 9 11 / 85%);
4131
+ --widget-color: rgb(39 39 42 / 50%);
4132
+ --hover-color: rgb(39 39 42 / 70%);
4133
+ --focus-color: rgb(39 39 42 / 90%);
4134
+ --number-color: #fafafa;
4135
+ --string-color: #fafafa;
4136
+ --font-size: 13px;
4137
+ --input-font-size: 13px;
4138
+ --font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
4139
+ --font-family-mono: monospace;
4140
+ --padding: 10px;
4141
+ --spacing: 10px;
4142
+ --widget-height: 28px;
4143
+ --title-height: 28px;
4144
+ --name-width: 45%;
4145
+ --slider-knob-width: 4px;
4146
+ --slider-input-width: 27%;
4147
+ --color-input-width: 27%;
4148
+ --slider-input-min-width: 45px;
4149
+ --color-input-min-width: 45px;
4150
+ --folder-indent: 8px;
4151
+ --widget-padding: 0 10px;
4152
+ --widget-border-radius: 4px;
4153
+ --checkbox-size: 16px;
4154
+ --scrollbar-width: 6px;
4155
+ }
4156
+
4157
+ .lil-gui.root.liquidgl-helper {
4158
+ border-radius: 12px !important;
4159
+ border: 0.5px solid #1e1e20 !important;
4160
+ backdrop-filter: blur(16px);
4161
+ box-shadow: 0 4px 16px rgb(0 0 0 / 20%) !important;
4162
+ position: fixed !important;
4163
+ top: 1rem !important;
4164
+ right: 1rem !important;
4165
+ left: auto !important;
4166
+ z-index: 999999999 !important;
4167
+ }
4168
+
4169
+ .lil-gui.liquidgl-helper .title {
4170
+ background: transparent;
4171
+ border-bottom: 0.5px solid #1e1e20;
4172
+ border-radius: 12px 12px 0 0 !important;
4173
+ }
4174
+
4175
+ .lil-gui.liquidgl-helper .title button {
4176
+ padding: 12px 16px !important;
4177
+ }
4178
+
4179
+ .lil-gui.liquidgl-helper.closed .title {
4180
+ border-radius: 12px !important;
4181
+ border-bottom: none;
4182
+ }
4183
+
4184
+ .lil-gui.liquidgl-helper .children {
4185
+ border-top: 0.5px solid #1e1e20;
4186
+ }
4187
+
4188
+ @media (max-width: 768px) {
4189
+ .lil-gui.root.liquidgl-helper {
4190
+ top: 1rem !important;
4191
+ right: 1rem !important;
4192
+ }
4193
+ }
4194
+ `;
4195
+ document.head.appendChild(style);
4196
+ }
4197
+
4198
+ function createHelperGUI(lenses, options, instanceIndex) {
4199
+ if (typeof lil === "undefined") return;
4200
+
4201
+ const lensList = (Array.isArray(lenses) ? lenses : [lenses]).filter(
4202
+ Boolean,
4203
+ );
4204
+ if (!lensList.length) return;
4205
+
4206
+ const gui = new lil.GUI({
4207
+ title: `liquidGL Helper ${instanceIndex + 1}`,
4208
+ closeFolders: true,
4209
+ });
4210
+ gui.domElement.classList.add("liquidgl-helper");
4211
+ gui.$title.style.cursor = "default";
4212
+
4213
+ const topOffset = 1 + instanceIndex * 3;
4214
+ gui.domElement.style.top = `${topOffset}rem`;
4215
+
4216
+ const state = lensList[0].options;
4217
+
4218
+ const applyToLenses = (key, value) => {
4219
+ lensList.forEach((ln) => {
4220
+ if (!ln) return;
4221
+ ln.options[key] = value;
4222
+ if (key === "shadow") ln.setShadow(value);
4223
+ if (key === "tilt") ln.setTilt(value);
4224
+ });
4225
+ };
4226
+
4227
+ const refractionFolder = gui.addFolder("Refraction");
4228
+ refractionFolder
4229
+ .add(state, "refraction", 0, 0.1, 0.001)
4230
+ .name("Refraction")
4231
+ .onChange((v) => applyToLenses("refraction", v));
4232
+ refractionFolder
4233
+ .add(state, "aberration", 0, 1, 0.01)
4234
+ .name("Aberration")
4235
+ .onChange((v) => applyToLenses("aberration", v));
4236
+ refractionFolder
4237
+ .add(state, "bevelDepth", 0, 0.2, 0.001)
4238
+ .name("Bevel Depth")
4239
+ .onChange((v) => applyToLenses("bevelDepth", v));
4240
+ refractionFolder
4241
+ .add(state, "bevelWidth", 0, 0.5, 0.001)
4242
+ .name("Bevel Width")
4243
+ .onChange((v) => applyToLenses("bevelWidth", v));
4244
+ refractionFolder
4245
+ .add(state, "magnify", 1, 5, 0.1)
4246
+ .name("Magnify")
4247
+ .onChange((v) => applyToLenses("magnify", v));
4248
+
4249
+ const surfaceFolder = gui.addFolder("Surface");
4250
+ surfaceFolder
4251
+ .add(state, "frost", 0, 10, 0.1)
4252
+ .name("Frost")
4253
+ .onChange((v) => applyToLenses("frost", v));
4254
+ surfaceFolder
4255
+ .add(state, "specular")
4256
+ .name("Specular")
4257
+ .onChange((v) => applyToLenses("specular", v));
4258
+ surfaceFolder
4259
+ .add(state, "shadow")
4260
+ .name("Shadow")
4261
+ .onChange((v) => applyToLenses("shadow", v));
4262
+
4263
+ const tiltFolder = gui.addFolder("Tilt");
4264
+ tiltFolder
4265
+ .add(state, "tilt")
4266
+ .name("Tilt")
4267
+ .onChange((v) => applyToLenses("tilt", v));
4268
+ tiltFolder
4269
+ .add(state, "tiltFactor", 0, 25, 0.1)
4270
+ .name("Tilt Factor")
4271
+ .onChange((v) => applyToLenses("tiltFactor", v));
4272
+ tiltFolder
4273
+ .add(state, "tiltEase", 0, 1000, 10)
4274
+ .name("Tilt Ease")
4275
+ .onChange((v) => applyToLenses("tiltEase", v));
4276
+
4277
+ const initFolder = gui.addFolder("Initialisation");
4278
+ const reinitState = {
4279
+ reveal: options.reveal,
4280
+ resolution: options.resolution,
4281
+ };
4282
+
4283
+ initFolder
4284
+ .add(reinitState, "reveal", ["none", "fade"])
4285
+ .name("Reveal")
4286
+ .onFinishChange((value) => {
4287
+ if (
4288
+ confirm(
4289
+ "Changing reveal applies on the next initialisation. Continue?",
4290
+ )
4291
+ ) {
4292
+ applyToLenses("reveal", value);
4293
+ } else {
4294
+ reinitState.reveal = options.reveal;
4295
+ gui.controllersRecursive().forEach((c) => c.updateDisplay());
4296
+ }
4297
+ });
4298
+
4299
+ initFolder
4300
+ .add(reinitState, "resolution", 0.5, 3, 0.25)
4301
+ .name("Resolution")
4302
+ .onFinishChange((value) => {
4303
+ if (
4304
+ confirm(
4305
+ "Changing resolution re-captures the page snapshot. Continue?",
4306
+ )
4307
+ ) {
4308
+ applyToLenses("resolution", value);
4309
+ const renderer = window.__liquidGLRenderer__;
4310
+ if (renderer) {
4311
+ renderer._snapshotResolution = Math.max(0.1, Math.min(3.0, value));
4312
+ renderer.captureSnapshot();
4313
+ }
4314
+ } else {
4315
+ reinitState.resolution = options.resolution;
4316
+ gui.controllersRecursive().forEach((c) => c.updateDisplay());
4317
+ }
4318
+ });
4319
+
4320
+ const copyButton = {
4321
+ copySettings: () => {
4322
+ const code = generateInitCode(options);
4323
+ const controller = gui.controllers.find(
4324
+ (c) => c.property === "copySettings",
4325
+ );
4326
+
4327
+ if (!controller) return;
4328
+
4329
+ const originalName = controller._name;
4330
+ controller.disable();
4331
+ controller.name("✓ Copied");
4332
+
4333
+ const copySuccess = () => {
4334
+ setTimeout(() => {
4335
+ controller.name(originalName);
4336
+ controller.enable();
4337
+ }, 1500);
4338
+ };
4339
+
4340
+ if (navigator.clipboard && navigator.clipboard.writeText) {
4341
+ navigator.clipboard
4342
+ .writeText(code)
4343
+ .then(() => {
4344
+ copySuccess();
4345
+ })
4346
+ .catch((err) => {
4347
+ console.error("Clipboard error:", err);
4348
+ fallbackCopy(code);
4349
+ });
4350
+ } else {
4351
+ fallbackCopy(code);
4352
+ }
4353
+
4354
+ function fallbackCopy(text) {
4355
+ const textarea = document.createElement("textarea");
4356
+ textarea.value = text;
4357
+ textarea.style.position = "fixed";
4358
+ textarea.style.opacity = "0";
4359
+ document.body.appendChild(textarea);
4360
+ textarea.select();
4361
+ try {
4362
+ document.execCommand("copy");
4363
+ copySuccess();
4364
+ } catch (err) {
4365
+ console.error("Copy failed:", err);
4366
+ controller.name(originalName);
4367
+ controller.enable();
4368
+ prompt("Copy this code manually:", text);
4369
+ }
4370
+ document.body.removeChild(textarea);
4371
+ }
4372
+ },
4373
+ };
4374
+ gui.add(copyButton, "copySettings").name("Copy settings");
4375
+
4376
+ refractionFolder.close();
4377
+ surfaceFolder.close();
4378
+ tiltFolder.close();
4379
+ initFolder.close();
4380
+
4381
+ helperGUIs.push({ gui, lenses: lensList });
4382
+ return gui;
4383
+ }
4384
+
4385
+ function generateInitCode(options) {
4386
+ const lines = ["liquidGL({"];
4387
+
4388
+ lines.push(` target: "${options.target}",`);
4389
+ lines.push(` snapshot: "${options.snapshot}",`);
4390
+ lines.push(` resolution: ${options.resolution},`);
4391
+ lines.push(` refraction: ${options.refraction},`);
4392
+ lines.push(` aberration: ${options.aberration},`);
4393
+ lines.push(` bevelDepth: ${options.bevelDepth},`);
4394
+ lines.push(` bevelWidth: ${options.bevelWidth},`);
4395
+ lines.push(` frost: ${options.frost},`);
4396
+ lines.push(` shadow: ${options.shadow},`);
4397
+ lines.push(` specular: ${options.specular},`);
4398
+ lines.push(` reveal: "${options.reveal}",`);
4399
+ lines.push(` tilt: ${options.tilt},`);
4400
+ lines.push(` tiltFactor: ${options.tiltFactor},`);
4401
+ lines.push(` tiltEase: ${options.tiltEase},`);
4402
+ lines.push(` magnify: ${options.magnify},`);
4403
+ lines.push(` helper: false,`);
4404
+ lines.push(`});`);
4405
+
4406
+ return lines.join("\n");
4407
+ }
4408
+
4065
4409
  /* --------------------------------------------------
4066
4410
  * Public API
4067
4411
  * ------------------------------------------------*/
@@ -4082,6 +4426,7 @@ const liquidGL = (() => {
4082
4426
  tiltFactor: 5,
4083
4427
  tiltEase: 400,
4084
4428
  magnify: 1,
4429
+ helper: false,
4085
4430
  on: {},
4086
4431
  };
4087
4432
  const options = { ...defaults, ...userOptions };
@@ -4140,6 +4485,17 @@ const liquidGL = (() => {
4140
4485
  renderer._rafId = requestAnimationFrame(loop);
4141
4486
  }
4142
4487
 
4488
+ if (options.helper) {
4489
+ loadLilGui()
4490
+ .then(() => {
4491
+ const instanceIndex = helperGUIs.length;
4492
+ createHelperGUI(instances, options, instanceIndex);
4493
+ })
4494
+ .catch((err) => {
4495
+ console.error("liquidGL: Failed to load helper GUI:", err);
4496
+ });
4497
+ }
4498
+
4143
4499
  return instances.length === 1 ? instances[0] : instances;
4144
4500
  };
4145
4501
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "liquid-gl",
3
- "version": "2.0.3",
3
+ "version": "2.1.1",
4
4
  "description": "Liquid Glass - Powered by WebGL.",
5
5
  "type": "module",
6
6
  "main": "./liquidGL.js",