liquid-gl 2.0.3 → 2.1.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.
Files changed (3) hide show
  1. package/README.md +4 -1
  2. package/liquidGL.js +349 -1
  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.0
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 = (() => {
@@ -4062,6 +4062,342 @@ const liquidGL = (() => {
4062
4062
  }
4063
4063
  }
4064
4064
 
4065
+ /* --------------------------------------------------
4066
+ * Helper GUI System
4067
+ * ------------------------------------------------*/
4068
+ let helperGUIs = [];
4069
+ let lilGuiLoaded = false;
4070
+ let lilGuiLoadPromise = null;
4071
+
4072
+ function loadLilGui() {
4073
+ if (lilGuiLoaded) {
4074
+ return Promise.resolve();
4075
+ }
4076
+ if (lilGuiLoadPromise) {
4077
+ return lilGuiLoadPromise;
4078
+ }
4079
+
4080
+ lilGuiLoadPromise = new Promise((resolve, reject) => {
4081
+ if (typeof lil !== "undefined") {
4082
+ lilGuiLoaded = true;
4083
+ injectHelperStyles();
4084
+ resolve();
4085
+ return;
4086
+ }
4087
+
4088
+ const script = document.createElement("script");
4089
+ script.src =
4090
+ "https://cdn.jsdelivr.net/npm/lil-gui@0.19.1/dist/lil-gui.umd.min.js";
4091
+ script.integrity =
4092
+ "sha384-2eNPNc7Cms+nVcpmQPotBpthLWCwjAGbkp0Y+3MUQqwPbmTpMFmbh2a230Gkns0x";
4093
+ script.crossOrigin = "anonymous";
4094
+ script.onload = () => {
4095
+ lilGuiLoaded = true;
4096
+ injectHelperStyles();
4097
+ resolve();
4098
+ };
4099
+ script.onerror = () => {
4100
+ reject(new Error("Failed to load lil-gui"));
4101
+ };
4102
+ document.head.appendChild(script);
4103
+ });
4104
+
4105
+ return lilGuiLoadPromise;
4106
+ }
4107
+
4108
+ function injectHelperStyles() {
4109
+ if (document.getElementById("liquidgl-helper-styles")) return;
4110
+
4111
+ const style = document.createElement("style");
4112
+ style.id = "liquidgl-helper-styles";
4113
+ style.textContent = `
4114
+ @media screen and (max-width: 768px) {
4115
+ .lil-gui.root.liquidgl-helper {
4116
+ width: 61vw;
4117
+ }
4118
+ }
4119
+
4120
+ .lil-gui.root.liquidgl-helper,
4121
+ .lil-gui.liquidgl-helper .lil-gui {
4122
+ --background-color: rgb(9 9 11 / 85%);
4123
+ --widget-color: rgb(39 39 42 / 50%);
4124
+ --hover-color: rgb(39 39 42 / 70%);
4125
+ --focus-color: rgb(39 39 42 / 90%);
4126
+ --number-color: #fafafa;
4127
+ --string-color: #fafafa;
4128
+ --font-size: 13px;
4129
+ --input-font-size: 13px;
4130
+ --font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
4131
+ --font-family-mono: monospace;
4132
+ --padding: 10px;
4133
+ --spacing: 10px;
4134
+ --widget-height: 28px;
4135
+ --title-height: 28px;
4136
+ --name-width: 45%;
4137
+ --slider-knob-width: 4px;
4138
+ --slider-input-width: 27%;
4139
+ --color-input-width: 27%;
4140
+ --slider-input-min-width: 45px;
4141
+ --color-input-min-width: 45px;
4142
+ --folder-indent: 8px;
4143
+ --widget-padding: 0 10px;
4144
+ --widget-border-radius: 4px;
4145
+ --checkbox-size: 16px;
4146
+ --scrollbar-width: 6px;
4147
+ }
4148
+
4149
+ .lil-gui.root.liquidgl-helper {
4150
+ border-radius: 12px !important;
4151
+ border: 0.5px solid #1e1e20 !important;
4152
+ backdrop-filter: blur(16px);
4153
+ box-shadow: 0 4px 16px rgb(0 0 0 / 20%) !important;
4154
+ position: fixed !important;
4155
+ top: 1rem !important;
4156
+ right: 1rem !important;
4157
+ left: auto !important;
4158
+ z-index: 999999999 !important;
4159
+ }
4160
+
4161
+ .lil-gui.liquidgl-helper .title {
4162
+ background: transparent;
4163
+ border-bottom: 0.5px solid #1e1e20;
4164
+ border-radius: 12px 12px 0 0 !important;
4165
+ }
4166
+
4167
+ .lil-gui.liquidgl-helper .title button {
4168
+ padding: 12px 16px !important;
4169
+ }
4170
+
4171
+ .lil-gui.liquidgl-helper.closed .title {
4172
+ border-radius: 12px !important;
4173
+ border-bottom: none;
4174
+ }
4175
+
4176
+ .lil-gui.liquidgl-helper .children {
4177
+ border-top: 0.5px solid #1e1e20;
4178
+ }
4179
+
4180
+ @media (max-width: 768px) {
4181
+ .lil-gui.root.liquidgl-helper {
4182
+ top: 1rem !important;
4183
+ right: 1rem !important;
4184
+ }
4185
+ }
4186
+ `;
4187
+ document.head.appendChild(style);
4188
+ }
4189
+
4190
+ function createHelperGUI(lenses, options, instanceIndex) {
4191
+ if (typeof lil === "undefined") return;
4192
+
4193
+ const lensList = (Array.isArray(lenses) ? lenses : [lenses]).filter(
4194
+ Boolean,
4195
+ );
4196
+ if (!lensList.length) return;
4197
+
4198
+ const gui = new lil.GUI({
4199
+ title: `liquidGL Helper ${instanceIndex + 1}`,
4200
+ closeFolders: true,
4201
+ });
4202
+ gui.domElement.classList.add("liquidgl-helper");
4203
+ gui.$title.style.cursor = "default";
4204
+
4205
+ const topOffset = 1 + instanceIndex * 3;
4206
+ gui.domElement.style.top = `${topOffset}rem`;
4207
+
4208
+ const state = lensList[0].options;
4209
+
4210
+ const applyToLenses = (key, value) => {
4211
+ lensList.forEach((ln) => {
4212
+ if (!ln) return;
4213
+ ln.options[key] = value;
4214
+ if (key === "shadow") ln.setShadow(value);
4215
+ if (key === "tilt") ln.setTilt(value);
4216
+ });
4217
+ };
4218
+
4219
+ const refractionFolder = gui.addFolder("Refraction");
4220
+ refractionFolder
4221
+ .add(state, "refraction", 0, 0.1, 0.001)
4222
+ .name("Refraction")
4223
+ .onChange((v) => applyToLenses("refraction", v));
4224
+ refractionFolder
4225
+ .add(state, "aberration", 0, 1, 0.01)
4226
+ .name("Aberration")
4227
+ .onChange((v) => applyToLenses("aberration", v));
4228
+ refractionFolder
4229
+ .add(state, "bevelDepth", 0, 0.2, 0.001)
4230
+ .name("Bevel Depth")
4231
+ .onChange((v) => applyToLenses("bevelDepth", v));
4232
+ refractionFolder
4233
+ .add(state, "bevelWidth", 0, 0.5, 0.001)
4234
+ .name("Bevel Width")
4235
+ .onChange((v) => applyToLenses("bevelWidth", v));
4236
+ refractionFolder
4237
+ .add(state, "magnify", 1, 5, 0.1)
4238
+ .name("Magnify")
4239
+ .onChange((v) => applyToLenses("magnify", v));
4240
+
4241
+ const surfaceFolder = gui.addFolder("Surface");
4242
+ surfaceFolder
4243
+ .add(state, "frost", 0, 10, 0.1)
4244
+ .name("Frost")
4245
+ .onChange((v) => applyToLenses("frost", v));
4246
+ surfaceFolder
4247
+ .add(state, "specular")
4248
+ .name("Specular")
4249
+ .onChange((v) => applyToLenses("specular", v));
4250
+ surfaceFolder
4251
+ .add(state, "shadow")
4252
+ .name("Shadow")
4253
+ .onChange((v) => applyToLenses("shadow", v));
4254
+
4255
+ const tiltFolder = gui.addFolder("Tilt");
4256
+ tiltFolder
4257
+ .add(state, "tilt")
4258
+ .name("Tilt")
4259
+ .onChange((v) => applyToLenses("tilt", v));
4260
+ tiltFolder
4261
+ .add(state, "tiltFactor", 0, 25, 0.1)
4262
+ .name("Tilt Factor")
4263
+ .onChange((v) => applyToLenses("tiltFactor", v));
4264
+ tiltFolder
4265
+ .add(state, "tiltEase", 0, 1000, 10)
4266
+ .name("Tilt Ease")
4267
+ .onChange((v) => applyToLenses("tiltEase", v));
4268
+
4269
+ const initFolder = gui.addFolder("Initialisation");
4270
+ const reinitState = {
4271
+ reveal: options.reveal,
4272
+ resolution: options.resolution,
4273
+ };
4274
+
4275
+ initFolder
4276
+ .add(reinitState, "reveal", ["none", "fade"])
4277
+ .name("Reveal")
4278
+ .onFinishChange((value) => {
4279
+ if (
4280
+ confirm(
4281
+ "Changing reveal applies on the next initialisation. Continue?",
4282
+ )
4283
+ ) {
4284
+ applyToLenses("reveal", value);
4285
+ } else {
4286
+ reinitState.reveal = options.reveal;
4287
+ gui.controllersRecursive().forEach((c) => c.updateDisplay());
4288
+ }
4289
+ });
4290
+
4291
+ initFolder
4292
+ .add(reinitState, "resolution", 0.5, 3, 0.25)
4293
+ .name("Resolution")
4294
+ .onFinishChange((value) => {
4295
+ if (
4296
+ confirm(
4297
+ "Changing resolution re-captures the page snapshot. Continue?",
4298
+ )
4299
+ ) {
4300
+ applyToLenses("resolution", value);
4301
+ const renderer = window.__liquidGLRenderer__;
4302
+ if (renderer) {
4303
+ renderer._snapshotResolution = Math.max(0.1, Math.min(3.0, value));
4304
+ renderer.captureSnapshot();
4305
+ }
4306
+ } else {
4307
+ reinitState.resolution = options.resolution;
4308
+ gui.controllersRecursive().forEach((c) => c.updateDisplay());
4309
+ }
4310
+ });
4311
+
4312
+ const copyButton = {
4313
+ copySettings: () => {
4314
+ const code = generateInitCode(options);
4315
+ const controller = gui.controllers.find(
4316
+ (c) => c.property === "copySettings",
4317
+ );
4318
+
4319
+ if (!controller) return;
4320
+
4321
+ const originalName = controller._name;
4322
+ controller.disable();
4323
+ controller.name("✓ Copied");
4324
+
4325
+ const copySuccess = () => {
4326
+ setTimeout(() => {
4327
+ controller.name(originalName);
4328
+ controller.enable();
4329
+ }, 1500);
4330
+ };
4331
+
4332
+ if (navigator.clipboard && navigator.clipboard.writeText) {
4333
+ navigator.clipboard
4334
+ .writeText(code)
4335
+ .then(() => {
4336
+ copySuccess();
4337
+ })
4338
+ .catch((err) => {
4339
+ console.error("Clipboard error:", err);
4340
+ fallbackCopy(code);
4341
+ });
4342
+ } else {
4343
+ fallbackCopy(code);
4344
+ }
4345
+
4346
+ function fallbackCopy(text) {
4347
+ const textarea = document.createElement("textarea");
4348
+ textarea.value = text;
4349
+ textarea.style.position = "fixed";
4350
+ textarea.style.opacity = "0";
4351
+ document.body.appendChild(textarea);
4352
+ textarea.select();
4353
+ try {
4354
+ document.execCommand("copy");
4355
+ copySuccess();
4356
+ } catch (err) {
4357
+ console.error("Copy failed:", err);
4358
+ controller.name(originalName);
4359
+ controller.enable();
4360
+ prompt("Copy this code manually:", text);
4361
+ }
4362
+ document.body.removeChild(textarea);
4363
+ }
4364
+ },
4365
+ };
4366
+ gui.add(copyButton, "copySettings").name("Copy settings");
4367
+
4368
+ refractionFolder.close();
4369
+ surfaceFolder.close();
4370
+ tiltFolder.close();
4371
+ initFolder.close();
4372
+
4373
+ helperGUIs.push({ gui, lenses: lensList });
4374
+ return gui;
4375
+ }
4376
+
4377
+ function generateInitCode(options) {
4378
+ const lines = ["liquidGL({"];
4379
+
4380
+ lines.push(` target: "${options.target}",`);
4381
+ lines.push(` snapshot: "${options.snapshot}",`);
4382
+ lines.push(` resolution: ${options.resolution},`);
4383
+ lines.push(` refraction: ${options.refraction},`);
4384
+ lines.push(` aberration: ${options.aberration},`);
4385
+ lines.push(` bevelDepth: ${options.bevelDepth},`);
4386
+ lines.push(` bevelWidth: ${options.bevelWidth},`);
4387
+ lines.push(` frost: ${options.frost},`);
4388
+ lines.push(` shadow: ${options.shadow},`);
4389
+ lines.push(` specular: ${options.specular},`);
4390
+ lines.push(` reveal: "${options.reveal}",`);
4391
+ lines.push(` tilt: ${options.tilt},`);
4392
+ lines.push(` tiltFactor: ${options.tiltFactor},`);
4393
+ lines.push(` tiltEase: ${options.tiltEase},`);
4394
+ lines.push(` magnify: ${options.magnify},`);
4395
+ lines.push(` helper: false,`);
4396
+ lines.push(`});`);
4397
+
4398
+ return lines.join("\n");
4399
+ }
4400
+
4065
4401
  /* --------------------------------------------------
4066
4402
  * Public API
4067
4403
  * ------------------------------------------------*/
@@ -4082,6 +4418,7 @@ const liquidGL = (() => {
4082
4418
  tiltFactor: 5,
4083
4419
  tiltEase: 400,
4084
4420
  magnify: 1,
4421
+ helper: false,
4085
4422
  on: {},
4086
4423
  };
4087
4424
  const options = { ...defaults, ...userOptions };
@@ -4140,6 +4477,17 @@ const liquidGL = (() => {
4140
4477
  renderer._rafId = requestAnimationFrame(loop);
4141
4478
  }
4142
4479
 
4480
+ if (options.helper) {
4481
+ loadLilGui()
4482
+ .then(() => {
4483
+ const instanceIndex = helperGUIs.length;
4484
+ createHelperGUI(instances, options, instanceIndex);
4485
+ })
4486
+ .catch((err) => {
4487
+ console.error("liquidGL: Failed to load helper GUI:", err);
4488
+ });
4489
+ }
4490
+
4143
4491
  return instances.length === 1 ? instances[0] : instances;
4144
4492
  };
4145
4493
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "liquid-gl",
3
- "version": "2.0.3",
3
+ "version": "2.1.0",
4
4
  "description": "Liquid Glass - Powered by WebGL.",
5
5
  "type": "module",
6
6
  "main": "./liquidGL.js",