@marcosdemik/liquidglass 2.0.2 → 2.0.4
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/dist/index.cjs +67 -22
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +11 -3
- package/dist/index.d.ts +11 -3
- package/dist/index.js +64 -21
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -62,7 +62,9 @@ var index_exports = {};
|
|
|
62
62
|
__export(index_exports, {
|
|
63
63
|
LiquidGlassButton: () => LiquidGlassButton,
|
|
64
64
|
cn: () => cn,
|
|
65
|
-
generateGlassMaps: () => generateGlassMaps
|
|
65
|
+
generateGlassMaps: () => generateGlassMaps,
|
|
66
|
+
getCachedGlassMaps: () => getCachedGlassMaps,
|
|
67
|
+
revokeGlassMaps: () => revokeGlassMaps
|
|
66
68
|
});
|
|
67
69
|
module.exports = __toCommonJS(index_exports);
|
|
68
70
|
|
|
@@ -150,7 +152,11 @@ function getGL() {
|
|
|
150
152
|
_cachedProgram = { gl, program, canvas };
|
|
151
153
|
return _cachedProgram;
|
|
152
154
|
}
|
|
153
|
-
|
|
155
|
+
var _mapCache = /* @__PURE__ */ new Map();
|
|
156
|
+
function cacheKey(w, h, r, bs, sw) {
|
|
157
|
+
return `${w}|${h}|${r}|${bs}|${sw}`;
|
|
158
|
+
}
|
|
159
|
+
function renderToBlob(width, height, radius, borderSoftness, specularWidth, mode) {
|
|
154
160
|
const { gl, program, canvas } = getGL();
|
|
155
161
|
canvas.width = width;
|
|
156
162
|
canvas.height = height;
|
|
@@ -164,9 +170,41 @@ function render(width, height, radius, borderSoftness, specularWidth, mode) {
|
|
|
164
170
|
gl.uniform1f(gl.getUniformLocation(program, "uSpecularWidth"), specularWidth);
|
|
165
171
|
gl.uniform1i(gl.getUniformLocation(program, "uMode"), mode);
|
|
166
172
|
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
|
167
|
-
return
|
|
173
|
+
return new Promise((resolve) => {
|
|
174
|
+
canvas.toBlob((blob) => {
|
|
175
|
+
resolve(URL.createObjectURL(blob));
|
|
176
|
+
}, "image/png");
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
async function generateGlassMaps(opts) {
|
|
180
|
+
const {
|
|
181
|
+
width,
|
|
182
|
+
height,
|
|
183
|
+
radius = 60,
|
|
184
|
+
edgeSize = 30,
|
|
185
|
+
intensity = 0.7,
|
|
186
|
+
specularWidth = 0.02,
|
|
187
|
+
quality = 2
|
|
188
|
+
} = opts;
|
|
189
|
+
const scale = Math.max(1, Math.round(quality));
|
|
190
|
+
const rw = width * scale;
|
|
191
|
+
const rh = height * scale;
|
|
192
|
+
const r = Math.min(radius * scale, rw / 2, rh / 2);
|
|
193
|
+
const borderSoftness = edgeSize * intensity * scale;
|
|
194
|
+
const specPx = specularWidth * Math.min(rw, rh);
|
|
195
|
+
const key = cacheKey(rw, rh, r, borderSoftness, specPx);
|
|
196
|
+
const cached = _mapCache.get(key);
|
|
197
|
+
if (cached) return cached;
|
|
198
|
+
const [displacement, specular] = await Promise.all([
|
|
199
|
+
renderToBlob(rw, rh, r, borderSoftness, specPx, 0),
|
|
200
|
+
renderToBlob(rw, rh, r, borderSoftness, specPx, 1)
|
|
201
|
+
]);
|
|
202
|
+
const result = { displacement, specular };
|
|
203
|
+
_mapCache.set(key, result);
|
|
204
|
+
return result;
|
|
168
205
|
}
|
|
169
|
-
function
|
|
206
|
+
function getCachedGlassMaps(opts) {
|
|
207
|
+
var _a;
|
|
170
208
|
const {
|
|
171
209
|
width,
|
|
172
210
|
height,
|
|
@@ -182,10 +220,11 @@ function generateGlassMaps(opts) {
|
|
|
182
220
|
const r = Math.min(radius * scale, rw / 2, rh / 2);
|
|
183
221
|
const borderSoftness = edgeSize * intensity * scale;
|
|
184
222
|
const specPx = specularWidth * Math.min(rw, rh);
|
|
185
|
-
return
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
223
|
+
return (_a = _mapCache.get(cacheKey(rw, rh, r, borderSoftness, specPx))) != null ? _a : null;
|
|
224
|
+
}
|
|
225
|
+
function revokeGlassMaps(maps) {
|
|
226
|
+
URL.revokeObjectURL(maps.displacement);
|
|
227
|
+
URL.revokeObjectURL(maps.specular);
|
|
189
228
|
}
|
|
190
229
|
|
|
191
230
|
// src/liquid-glass-button.tsx
|
|
@@ -240,25 +279,29 @@ var LiquidGlassButton = (0, import_react.forwardRef)(
|
|
|
240
279
|
const displacerRef = (0, import_react.useRef)(null);
|
|
241
280
|
const blurRef = (0, import_react.useRef)(null);
|
|
242
281
|
const filterId = "lg" + (0, import_react.useId)().replace(/:/g, "");
|
|
243
|
-
const
|
|
282
|
+
const mapOpts = { width, height, radius, edgeSize, intensity, specularWidth, quality };
|
|
283
|
+
const [maps, setMaps] = (0, import_react.useState)(
|
|
284
|
+
() => getCachedGlassMaps(mapOpts)
|
|
285
|
+
);
|
|
244
286
|
(0, import_react.useEffect)(() => {
|
|
287
|
+
const cached = getCachedGlassMaps(mapOpts);
|
|
288
|
+
if (cached) {
|
|
289
|
+
setMaps(cached);
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
245
292
|
let cancelled = false;
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
loaded++;
|
|
250
|
-
if (loaded === 2 && !cancelled) setMaps(m);
|
|
251
|
-
};
|
|
252
|
-
const img1 = new Image();
|
|
253
|
-
img1.onload = onLoad;
|
|
254
|
-
img1.src = m.displacement;
|
|
255
|
-
const img2 = new Image();
|
|
256
|
-
img2.onload = onLoad;
|
|
257
|
-
img2.src = m.specular;
|
|
293
|
+
generateGlassMaps(mapOpts).then((m) => {
|
|
294
|
+
if (!cancelled) setMaps(m);
|
|
295
|
+
});
|
|
258
296
|
return () => {
|
|
259
297
|
cancelled = true;
|
|
260
298
|
};
|
|
261
299
|
}, [width, height, radius, edgeSize, intensity, specularWidth, quality]);
|
|
300
|
+
(0, import_react.useEffect)(() => {
|
|
301
|
+
return () => {
|
|
302
|
+
if (maps) revokeGlassMaps(maps);
|
|
303
|
+
};
|
|
304
|
+
}, [maps]);
|
|
262
305
|
(0, import_react.useEffect)(() => {
|
|
263
306
|
const button = buttonRef.current;
|
|
264
307
|
const displacer = displacerRef.current;
|
|
@@ -447,6 +490,8 @@ var LiquidGlassButton = (0, import_react.forwardRef)(
|
|
|
447
490
|
0 && (module.exports = {
|
|
448
491
|
LiquidGlassButton,
|
|
449
492
|
cn,
|
|
450
|
-
generateGlassMaps
|
|
493
|
+
generateGlassMaps,
|
|
494
|
+
getCachedGlassMaps,
|
|
495
|
+
revokeGlassMaps
|
|
451
496
|
});
|
|
452
497
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/liquid-glass-button.tsx","../src/utils.ts","../src/generate-displacement-map.ts"],"sourcesContent":["export { LiquidGlassButton } from \"./liquid-glass-button\";\nexport type { LiquidGlassButtonProps } from \"./liquid-glass-button\";\nexport { generateGlassMaps } from \"./generate-displacement-map\";\nexport type { MapOptions } from \"./generate-displacement-map\";\nexport { cn } from \"./utils\";\n","import React, { useRef, useEffect, useState, useId, forwardRef } from \"react\";\nimport gsap from \"gsap\";\nimport { cn } from \"./utils\";\nimport { generateGlassMaps } from \"./generate-displacement-map\";\n\nexport interface LiquidGlassButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n /** Button width in px */\n width?: number;\n /** Button height in px */\n height?: number;\n /** Border radius in px */\n radius?: number;\n /** Edge thickness of the glass refraction zone */\n edgeSize?: number;\n /** Edge refraction intensity (0-1) */\n intensity?: number;\n /** Specular rim thickness relative to size (0-1) */\n specularWidth?: number;\n /** feDisplacementMap scale - how much the background refracts */\n displacement?: number;\n /** Gaussian blur applied to the background */\n blur?: number;\n /** Saturation applied to the displaced result */\n saturation?: number;\n /** Brightness boost on the backdrop-filter (1 = normal) */\n brightness?: number;\n /** Background tint color of the glass */\n glassColor?: string;\n /** Scale multiplier on hover */\n hoverScale?: number;\n /** Displacement scale on hover */\n hoverDisplacement?: number;\n /** Blur amount on hover */\n hoverBlur?: number;\n /** Duration of hover animation in seconds */\n hoverDuration?: number;\n /** Disable all GSAP animations */\n disableAnimation?: boolean;\n /** Supersampling quality for the displacement map (default 2, higher = smoother) */\n quality?: number;\n}\n\nexport const LiquidGlassButton = forwardRef<HTMLButtonElement, LiquidGlassButtonProps>(\n function LiquidGlassButton({\n children, className, style,\n width = 300,\n height = 56,\n radius = 60,\n edgeSize = 30,\n intensity = 0.7,\n specularWidth = 0.02,\n displacement = 55,\n blur = 1,\n saturation = 150,\n brightness = 1.1,\n glassColor = \"transparent\",\n hoverScale = 1.08,\n hoverDisplacement = 125,\n hoverBlur = 4,\n hoverDuration = 0.25,\n disableAnimation = false,\n quality = 2,\n ...props\n }, ref) {\n const internalRef = useRef<HTMLButtonElement>(null);\n const buttonRef = (ref as React.RefObject<HTMLButtonElement>) ?? internalRef;\n\n const displacerRef = useRef<SVGFEDisplacementMapElement>(null);\n const blurRef = useRef<SVGFEGaussianBlurElement>(null);\n\n const filterId = \"lg\" + useId().replace(/:/g, \"\");\n\n const [maps, setMaps] = useState<ReturnType<typeof generateGlassMaps> | null>(null);\n\n useEffect(() => {\n let cancelled = false;\n const m = generateGlassMaps({ width, height, radius, edgeSize, intensity, specularWidth, quality });\n\n // Pre-decode both PNGs\n let loaded = 0;\n const onLoad = () => {\n loaded++;\n if (loaded === 2 && !cancelled) setMaps(m);\n };\n\n const img1 = new Image();\n img1.onload = onLoad;\n img1.src = m.displacement;\n\n const img2 = new Image();\n img2.onload = onLoad;\n img2.src = m.specular;\n\n return () => { cancelled = true; };\n }, [width, height, radius, edgeSize, intensity, specularWidth, quality]);\n\n useEffect(() => {\n const button = buttonRef.current;\n const displacer = displacerRef.current;\n const blurEl = blurRef.current;\n if (!button || !displacer || !blurEl || disableAnimation) return;\n\n if (window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches) return;\n\n const fx = {\n displacement: displacement,\n blur: blur,\n };\n\n const sync = () => {\n displacer.setAttribute(\"scale\", fx.displacement.toString());\n blurEl.setAttribute(\"stdDeviation\", fx.blur.toString());\n };\n\n sync();\n\n const onEnter = () => {\n gsap.killTweensOf([fx, button]);\n gsap.to(fx, {\n displacement: hoverDisplacement,\n blur: hoverBlur,\n duration: hoverDuration,\n ease: \"back.out(1.4)\",\n onUpdate: sync,\n });\n gsap.to(button, {\n scale: hoverScale,\n duration: hoverDuration,\n ease: \"back.out(1.4)\",\n });\n };\n\n const onLeave = () => {\n gsap.killTweensOf([fx, button]);\n gsap.to(fx, {\n displacement: displacement,\n blur: blur,\n duration: hoverDuration,\n ease: \"power2.out\",\n onUpdate: sync,\n });\n gsap.to(button, {\n scale: 1,\n duration: hoverDuration,\n ease: \"power2.out\",\n });\n };\n\n const onClick = () => {\n gsap.killTweensOf(button);\n const cur = gsap.getProperty(button, \"scale\") as number;\n gsap.timeline()\n .to(button, { scale: cur * 0.92, duration: 0.08, ease: \"power2.in\" })\n .to(button, { scale: hoverScale, duration: 0.25, ease: \"back.out(2)\" });\n };\n\n button.addEventListener(\"pointerenter\", onEnter);\n button.addEventListener(\"pointerleave\", onLeave);\n button.addEventListener(\"click\", onClick);\n\n return () => {\n button.removeEventListener(\"pointerenter\", onEnter);\n button.removeEventListener(\"pointerleave\", onLeave);\n button.removeEventListener(\"click\", onClick);\n gsap.killTweensOf([button, fx]);\n };\n }, [buttonRef, maps, displacement, blur, hoverScale, hoverDisplacement, hoverBlur, hoverDuration, disableAnimation]);\n\n if (!maps) return null;\n\n return (\n <>\n <button\n ref={buttonRef}\n className={cn(\"relative overflow-hidden shadow-lg cursor-pointer\", className)}\n style={{ width, height, borderRadius: radius, border: \"none\", background: glassColor, ...style }}\n {...props}\n >\n <div\n className=\"absolute inset-0\"\n style={{\n backdropFilter: `url(#${filterId}) brightness(${brightness * 100}%)`,\n WebkitBackdropFilter: `url(#${filterId}) brightness(${brightness * 100}%)`,\n borderRadius: \"inherit\",\n willChange: \"backdrop-filter\",\n transform: \"translateZ(0)\",\n }}\n />\n <div\n className=\"absolute inset-0 inline-flex items-center justify-center font-bold text-white\"\n style={{ background: \"hsl(0 100% 100% / 15%)\", borderRadius: \"inherit\" }}\n >\n {children}\n </div>\n </button>\n\n <svg\n colorInterpolationFilters=\"sRGB\"\n style={{ position: \"absolute\", width: 0, height: 0, overflow: \"hidden\" }}\n aria-hidden=\"true\"\n >\n <defs>\n <filter id={filterId}>\n <feGaussianBlur\n ref={blurRef}\n in=\"SourceGraphic\"\n stdDeviation={blur}\n result=\"blurred_source\"\n />\n <feImage\n href={maps.displacement}\n x=\"0\"\n y=\"0\"\n width={width}\n height={height}\n result=\"displacement_map\"\n />\n <feDisplacementMap\n ref={displacerRef}\n in=\"blurred_source\"\n in2=\"displacement_map\"\n scale={displacement}\n xChannelSelector=\"R\"\n yChannelSelector=\"G\"\n result=\"displaced\"\n />\n <feColorMatrix\n in=\"displaced\"\n type=\"saturate\"\n result=\"displaced_saturated\"\n values={saturation.toString()}\n />\n <feImage\n href={maps.specular}\n x=\"0\"\n y=\"0\"\n width={width}\n height={height}\n result=\"specular_layer\"\n />\n <feGaussianBlur\n in=\"specular_layer\"\n stdDeviation=\"1\"\n result=\"blurred_specular_layer\"\n />\n <feComposite\n in=\"displaced_saturated\"\n in2=\"blurred_specular_layer\"\n operator=\"in\"\n result=\"final_specular_layer\"\n />\n <feBlend\n in=\"final_specular_layer\"\n in2=\"displaced\"\n mode=\"normal\"\n />\n </filter>\n </defs>\n </svg>\n </>\n );\n }\n);\n","export function cn(...classes: (string | undefined | null | false)[]) {\n return classes.filter(Boolean).join(\" \");\n}\n","export interface MapOptions {\n width: number;\n height: number;\n radius?: number;\n edgeSize?: number;\n intensity?: number;\n specularWidth?: number;\n /** Supersampling multiplier for the displacement map (default: 2). Higher = smoother gradients. */\n quality?: number;\n}\n\nconst VERT = `attribute vec4 position; void main(){ gl_Position = position; }`;\n\nconst FRAG = `\nprecision highp float;\nuniform vec2 uRes;\nuniform float uRadius;\nuniform float uBorderSoftness;\nuniform float uSpecularWidth;\nuniform int uMode; // 0 = displacement, 1 = specular\n\nfloat sdRoundedBox(vec2 p, vec2 b, float r){\n r = min(r, min(b.x, b.y));\n vec2 q = abs(p) - b + r;\n return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r;\n}\n\nvec3 calcNormal(vec2 p, vec2 b, float r){\n float e = max(0.5, min(b.x, b.y) * 0.01);\n vec2 h = vec2(e, 0.0);\n return normalize(vec3(\n sdRoundedBox(p+h.xy, b, r) - sdRoundedBox(p-h.xy, b, r),\n sdRoundedBox(p+h.yx, b, r) - sdRoundedBox(p-h.yx, b, r),\n -e * 2.0\n ));\n}\n\nvoid main(){\n vec2 p = gl_FragCoord.xy - uRes * 0.5;\n vec2 halfSize = uRes * 0.5 - 1.0;\n float d = sdRoundedBox(p, halfSize, uRadius);\n\n if(d > 0.0){ gl_FragColor = vec4(0.0); return; }\n\n if(uMode == 0){\n vec3 n = calcNormal(p, halfSize, uRadius);\n vec3 nc = n * 0.5 + 0.5;\n float border = smoothstep(-uBorderSoftness, 0.0, d);\n vec3 flat_ = vec3(0.5, 0.5, 1.0);\n gl_FragColor = vec4(mix(flat_, nc, border), 1.0);\n } else {\n float rim = smoothstep(-uSpecularWidth - 2.0, -uSpecularWidth, d)\n * (1.0 - smoothstep(-2.0, 0.0, d));\n float glow = smoothstep(-uBorderSoftness, 0.0, d) * 0.1;\n float s = clamp(rim + glow, 0.0, 1.0);\n gl_FragColor = vec4(vec3(s), s);\n }\n}\n`;\n\nfunction compile(gl: WebGLRenderingContext, type: number, src: string) {\n const s = gl.createShader(type)!;\n gl.shaderSource(s, src);\n gl.compileShader(s);\n return s;\n}\n\nlet _cachedProgram: { gl: WebGLRenderingContext; program: WebGLProgram; canvas: HTMLCanvasElement } | null = null;\n\nfunction getGL() {\n if (_cachedProgram) return _cachedProgram;\n\n const canvas = document.createElement(\"canvas\");\n const gl = canvas.getContext(\"webgl\", { preserveDrawingBuffer: true, premultipliedAlpha: false })!;\n\n const vs = compile(gl, gl.VERTEX_SHADER, VERT);\n const fs = compile(gl, gl.FRAGMENT_SHADER, FRAG);\n const program = gl.createProgram()!;\n gl.attachShader(program, vs);\n gl.attachShader(program, fs);\n gl.linkProgram(program);\n gl.useProgram(program);\n\n const buf = gl.createBuffer();\n gl.bindBuffer(gl.ARRAY_BUFFER, buf);\n gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), gl.STATIC_DRAW);\n const pos = gl.getAttribLocation(program, \"position\");\n gl.enableVertexAttribArray(pos);\n gl.vertexAttribPointer(pos, 2, gl.FLOAT, false, 0, 0);\n\n _cachedProgram = { gl, program, canvas };\n return _cachedProgram;\n}\n\nfunction render(\n width: number,\n height: number,\n radius: number,\n borderSoftness: number,\n specularWidth: number,\n mode: number,\n): string {\n const { gl, program, canvas } = getGL();\n canvas.width = width;\n canvas.height = height;\n gl.viewport(0, 0, width, height);\n gl.clearColor(0, 0, 0, 0);\n gl.clear(gl.COLOR_BUFFER_BIT);\n gl.useProgram(program);\n\n gl.uniform2f(gl.getUniformLocation(program, \"uRes\"), width, height);\n gl.uniform1f(gl.getUniformLocation(program, \"uRadius\"), radius);\n gl.uniform1f(gl.getUniformLocation(program, \"uBorderSoftness\"), borderSoftness);\n gl.uniform1f(gl.getUniformLocation(program, \"uSpecularWidth\"), specularWidth);\n gl.uniform1i(gl.getUniformLocation(program, \"uMode\"), mode);\n\n gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);\n return canvas.toDataURL(\"image/png\");\n}\n\nexport function generateGlassMaps(opts: MapOptions): {\n displacement: string;\n specular: string;\n} {\n const {\n width,\n height,\n radius = 60,\n edgeSize = 30,\n intensity = 0.7,\n specularWidth = 0.02,\n quality = 2,\n } = opts;\n\n const scale = Math.max(1, Math.round(quality));\n const rw = width * scale;\n const rh = height * scale;\n\n const r = Math.min(radius * scale, rw / 2, rh / 2);\n const borderSoftness = edgeSize * intensity * scale;\n const specPx = specularWidth * Math.min(rw, rh);\n\n return {\n displacement: render(rw, rh, r, borderSoftness, specPx, 0),\n specular: render(rw, rh, r, borderSoftness, specPx, 1),\n };\n}\n"],"mappings":"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","names":["LiquidGlassButton","gsap"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/liquid-glass-button.tsx","../src/utils.ts","../src/generate-displacement-map.ts"],"sourcesContent":["export { LiquidGlassButton } from \"./liquid-glass-button\";\nexport type { LiquidGlassButtonProps } from \"./liquid-glass-button\";\nexport { generateGlassMaps, getCachedGlassMaps, revokeGlassMaps } from \"./generate-displacement-map\";\nexport type { MapOptions } from \"./generate-displacement-map\";\nexport { cn } from \"./utils\";\n","import React, { useRef, useEffect, useState, useId, forwardRef } from \"react\";\nimport gsap from \"gsap\";\nimport { cn } from \"./utils\";\nimport { generateGlassMaps, getCachedGlassMaps, revokeGlassMaps } from \"./generate-displacement-map\";\n\nexport interface LiquidGlassButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n /** Button width in px */\n width?: number;\n /** Button height in px */\n height?: number;\n /** Border radius in px */\n radius?: number;\n /** Edge thickness of the glass refraction zone */\n edgeSize?: number;\n /** Edge refraction intensity (0-1) */\n intensity?: number;\n /** Specular rim thickness relative to size (0-1) */\n specularWidth?: number;\n /** feDisplacementMap scale - how much the background refracts */\n displacement?: number;\n /** Gaussian blur applied to the background */\n blur?: number;\n /** Saturation applied to the displaced result */\n saturation?: number;\n /** Brightness boost on the backdrop-filter (1 = normal) */\n brightness?: number;\n /** Background tint color of the glass */\n glassColor?: string;\n /** Scale multiplier on hover */\n hoverScale?: number;\n /** Displacement scale on hover */\n hoverDisplacement?: number;\n /** Blur amount on hover */\n hoverBlur?: number;\n /** Duration of hover animation in seconds */\n hoverDuration?: number;\n /** Disable all GSAP animations */\n disableAnimation?: boolean;\n /** Supersampling quality for the displacement map (default 2, higher = smoother) */\n quality?: number;\n}\n\nexport const LiquidGlassButton = forwardRef<HTMLButtonElement, LiquidGlassButtonProps>(\n function LiquidGlassButton({\n children, className, style,\n width = 300,\n height = 56,\n radius = 60,\n edgeSize = 30,\n intensity = 0.7,\n specularWidth = 0.02,\n displacement = 55,\n blur = 1,\n saturation = 150,\n brightness = 1.1,\n glassColor = \"transparent\",\n hoverScale = 1.08,\n hoverDisplacement = 125,\n hoverBlur = 4,\n hoverDuration = 0.25,\n disableAnimation = false,\n quality = 2,\n ...props\n }, ref) {\n const internalRef = useRef<HTMLButtonElement>(null);\n const buttonRef = (ref as React.RefObject<HTMLButtonElement>) ?? internalRef;\n\n const displacerRef = useRef<SVGFEDisplacementMapElement>(null);\n const blurRef = useRef<SVGFEGaussianBlurElement>(null);\n\n const filterId = \"lg\" + useId().replace(/:/g, \"\");\n\n const mapOpts = { width, height, radius, edgeSize, intensity, specularWidth, quality };\n const [maps, setMaps] = useState<{ displacement: string; specular: string } | null>(\n () => getCachedGlassMaps(mapOpts),\n );\n\n useEffect(() => {\n // If already have maps from cache, skip\n const cached = getCachedGlassMaps(mapOpts);\n if (cached) {\n setMaps(cached);\n return;\n }\n\n let cancelled = false;\n generateGlassMaps(mapOpts).then((m) => {\n if (!cancelled) setMaps(m);\n });\n\n return () => { cancelled = true; };\n }, [width, height, radius, edgeSize, intensity, specularWidth, quality]);\n\n useEffect(() => {\n return () => {\n if (maps) revokeGlassMaps(maps);\n };\n }, [maps]);\n\n useEffect(() => {\n const button = buttonRef.current;\n const displacer = displacerRef.current;\n const blurEl = blurRef.current;\n if (!button || !displacer || !blurEl || disableAnimation) return;\n\n if (window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches) return;\n\n const fx = {\n displacement: displacement,\n blur: blur,\n };\n\n const sync = () => {\n displacer.setAttribute(\"scale\", fx.displacement.toString());\n blurEl.setAttribute(\"stdDeviation\", fx.blur.toString());\n };\n\n sync();\n\n const onEnter = () => {\n gsap.killTweensOf([fx, button]);\n gsap.to(fx, {\n displacement: hoverDisplacement,\n blur: hoverBlur,\n duration: hoverDuration,\n ease: \"back.out(1.4)\",\n onUpdate: sync,\n });\n gsap.to(button, {\n scale: hoverScale,\n duration: hoverDuration,\n ease: \"back.out(1.4)\",\n });\n };\n\n const onLeave = () => {\n gsap.killTweensOf([fx, button]);\n gsap.to(fx, {\n displacement: displacement,\n blur: blur,\n duration: hoverDuration,\n ease: \"power2.out\",\n onUpdate: sync,\n });\n gsap.to(button, {\n scale: 1,\n duration: hoverDuration,\n ease: \"power2.out\",\n });\n };\n\n const onClick = () => {\n gsap.killTweensOf(button);\n const cur = gsap.getProperty(button, \"scale\") as number;\n gsap.timeline()\n .to(button, { scale: cur * 0.92, duration: 0.08, ease: \"power2.in\" })\n .to(button, { scale: hoverScale, duration: 0.25, ease: \"back.out(2)\" });\n };\n\n button.addEventListener(\"pointerenter\", onEnter);\n button.addEventListener(\"pointerleave\", onLeave);\n button.addEventListener(\"click\", onClick);\n\n return () => {\n button.removeEventListener(\"pointerenter\", onEnter);\n button.removeEventListener(\"pointerleave\", onLeave);\n button.removeEventListener(\"click\", onClick);\n gsap.killTweensOf([button, fx]);\n };\n }, [buttonRef, maps, displacement, blur, hoverScale, hoverDisplacement, hoverBlur, hoverDuration, disableAnimation]);\n\n if (!maps) return null;\n\n return (\n <>\n <button\n ref={buttonRef}\n className={cn(\"relative overflow-hidden shadow-lg cursor-pointer\", className)}\n style={{ width, height, borderRadius: radius, border: \"none\", background: glassColor, ...style }}\n {...props}\n >\n <div\n className=\"absolute inset-0\"\n style={{\n backdropFilter: `url(#${filterId}) brightness(${brightness * 100}%)`,\n WebkitBackdropFilter: `url(#${filterId}) brightness(${brightness * 100}%)`,\n borderRadius: \"inherit\",\n willChange: \"backdrop-filter\",\n transform: \"translateZ(0)\",\n }}\n />\n <div\n className=\"absolute inset-0 inline-flex items-center justify-center font-bold text-white\"\n style={{ background: \"hsl(0 100% 100% / 15%)\", borderRadius: \"inherit\" }}\n >\n {children}\n </div>\n </button>\n\n <svg\n colorInterpolationFilters=\"sRGB\"\n style={{ position: \"absolute\", width: 0, height: 0, overflow: \"hidden\" }}\n aria-hidden=\"true\"\n >\n <defs>\n <filter id={filterId}>\n <feGaussianBlur\n ref={blurRef}\n in=\"SourceGraphic\"\n stdDeviation={blur}\n result=\"blurred_source\"\n />\n <feImage\n href={maps.displacement}\n x=\"0\"\n y=\"0\"\n width={width}\n height={height}\n result=\"displacement_map\"\n />\n <feDisplacementMap\n ref={displacerRef}\n in=\"blurred_source\"\n in2=\"displacement_map\"\n scale={displacement}\n xChannelSelector=\"R\"\n yChannelSelector=\"G\"\n result=\"displaced\"\n />\n <feColorMatrix\n in=\"displaced\"\n type=\"saturate\"\n result=\"displaced_saturated\"\n values={saturation.toString()}\n />\n <feImage\n href={maps.specular}\n x=\"0\"\n y=\"0\"\n width={width}\n height={height}\n result=\"specular_layer\"\n />\n <feGaussianBlur\n in=\"specular_layer\"\n stdDeviation=\"1\"\n result=\"blurred_specular_layer\"\n />\n <feComposite\n in=\"displaced_saturated\"\n in2=\"blurred_specular_layer\"\n operator=\"in\"\n result=\"final_specular_layer\"\n />\n <feBlend\n in=\"final_specular_layer\"\n in2=\"displaced\"\n mode=\"normal\"\n />\n </filter>\n </defs>\n </svg>\n </>\n );\n }\n);\n","export function cn(...classes: (string | undefined | null | false)[]) {\n return classes.filter(Boolean).join(\" \");\n}\n","export interface MapOptions {\n width: number;\n height: number;\n radius?: number;\n edgeSize?: number;\n intensity?: number;\n specularWidth?: number;\n /** Supersampling multiplier for the displacement map (default: 2). Higher = smoother gradients. */\n quality?: number;\n}\n\nconst VERT = `attribute vec4 position; void main(){ gl_Position = position; }`;\n\nconst FRAG = `\nprecision highp float;\nuniform vec2 uRes;\nuniform float uRadius;\nuniform float uBorderSoftness;\nuniform float uSpecularWidth;\nuniform int uMode; // 0 = displacement, 1 = specular\n\nfloat sdRoundedBox(vec2 p, vec2 b, float r){\n r = min(r, min(b.x, b.y));\n vec2 q = abs(p) - b + r;\n return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r;\n}\n\nvec3 calcNormal(vec2 p, vec2 b, float r){\n float e = max(0.5, min(b.x, b.y) * 0.01);\n vec2 h = vec2(e, 0.0);\n return normalize(vec3(\n sdRoundedBox(p+h.xy, b, r) - sdRoundedBox(p-h.xy, b, r),\n sdRoundedBox(p+h.yx, b, r) - sdRoundedBox(p-h.yx, b, r),\n -e * 2.0\n ));\n}\n\nvoid main(){\n vec2 p = gl_FragCoord.xy - uRes * 0.5;\n vec2 halfSize = uRes * 0.5 - 1.0;\n float d = sdRoundedBox(p, halfSize, uRadius);\n\n if(d > 0.0){ gl_FragColor = vec4(0.0); return; }\n\n if(uMode == 0){\n vec3 n = calcNormal(p, halfSize, uRadius);\n vec3 nc = n * 0.5 + 0.5;\n float border = smoothstep(-uBorderSoftness, 0.0, d);\n vec3 flat_ = vec3(0.5, 0.5, 1.0);\n gl_FragColor = vec4(mix(flat_, nc, border), 1.0);\n } else {\n float rim = smoothstep(-uSpecularWidth - 2.0, -uSpecularWidth, d)\n * (1.0 - smoothstep(-2.0, 0.0, d));\n float glow = smoothstep(-uBorderSoftness, 0.0, d) * 0.1;\n float s = clamp(rim + glow, 0.0, 1.0);\n gl_FragColor = vec4(vec3(s), s);\n }\n}\n`;\n\nfunction compile(gl: WebGLRenderingContext, type: number, src: string) {\n const s = gl.createShader(type)!;\n gl.shaderSource(s, src);\n gl.compileShader(s);\n return s;\n}\n\nlet _cachedProgram: { gl: WebGLRenderingContext; program: WebGLProgram; canvas: HTMLCanvasElement } | null = null;\n\nfunction getGL() {\n if (_cachedProgram) return _cachedProgram;\n\n const canvas = document.createElement(\"canvas\");\n const gl = canvas.getContext(\"webgl\", { preserveDrawingBuffer: true, premultipliedAlpha: false })!;\n\n const vs = compile(gl, gl.VERTEX_SHADER, VERT);\n const fs = compile(gl, gl.FRAGMENT_SHADER, FRAG);\n const program = gl.createProgram()!;\n gl.attachShader(program, vs);\n gl.attachShader(program, fs);\n gl.linkProgram(program);\n gl.useProgram(program);\n\n const buf = gl.createBuffer();\n gl.bindBuffer(gl.ARRAY_BUFFER, buf);\n gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), gl.STATIC_DRAW);\n const pos = gl.getAttribLocation(program, \"position\");\n gl.enableVertexAttribArray(pos);\n gl.vertexAttribPointer(pos, 2, gl.FLOAT, false, 0, 0);\n\n _cachedProgram = { gl, program, canvas };\n return _cachedProgram;\n}\n\n// ── Cache ──────────────────────────────────────────────────────\n\nconst _mapCache = new Map<string, { displacement: string; specular: string }>();\n\nfunction cacheKey(\n w: number, h: number, r: number, bs: number, sw: number,\n): string {\n return `${w}|${h}|${r}|${bs}|${sw}`;\n}\n\n// ── Render ─────────────────────────────────────────────────────\n\nfunction renderToBlob(\n width: number,\n height: number,\n radius: number,\n borderSoftness: number,\n specularWidth: number,\n mode: number,\n): Promise<string> {\n const { gl, program, canvas } = getGL();\n canvas.width = width;\n canvas.height = height;\n gl.viewport(0, 0, width, height);\n gl.clearColor(0, 0, 0, 0);\n gl.clear(gl.COLOR_BUFFER_BIT);\n gl.useProgram(program);\n\n gl.uniform2f(gl.getUniformLocation(program, \"uRes\"), width, height);\n gl.uniform1f(gl.getUniformLocation(program, \"uRadius\"), radius);\n gl.uniform1f(gl.getUniformLocation(program, \"uBorderSoftness\"), borderSoftness);\n gl.uniform1f(gl.getUniformLocation(program, \"uSpecularWidth\"), specularWidth);\n gl.uniform1i(gl.getUniformLocation(program, \"uMode\"), mode);\n\n gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);\n\n return new Promise((resolve) => {\n canvas.toBlob((blob) => {\n resolve(URL.createObjectURL(blob!));\n }, \"image/png\");\n });\n}\n\n// ── Public API ─────────────────────────────────────────────────\n\nexport async function generateGlassMaps(opts: MapOptions): Promise<{\n displacement: string;\n specular: string;\n}> {\n const {\n width,\n height,\n radius = 60,\n edgeSize = 30,\n intensity = 0.7,\n specularWidth = 0.02,\n quality = 2,\n } = opts;\n\n const scale = Math.max(1, Math.round(quality));\n const rw = width * scale;\n const rh = height * scale;\n\n const r = Math.min(radius * scale, rw / 2, rh / 2);\n const borderSoftness = edgeSize * intensity * scale;\n const specPx = specularWidth * Math.min(rw, rh);\n\n const key = cacheKey(rw, rh, r, borderSoftness, specPx);\n const cached = _mapCache.get(key);\n if (cached) return cached;\n\n const [displacement, specular] = await Promise.all([\n renderToBlob(rw, rh, r, borderSoftness, specPx, 0),\n renderToBlob(rw, rh, r, borderSoftness, specPx, 1),\n ]);\n\n const result = { displacement, specular };\n _mapCache.set(key, result);\n return result;\n}\n\nexport function getCachedGlassMaps(opts: MapOptions): { displacement: string; specular: string } | null {\n const {\n width,\n height,\n radius = 60,\n edgeSize = 30,\n intensity = 0.7,\n specularWidth = 0.02,\n quality = 2,\n } = opts;\n\n const scale = Math.max(1, Math.round(quality));\n const rw = width * scale;\n const rh = height * scale;\n\n const r = Math.min(radius * scale, rw / 2, rh / 2);\n const borderSoftness = edgeSize * intensity * scale;\n const specPx = specularWidth * Math.min(rw, rh);\n\n return _mapCache.get(cacheKey(rw, rh, r, borderSoftness, specPx)) ?? null;\n}\n\nexport function revokeGlassMaps(maps: { displacement: string; specular: string }) {\n URL.revokeObjectURL(maps.displacement);\n URL.revokeObjectURL(maps.specular);\n}\n"],"mappings":"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","names":["LiquidGlassButton","gsap"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -48,11 +48,19 @@ interface MapOptions {
|
|
|
48
48
|
/** Supersampling multiplier for the displacement map (default: 2). Higher = smoother gradients. */
|
|
49
49
|
quality?: number;
|
|
50
50
|
}
|
|
51
|
-
declare function generateGlassMaps(opts: MapOptions): {
|
|
51
|
+
declare function generateGlassMaps(opts: MapOptions): Promise<{
|
|
52
52
|
displacement: string;
|
|
53
53
|
specular: string;
|
|
54
|
-
}
|
|
54
|
+
}>;
|
|
55
|
+
declare function getCachedGlassMaps(opts: MapOptions): {
|
|
56
|
+
displacement: string;
|
|
57
|
+
specular: string;
|
|
58
|
+
} | null;
|
|
59
|
+
declare function revokeGlassMaps(maps: {
|
|
60
|
+
displacement: string;
|
|
61
|
+
specular: string;
|
|
62
|
+
}): void;
|
|
55
63
|
|
|
56
64
|
declare function cn(...classes: (string | undefined | null | false)[]): string;
|
|
57
65
|
|
|
58
|
-
export { LiquidGlassButton, type LiquidGlassButtonProps, type MapOptions, cn, generateGlassMaps };
|
|
66
|
+
export { LiquidGlassButton, type LiquidGlassButtonProps, type MapOptions, cn, generateGlassMaps, getCachedGlassMaps, revokeGlassMaps };
|
package/dist/index.d.ts
CHANGED
|
@@ -48,11 +48,19 @@ interface MapOptions {
|
|
|
48
48
|
/** Supersampling multiplier for the displacement map (default: 2). Higher = smoother gradients. */
|
|
49
49
|
quality?: number;
|
|
50
50
|
}
|
|
51
|
-
declare function generateGlassMaps(opts: MapOptions): {
|
|
51
|
+
declare function generateGlassMaps(opts: MapOptions): Promise<{
|
|
52
52
|
displacement: string;
|
|
53
53
|
specular: string;
|
|
54
|
-
}
|
|
54
|
+
}>;
|
|
55
|
+
declare function getCachedGlassMaps(opts: MapOptions): {
|
|
56
|
+
displacement: string;
|
|
57
|
+
specular: string;
|
|
58
|
+
} | null;
|
|
59
|
+
declare function revokeGlassMaps(maps: {
|
|
60
|
+
displacement: string;
|
|
61
|
+
specular: string;
|
|
62
|
+
}): void;
|
|
55
63
|
|
|
56
64
|
declare function cn(...classes: (string | undefined | null | false)[]): string;
|
|
57
65
|
|
|
58
|
-
export { LiquidGlassButton, type LiquidGlassButtonProps, type MapOptions, cn, generateGlassMaps };
|
|
66
|
+
export { LiquidGlassButton, type LiquidGlassButtonProps, type MapOptions, cn, generateGlassMaps, getCachedGlassMaps, revokeGlassMaps };
|
package/dist/index.js
CHANGED
|
@@ -115,7 +115,11 @@ function getGL() {
|
|
|
115
115
|
_cachedProgram = { gl, program, canvas };
|
|
116
116
|
return _cachedProgram;
|
|
117
117
|
}
|
|
118
|
-
|
|
118
|
+
var _mapCache = /* @__PURE__ */ new Map();
|
|
119
|
+
function cacheKey(w, h, r, bs, sw) {
|
|
120
|
+
return `${w}|${h}|${r}|${bs}|${sw}`;
|
|
121
|
+
}
|
|
122
|
+
function renderToBlob(width, height, radius, borderSoftness, specularWidth, mode) {
|
|
119
123
|
const { gl, program, canvas } = getGL();
|
|
120
124
|
canvas.width = width;
|
|
121
125
|
canvas.height = height;
|
|
@@ -129,9 +133,41 @@ function render(width, height, radius, borderSoftness, specularWidth, mode) {
|
|
|
129
133
|
gl.uniform1f(gl.getUniformLocation(program, "uSpecularWidth"), specularWidth);
|
|
130
134
|
gl.uniform1i(gl.getUniformLocation(program, "uMode"), mode);
|
|
131
135
|
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
|
|
132
|
-
return
|
|
136
|
+
return new Promise((resolve) => {
|
|
137
|
+
canvas.toBlob((blob) => {
|
|
138
|
+
resolve(URL.createObjectURL(blob));
|
|
139
|
+
}, "image/png");
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
async function generateGlassMaps(opts) {
|
|
143
|
+
const {
|
|
144
|
+
width,
|
|
145
|
+
height,
|
|
146
|
+
radius = 60,
|
|
147
|
+
edgeSize = 30,
|
|
148
|
+
intensity = 0.7,
|
|
149
|
+
specularWidth = 0.02,
|
|
150
|
+
quality = 2
|
|
151
|
+
} = opts;
|
|
152
|
+
const scale = Math.max(1, Math.round(quality));
|
|
153
|
+
const rw = width * scale;
|
|
154
|
+
const rh = height * scale;
|
|
155
|
+
const r = Math.min(radius * scale, rw / 2, rh / 2);
|
|
156
|
+
const borderSoftness = edgeSize * intensity * scale;
|
|
157
|
+
const specPx = specularWidth * Math.min(rw, rh);
|
|
158
|
+
const key = cacheKey(rw, rh, r, borderSoftness, specPx);
|
|
159
|
+
const cached = _mapCache.get(key);
|
|
160
|
+
if (cached) return cached;
|
|
161
|
+
const [displacement, specular] = await Promise.all([
|
|
162
|
+
renderToBlob(rw, rh, r, borderSoftness, specPx, 0),
|
|
163
|
+
renderToBlob(rw, rh, r, borderSoftness, specPx, 1)
|
|
164
|
+
]);
|
|
165
|
+
const result = { displacement, specular };
|
|
166
|
+
_mapCache.set(key, result);
|
|
167
|
+
return result;
|
|
133
168
|
}
|
|
134
|
-
function
|
|
169
|
+
function getCachedGlassMaps(opts) {
|
|
170
|
+
var _a;
|
|
135
171
|
const {
|
|
136
172
|
width,
|
|
137
173
|
height,
|
|
@@ -147,10 +183,11 @@ function generateGlassMaps(opts) {
|
|
|
147
183
|
const r = Math.min(radius * scale, rw / 2, rh / 2);
|
|
148
184
|
const borderSoftness = edgeSize * intensity * scale;
|
|
149
185
|
const specPx = specularWidth * Math.min(rw, rh);
|
|
150
|
-
return
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
186
|
+
return (_a = _mapCache.get(cacheKey(rw, rh, r, borderSoftness, specPx))) != null ? _a : null;
|
|
187
|
+
}
|
|
188
|
+
function revokeGlassMaps(maps) {
|
|
189
|
+
URL.revokeObjectURL(maps.displacement);
|
|
190
|
+
URL.revokeObjectURL(maps.specular);
|
|
154
191
|
}
|
|
155
192
|
|
|
156
193
|
// src/liquid-glass-button.tsx
|
|
@@ -205,25 +242,29 @@ var LiquidGlassButton = forwardRef(
|
|
|
205
242
|
const displacerRef = useRef(null);
|
|
206
243
|
const blurRef = useRef(null);
|
|
207
244
|
const filterId = "lg" + useId().replace(/:/g, "");
|
|
208
|
-
const
|
|
245
|
+
const mapOpts = { width, height, radius, edgeSize, intensity, specularWidth, quality };
|
|
246
|
+
const [maps, setMaps] = useState(
|
|
247
|
+
() => getCachedGlassMaps(mapOpts)
|
|
248
|
+
);
|
|
209
249
|
useEffect(() => {
|
|
250
|
+
const cached = getCachedGlassMaps(mapOpts);
|
|
251
|
+
if (cached) {
|
|
252
|
+
setMaps(cached);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
210
255
|
let cancelled = false;
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
loaded++;
|
|
215
|
-
if (loaded === 2 && !cancelled) setMaps(m);
|
|
216
|
-
};
|
|
217
|
-
const img1 = new Image();
|
|
218
|
-
img1.onload = onLoad;
|
|
219
|
-
img1.src = m.displacement;
|
|
220
|
-
const img2 = new Image();
|
|
221
|
-
img2.onload = onLoad;
|
|
222
|
-
img2.src = m.specular;
|
|
256
|
+
generateGlassMaps(mapOpts).then((m) => {
|
|
257
|
+
if (!cancelled) setMaps(m);
|
|
258
|
+
});
|
|
223
259
|
return () => {
|
|
224
260
|
cancelled = true;
|
|
225
261
|
};
|
|
226
262
|
}, [width, height, radius, edgeSize, intensity, specularWidth, quality]);
|
|
263
|
+
useEffect(() => {
|
|
264
|
+
return () => {
|
|
265
|
+
if (maps) revokeGlassMaps(maps);
|
|
266
|
+
};
|
|
267
|
+
}, [maps]);
|
|
227
268
|
useEffect(() => {
|
|
228
269
|
const button = buttonRef.current;
|
|
229
270
|
const displacer = displacerRef.current;
|
|
@@ -411,6 +452,8 @@ var LiquidGlassButton = forwardRef(
|
|
|
411
452
|
export {
|
|
412
453
|
LiquidGlassButton,
|
|
413
454
|
cn,
|
|
414
|
-
generateGlassMaps
|
|
455
|
+
generateGlassMaps,
|
|
456
|
+
getCachedGlassMaps,
|
|
457
|
+
revokeGlassMaps
|
|
415
458
|
};
|
|
416
459
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/liquid-glass-button.tsx","../src/utils.ts","../src/generate-displacement-map.ts"],"sourcesContent":["import React, { useRef, useEffect, useState, useId, forwardRef } from \"react\";\nimport gsap from \"gsap\";\nimport { cn } from \"./utils\";\nimport { generateGlassMaps } from \"./generate-displacement-map\";\n\nexport interface LiquidGlassButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n /** Button width in px */\n width?: number;\n /** Button height in px */\n height?: number;\n /** Border radius in px */\n radius?: number;\n /** Edge thickness of the glass refraction zone */\n edgeSize?: number;\n /** Edge refraction intensity (0-1) */\n intensity?: number;\n /** Specular rim thickness relative to size (0-1) */\n specularWidth?: number;\n /** feDisplacementMap scale - how much the background refracts */\n displacement?: number;\n /** Gaussian blur applied to the background */\n blur?: number;\n /** Saturation applied to the displaced result */\n saturation?: number;\n /** Brightness boost on the backdrop-filter (1 = normal) */\n brightness?: number;\n /** Background tint color of the glass */\n glassColor?: string;\n /** Scale multiplier on hover */\n hoverScale?: number;\n /** Displacement scale on hover */\n hoverDisplacement?: number;\n /** Blur amount on hover */\n hoverBlur?: number;\n /** Duration of hover animation in seconds */\n hoverDuration?: number;\n /** Disable all GSAP animations */\n disableAnimation?: boolean;\n /** Supersampling quality for the displacement map (default 2, higher = smoother) */\n quality?: number;\n}\n\nexport const LiquidGlassButton = forwardRef<HTMLButtonElement, LiquidGlassButtonProps>(\n function LiquidGlassButton({\n children, className, style,\n width = 300,\n height = 56,\n radius = 60,\n edgeSize = 30,\n intensity = 0.7,\n specularWidth = 0.02,\n displacement = 55,\n blur = 1,\n saturation = 150,\n brightness = 1.1,\n glassColor = \"transparent\",\n hoverScale = 1.08,\n hoverDisplacement = 125,\n hoverBlur = 4,\n hoverDuration = 0.25,\n disableAnimation = false,\n quality = 2,\n ...props\n }, ref) {\n const internalRef = useRef<HTMLButtonElement>(null);\n const buttonRef = (ref as React.RefObject<HTMLButtonElement>) ?? internalRef;\n\n const displacerRef = useRef<SVGFEDisplacementMapElement>(null);\n const blurRef = useRef<SVGFEGaussianBlurElement>(null);\n\n const filterId = \"lg\" + useId().replace(/:/g, \"\");\n\n const [maps, setMaps] = useState<ReturnType<typeof generateGlassMaps> | null>(null);\n\n useEffect(() => {\n let cancelled = false;\n const m = generateGlassMaps({ width, height, radius, edgeSize, intensity, specularWidth, quality });\n\n // Pre-decode both PNGs\n let loaded = 0;\n const onLoad = () => {\n loaded++;\n if (loaded === 2 && !cancelled) setMaps(m);\n };\n\n const img1 = new Image();\n img1.onload = onLoad;\n img1.src = m.displacement;\n\n const img2 = new Image();\n img2.onload = onLoad;\n img2.src = m.specular;\n\n return () => { cancelled = true; };\n }, [width, height, radius, edgeSize, intensity, specularWidth, quality]);\n\n useEffect(() => {\n const button = buttonRef.current;\n const displacer = displacerRef.current;\n const blurEl = blurRef.current;\n if (!button || !displacer || !blurEl || disableAnimation) return;\n\n if (window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches) return;\n\n const fx = {\n displacement: displacement,\n blur: blur,\n };\n\n const sync = () => {\n displacer.setAttribute(\"scale\", fx.displacement.toString());\n blurEl.setAttribute(\"stdDeviation\", fx.blur.toString());\n };\n\n sync();\n\n const onEnter = () => {\n gsap.killTweensOf([fx, button]);\n gsap.to(fx, {\n displacement: hoverDisplacement,\n blur: hoverBlur,\n duration: hoverDuration,\n ease: \"back.out(1.4)\",\n onUpdate: sync,\n });\n gsap.to(button, {\n scale: hoverScale,\n duration: hoverDuration,\n ease: \"back.out(1.4)\",\n });\n };\n\n const onLeave = () => {\n gsap.killTweensOf([fx, button]);\n gsap.to(fx, {\n displacement: displacement,\n blur: blur,\n duration: hoverDuration,\n ease: \"power2.out\",\n onUpdate: sync,\n });\n gsap.to(button, {\n scale: 1,\n duration: hoverDuration,\n ease: \"power2.out\",\n });\n };\n\n const onClick = () => {\n gsap.killTweensOf(button);\n const cur = gsap.getProperty(button, \"scale\") as number;\n gsap.timeline()\n .to(button, { scale: cur * 0.92, duration: 0.08, ease: \"power2.in\" })\n .to(button, { scale: hoverScale, duration: 0.25, ease: \"back.out(2)\" });\n };\n\n button.addEventListener(\"pointerenter\", onEnter);\n button.addEventListener(\"pointerleave\", onLeave);\n button.addEventListener(\"click\", onClick);\n\n return () => {\n button.removeEventListener(\"pointerenter\", onEnter);\n button.removeEventListener(\"pointerleave\", onLeave);\n button.removeEventListener(\"click\", onClick);\n gsap.killTweensOf([button, fx]);\n };\n }, [buttonRef, maps, displacement, blur, hoverScale, hoverDisplacement, hoverBlur, hoverDuration, disableAnimation]);\n\n if (!maps) return null;\n\n return (\n <>\n <button\n ref={buttonRef}\n className={cn(\"relative overflow-hidden shadow-lg cursor-pointer\", className)}\n style={{ width, height, borderRadius: radius, border: \"none\", background: glassColor, ...style }}\n {...props}\n >\n <div\n className=\"absolute inset-0\"\n style={{\n backdropFilter: `url(#${filterId}) brightness(${brightness * 100}%)`,\n WebkitBackdropFilter: `url(#${filterId}) brightness(${brightness * 100}%)`,\n borderRadius: \"inherit\",\n willChange: \"backdrop-filter\",\n transform: \"translateZ(0)\",\n }}\n />\n <div\n className=\"absolute inset-0 inline-flex items-center justify-center font-bold text-white\"\n style={{ background: \"hsl(0 100% 100% / 15%)\", borderRadius: \"inherit\" }}\n >\n {children}\n </div>\n </button>\n\n <svg\n colorInterpolationFilters=\"sRGB\"\n style={{ position: \"absolute\", width: 0, height: 0, overflow: \"hidden\" }}\n aria-hidden=\"true\"\n >\n <defs>\n <filter id={filterId}>\n <feGaussianBlur\n ref={blurRef}\n in=\"SourceGraphic\"\n stdDeviation={blur}\n result=\"blurred_source\"\n />\n <feImage\n href={maps.displacement}\n x=\"0\"\n y=\"0\"\n width={width}\n height={height}\n result=\"displacement_map\"\n />\n <feDisplacementMap\n ref={displacerRef}\n in=\"blurred_source\"\n in2=\"displacement_map\"\n scale={displacement}\n xChannelSelector=\"R\"\n yChannelSelector=\"G\"\n result=\"displaced\"\n />\n <feColorMatrix\n in=\"displaced\"\n type=\"saturate\"\n result=\"displaced_saturated\"\n values={saturation.toString()}\n />\n <feImage\n href={maps.specular}\n x=\"0\"\n y=\"0\"\n width={width}\n height={height}\n result=\"specular_layer\"\n />\n <feGaussianBlur\n in=\"specular_layer\"\n stdDeviation=\"1\"\n result=\"blurred_specular_layer\"\n />\n <feComposite\n in=\"displaced_saturated\"\n in2=\"blurred_specular_layer\"\n operator=\"in\"\n result=\"final_specular_layer\"\n />\n <feBlend\n in=\"final_specular_layer\"\n in2=\"displaced\"\n mode=\"normal\"\n />\n </filter>\n </defs>\n </svg>\n </>\n );\n }\n);\n","export function cn(...classes: (string | undefined | null | false)[]) {\n return classes.filter(Boolean).join(\" \");\n}\n","export interface MapOptions {\n width: number;\n height: number;\n radius?: number;\n edgeSize?: number;\n intensity?: number;\n specularWidth?: number;\n /** Supersampling multiplier for the displacement map (default: 2). Higher = smoother gradients. */\n quality?: number;\n}\n\nconst VERT = `attribute vec4 position; void main(){ gl_Position = position; }`;\n\nconst FRAG = `\nprecision highp float;\nuniform vec2 uRes;\nuniform float uRadius;\nuniform float uBorderSoftness;\nuniform float uSpecularWidth;\nuniform int uMode; // 0 = displacement, 1 = specular\n\nfloat sdRoundedBox(vec2 p, vec2 b, float r){\n r = min(r, min(b.x, b.y));\n vec2 q = abs(p) - b + r;\n return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r;\n}\n\nvec3 calcNormal(vec2 p, vec2 b, float r){\n float e = max(0.5, min(b.x, b.y) * 0.01);\n vec2 h = vec2(e, 0.0);\n return normalize(vec3(\n sdRoundedBox(p+h.xy, b, r) - sdRoundedBox(p-h.xy, b, r),\n sdRoundedBox(p+h.yx, b, r) - sdRoundedBox(p-h.yx, b, r),\n -e * 2.0\n ));\n}\n\nvoid main(){\n vec2 p = gl_FragCoord.xy - uRes * 0.5;\n vec2 halfSize = uRes * 0.5 - 1.0;\n float d = sdRoundedBox(p, halfSize, uRadius);\n\n if(d > 0.0){ gl_FragColor = vec4(0.0); return; }\n\n if(uMode == 0){\n vec3 n = calcNormal(p, halfSize, uRadius);\n vec3 nc = n * 0.5 + 0.5;\n float border = smoothstep(-uBorderSoftness, 0.0, d);\n vec3 flat_ = vec3(0.5, 0.5, 1.0);\n gl_FragColor = vec4(mix(flat_, nc, border), 1.0);\n } else {\n float rim = smoothstep(-uSpecularWidth - 2.0, -uSpecularWidth, d)\n * (1.0 - smoothstep(-2.0, 0.0, d));\n float glow = smoothstep(-uBorderSoftness, 0.0, d) * 0.1;\n float s = clamp(rim + glow, 0.0, 1.0);\n gl_FragColor = vec4(vec3(s), s);\n }\n}\n`;\n\nfunction compile(gl: WebGLRenderingContext, type: number, src: string) {\n const s = gl.createShader(type)!;\n gl.shaderSource(s, src);\n gl.compileShader(s);\n return s;\n}\n\nlet _cachedProgram: { gl: WebGLRenderingContext; program: WebGLProgram; canvas: HTMLCanvasElement } | null = null;\n\nfunction getGL() {\n if (_cachedProgram) return _cachedProgram;\n\n const canvas = document.createElement(\"canvas\");\n const gl = canvas.getContext(\"webgl\", { preserveDrawingBuffer: true, premultipliedAlpha: false })!;\n\n const vs = compile(gl, gl.VERTEX_SHADER, VERT);\n const fs = compile(gl, gl.FRAGMENT_SHADER, FRAG);\n const program = gl.createProgram()!;\n gl.attachShader(program, vs);\n gl.attachShader(program, fs);\n gl.linkProgram(program);\n gl.useProgram(program);\n\n const buf = gl.createBuffer();\n gl.bindBuffer(gl.ARRAY_BUFFER, buf);\n gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), gl.STATIC_DRAW);\n const pos = gl.getAttribLocation(program, \"position\");\n gl.enableVertexAttribArray(pos);\n gl.vertexAttribPointer(pos, 2, gl.FLOAT, false, 0, 0);\n\n _cachedProgram = { gl, program, canvas };\n return _cachedProgram;\n}\n\nfunction render(\n width: number,\n height: number,\n radius: number,\n borderSoftness: number,\n specularWidth: number,\n mode: number,\n): string {\n const { gl, program, canvas } = getGL();\n canvas.width = width;\n canvas.height = height;\n gl.viewport(0, 0, width, height);\n gl.clearColor(0, 0, 0, 0);\n gl.clear(gl.COLOR_BUFFER_BIT);\n gl.useProgram(program);\n\n gl.uniform2f(gl.getUniformLocation(program, \"uRes\"), width, height);\n gl.uniform1f(gl.getUniformLocation(program, \"uRadius\"), radius);\n gl.uniform1f(gl.getUniformLocation(program, \"uBorderSoftness\"), borderSoftness);\n gl.uniform1f(gl.getUniformLocation(program, \"uSpecularWidth\"), specularWidth);\n gl.uniform1i(gl.getUniformLocation(program, \"uMode\"), mode);\n\n gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);\n return canvas.toDataURL(\"image/png\");\n}\n\nexport function generateGlassMaps(opts: MapOptions): {\n displacement: string;\n specular: string;\n} {\n const {\n width,\n height,\n radius = 60,\n edgeSize = 30,\n intensity = 0.7,\n specularWidth = 0.02,\n quality = 2,\n } = opts;\n\n const scale = Math.max(1, Math.round(quality));\n const rw = width * scale;\n const rh = height * scale;\n\n const r = Math.min(radius * scale, rw / 2, rh / 2);\n const borderSoftness = edgeSize * intensity * scale;\n const specPx = specularWidth * Math.min(rw, rh);\n\n return {\n displacement: render(rw, rh, r, borderSoftness, specPx, 0),\n specular: render(rw, rh, r, borderSoftness, specPx, 1),\n };\n}\n"],"mappings":"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","names":["LiquidGlassButton"]}
|
|
1
|
+
{"version":3,"sources":["../src/liquid-glass-button.tsx","../src/utils.ts","../src/generate-displacement-map.ts"],"sourcesContent":["import React, { useRef, useEffect, useState, useId, forwardRef } from \"react\";\nimport gsap from \"gsap\";\nimport { cn } from \"./utils\";\nimport { generateGlassMaps, getCachedGlassMaps, revokeGlassMaps } from \"./generate-displacement-map\";\n\nexport interface LiquidGlassButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n /** Button width in px */\n width?: number;\n /** Button height in px */\n height?: number;\n /** Border radius in px */\n radius?: number;\n /** Edge thickness of the glass refraction zone */\n edgeSize?: number;\n /** Edge refraction intensity (0-1) */\n intensity?: number;\n /** Specular rim thickness relative to size (0-1) */\n specularWidth?: number;\n /** feDisplacementMap scale - how much the background refracts */\n displacement?: number;\n /** Gaussian blur applied to the background */\n blur?: number;\n /** Saturation applied to the displaced result */\n saturation?: number;\n /** Brightness boost on the backdrop-filter (1 = normal) */\n brightness?: number;\n /** Background tint color of the glass */\n glassColor?: string;\n /** Scale multiplier on hover */\n hoverScale?: number;\n /** Displacement scale on hover */\n hoverDisplacement?: number;\n /** Blur amount on hover */\n hoverBlur?: number;\n /** Duration of hover animation in seconds */\n hoverDuration?: number;\n /** Disable all GSAP animations */\n disableAnimation?: boolean;\n /** Supersampling quality for the displacement map (default 2, higher = smoother) */\n quality?: number;\n}\n\nexport const LiquidGlassButton = forwardRef<HTMLButtonElement, LiquidGlassButtonProps>(\n function LiquidGlassButton({\n children, className, style,\n width = 300,\n height = 56,\n radius = 60,\n edgeSize = 30,\n intensity = 0.7,\n specularWidth = 0.02,\n displacement = 55,\n blur = 1,\n saturation = 150,\n brightness = 1.1,\n glassColor = \"transparent\",\n hoverScale = 1.08,\n hoverDisplacement = 125,\n hoverBlur = 4,\n hoverDuration = 0.25,\n disableAnimation = false,\n quality = 2,\n ...props\n }, ref) {\n const internalRef = useRef<HTMLButtonElement>(null);\n const buttonRef = (ref as React.RefObject<HTMLButtonElement>) ?? internalRef;\n\n const displacerRef = useRef<SVGFEDisplacementMapElement>(null);\n const blurRef = useRef<SVGFEGaussianBlurElement>(null);\n\n const filterId = \"lg\" + useId().replace(/:/g, \"\");\n\n const mapOpts = { width, height, radius, edgeSize, intensity, specularWidth, quality };\n const [maps, setMaps] = useState<{ displacement: string; specular: string } | null>(\n () => getCachedGlassMaps(mapOpts),\n );\n\n useEffect(() => {\n // If already have maps from cache, skip\n const cached = getCachedGlassMaps(mapOpts);\n if (cached) {\n setMaps(cached);\n return;\n }\n\n let cancelled = false;\n generateGlassMaps(mapOpts).then((m) => {\n if (!cancelled) setMaps(m);\n });\n\n return () => { cancelled = true; };\n }, [width, height, radius, edgeSize, intensity, specularWidth, quality]);\n\n useEffect(() => {\n return () => {\n if (maps) revokeGlassMaps(maps);\n };\n }, [maps]);\n\n useEffect(() => {\n const button = buttonRef.current;\n const displacer = displacerRef.current;\n const blurEl = blurRef.current;\n if (!button || !displacer || !blurEl || disableAnimation) return;\n\n if (window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches) return;\n\n const fx = {\n displacement: displacement,\n blur: blur,\n };\n\n const sync = () => {\n displacer.setAttribute(\"scale\", fx.displacement.toString());\n blurEl.setAttribute(\"stdDeviation\", fx.blur.toString());\n };\n\n sync();\n\n const onEnter = () => {\n gsap.killTweensOf([fx, button]);\n gsap.to(fx, {\n displacement: hoverDisplacement,\n blur: hoverBlur,\n duration: hoverDuration,\n ease: \"back.out(1.4)\",\n onUpdate: sync,\n });\n gsap.to(button, {\n scale: hoverScale,\n duration: hoverDuration,\n ease: \"back.out(1.4)\",\n });\n };\n\n const onLeave = () => {\n gsap.killTweensOf([fx, button]);\n gsap.to(fx, {\n displacement: displacement,\n blur: blur,\n duration: hoverDuration,\n ease: \"power2.out\",\n onUpdate: sync,\n });\n gsap.to(button, {\n scale: 1,\n duration: hoverDuration,\n ease: \"power2.out\",\n });\n };\n\n const onClick = () => {\n gsap.killTweensOf(button);\n const cur = gsap.getProperty(button, \"scale\") as number;\n gsap.timeline()\n .to(button, { scale: cur * 0.92, duration: 0.08, ease: \"power2.in\" })\n .to(button, { scale: hoverScale, duration: 0.25, ease: \"back.out(2)\" });\n };\n\n button.addEventListener(\"pointerenter\", onEnter);\n button.addEventListener(\"pointerleave\", onLeave);\n button.addEventListener(\"click\", onClick);\n\n return () => {\n button.removeEventListener(\"pointerenter\", onEnter);\n button.removeEventListener(\"pointerleave\", onLeave);\n button.removeEventListener(\"click\", onClick);\n gsap.killTweensOf([button, fx]);\n };\n }, [buttonRef, maps, displacement, blur, hoverScale, hoverDisplacement, hoverBlur, hoverDuration, disableAnimation]);\n\n if (!maps) return null;\n\n return (\n <>\n <button\n ref={buttonRef}\n className={cn(\"relative overflow-hidden shadow-lg cursor-pointer\", className)}\n style={{ width, height, borderRadius: radius, border: \"none\", background: glassColor, ...style }}\n {...props}\n >\n <div\n className=\"absolute inset-0\"\n style={{\n backdropFilter: `url(#${filterId}) brightness(${brightness * 100}%)`,\n WebkitBackdropFilter: `url(#${filterId}) brightness(${brightness * 100}%)`,\n borderRadius: \"inherit\",\n willChange: \"backdrop-filter\",\n transform: \"translateZ(0)\",\n }}\n />\n <div\n className=\"absolute inset-0 inline-flex items-center justify-center font-bold text-white\"\n style={{ background: \"hsl(0 100% 100% / 15%)\", borderRadius: \"inherit\" }}\n >\n {children}\n </div>\n </button>\n\n <svg\n colorInterpolationFilters=\"sRGB\"\n style={{ position: \"absolute\", width: 0, height: 0, overflow: \"hidden\" }}\n aria-hidden=\"true\"\n >\n <defs>\n <filter id={filterId}>\n <feGaussianBlur\n ref={blurRef}\n in=\"SourceGraphic\"\n stdDeviation={blur}\n result=\"blurred_source\"\n />\n <feImage\n href={maps.displacement}\n x=\"0\"\n y=\"0\"\n width={width}\n height={height}\n result=\"displacement_map\"\n />\n <feDisplacementMap\n ref={displacerRef}\n in=\"blurred_source\"\n in2=\"displacement_map\"\n scale={displacement}\n xChannelSelector=\"R\"\n yChannelSelector=\"G\"\n result=\"displaced\"\n />\n <feColorMatrix\n in=\"displaced\"\n type=\"saturate\"\n result=\"displaced_saturated\"\n values={saturation.toString()}\n />\n <feImage\n href={maps.specular}\n x=\"0\"\n y=\"0\"\n width={width}\n height={height}\n result=\"specular_layer\"\n />\n <feGaussianBlur\n in=\"specular_layer\"\n stdDeviation=\"1\"\n result=\"blurred_specular_layer\"\n />\n <feComposite\n in=\"displaced_saturated\"\n in2=\"blurred_specular_layer\"\n operator=\"in\"\n result=\"final_specular_layer\"\n />\n <feBlend\n in=\"final_specular_layer\"\n in2=\"displaced\"\n mode=\"normal\"\n />\n </filter>\n </defs>\n </svg>\n </>\n );\n }\n);\n","export function cn(...classes: (string | undefined | null | false)[]) {\n return classes.filter(Boolean).join(\" \");\n}\n","export interface MapOptions {\n width: number;\n height: number;\n radius?: number;\n edgeSize?: number;\n intensity?: number;\n specularWidth?: number;\n /** Supersampling multiplier for the displacement map (default: 2). Higher = smoother gradients. */\n quality?: number;\n}\n\nconst VERT = `attribute vec4 position; void main(){ gl_Position = position; }`;\n\nconst FRAG = `\nprecision highp float;\nuniform vec2 uRes;\nuniform float uRadius;\nuniform float uBorderSoftness;\nuniform float uSpecularWidth;\nuniform int uMode; // 0 = displacement, 1 = specular\n\nfloat sdRoundedBox(vec2 p, vec2 b, float r){\n r = min(r, min(b.x, b.y));\n vec2 q = abs(p) - b + r;\n return min(max(q.x, q.y), 0.0) + length(max(q, 0.0)) - r;\n}\n\nvec3 calcNormal(vec2 p, vec2 b, float r){\n float e = max(0.5, min(b.x, b.y) * 0.01);\n vec2 h = vec2(e, 0.0);\n return normalize(vec3(\n sdRoundedBox(p+h.xy, b, r) - sdRoundedBox(p-h.xy, b, r),\n sdRoundedBox(p+h.yx, b, r) - sdRoundedBox(p-h.yx, b, r),\n -e * 2.0\n ));\n}\n\nvoid main(){\n vec2 p = gl_FragCoord.xy - uRes * 0.5;\n vec2 halfSize = uRes * 0.5 - 1.0;\n float d = sdRoundedBox(p, halfSize, uRadius);\n\n if(d > 0.0){ gl_FragColor = vec4(0.0); return; }\n\n if(uMode == 0){\n vec3 n = calcNormal(p, halfSize, uRadius);\n vec3 nc = n * 0.5 + 0.5;\n float border = smoothstep(-uBorderSoftness, 0.0, d);\n vec3 flat_ = vec3(0.5, 0.5, 1.0);\n gl_FragColor = vec4(mix(flat_, nc, border), 1.0);\n } else {\n float rim = smoothstep(-uSpecularWidth - 2.0, -uSpecularWidth, d)\n * (1.0 - smoothstep(-2.0, 0.0, d));\n float glow = smoothstep(-uBorderSoftness, 0.0, d) * 0.1;\n float s = clamp(rim + glow, 0.0, 1.0);\n gl_FragColor = vec4(vec3(s), s);\n }\n}\n`;\n\nfunction compile(gl: WebGLRenderingContext, type: number, src: string) {\n const s = gl.createShader(type)!;\n gl.shaderSource(s, src);\n gl.compileShader(s);\n return s;\n}\n\nlet _cachedProgram: { gl: WebGLRenderingContext; program: WebGLProgram; canvas: HTMLCanvasElement } | null = null;\n\nfunction getGL() {\n if (_cachedProgram) return _cachedProgram;\n\n const canvas = document.createElement(\"canvas\");\n const gl = canvas.getContext(\"webgl\", { preserveDrawingBuffer: true, premultipliedAlpha: false })!;\n\n const vs = compile(gl, gl.VERTEX_SHADER, VERT);\n const fs = compile(gl, gl.FRAGMENT_SHADER, FRAG);\n const program = gl.createProgram()!;\n gl.attachShader(program, vs);\n gl.attachShader(program, fs);\n gl.linkProgram(program);\n gl.useProgram(program);\n\n const buf = gl.createBuffer();\n gl.bindBuffer(gl.ARRAY_BUFFER, buf);\n gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, -1, 1, -1, -1, 1, 1, 1]), gl.STATIC_DRAW);\n const pos = gl.getAttribLocation(program, \"position\");\n gl.enableVertexAttribArray(pos);\n gl.vertexAttribPointer(pos, 2, gl.FLOAT, false, 0, 0);\n\n _cachedProgram = { gl, program, canvas };\n return _cachedProgram;\n}\n\n// ── Cache ──────────────────────────────────────────────────────\n\nconst _mapCache = new Map<string, { displacement: string; specular: string }>();\n\nfunction cacheKey(\n w: number, h: number, r: number, bs: number, sw: number,\n): string {\n return `${w}|${h}|${r}|${bs}|${sw}`;\n}\n\n// ── Render ─────────────────────────────────────────────────────\n\nfunction renderToBlob(\n width: number,\n height: number,\n radius: number,\n borderSoftness: number,\n specularWidth: number,\n mode: number,\n): Promise<string> {\n const { gl, program, canvas } = getGL();\n canvas.width = width;\n canvas.height = height;\n gl.viewport(0, 0, width, height);\n gl.clearColor(0, 0, 0, 0);\n gl.clear(gl.COLOR_BUFFER_BIT);\n gl.useProgram(program);\n\n gl.uniform2f(gl.getUniformLocation(program, \"uRes\"), width, height);\n gl.uniform1f(gl.getUniformLocation(program, \"uRadius\"), radius);\n gl.uniform1f(gl.getUniformLocation(program, \"uBorderSoftness\"), borderSoftness);\n gl.uniform1f(gl.getUniformLocation(program, \"uSpecularWidth\"), specularWidth);\n gl.uniform1i(gl.getUniformLocation(program, \"uMode\"), mode);\n\n gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);\n\n return new Promise((resolve) => {\n canvas.toBlob((blob) => {\n resolve(URL.createObjectURL(blob!));\n }, \"image/png\");\n });\n}\n\n// ── Public API ─────────────────────────────────────────────────\n\nexport async function generateGlassMaps(opts: MapOptions): Promise<{\n displacement: string;\n specular: string;\n}> {\n const {\n width,\n height,\n radius = 60,\n edgeSize = 30,\n intensity = 0.7,\n specularWidth = 0.02,\n quality = 2,\n } = opts;\n\n const scale = Math.max(1, Math.round(quality));\n const rw = width * scale;\n const rh = height * scale;\n\n const r = Math.min(radius * scale, rw / 2, rh / 2);\n const borderSoftness = edgeSize * intensity * scale;\n const specPx = specularWidth * Math.min(rw, rh);\n\n const key = cacheKey(rw, rh, r, borderSoftness, specPx);\n const cached = _mapCache.get(key);\n if (cached) return cached;\n\n const [displacement, specular] = await Promise.all([\n renderToBlob(rw, rh, r, borderSoftness, specPx, 0),\n renderToBlob(rw, rh, r, borderSoftness, specPx, 1),\n ]);\n\n const result = { displacement, specular };\n _mapCache.set(key, result);\n return result;\n}\n\nexport function getCachedGlassMaps(opts: MapOptions): { displacement: string; specular: string } | null {\n const {\n width,\n height,\n radius = 60,\n edgeSize = 30,\n intensity = 0.7,\n specularWidth = 0.02,\n quality = 2,\n } = opts;\n\n const scale = Math.max(1, Math.round(quality));\n const rw = width * scale;\n const rh = height * scale;\n\n const r = Math.min(radius * scale, rw / 2, rh / 2);\n const borderSoftness = edgeSize * intensity * scale;\n const specPx = specularWidth * Math.min(rw, rh);\n\n return _mapCache.get(cacheKey(rw, rh, r, borderSoftness, specPx)) ?? null;\n}\n\nexport function revokeGlassMaps(maps: { displacement: string; specular: string }) {\n URL.revokeObjectURL(maps.displacement);\n URL.revokeObjectURL(maps.specular);\n}\n"],"mappings":"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","names":["LiquidGlassButton"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@marcosdemik/liquidglass",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.4",
|
|
4
4
|
"description": "Liquid Glass UI effect for React - glassmorphism refraction with SVG filters, WebGL and GSAP animations",
|
|
5
5
|
"author": "Marcos Demik <marcosdemik>",
|
|
6
6
|
"license": "MIT",
|