@avenra/liquid-glass

中文文档
1.2.0发布于 2026/06/15MITTypeScriptESM + CJS零依赖近 30 天下载 6127

Physics-based liquid glass UI effect — Snell's law SVG displacement maps, spring animations, zero runtime dependencies.

原始使用文档
文档目录27 节
  1. Install
  2. Quick start
  3. npm / bundler
  4. CDN / script tag
  5. Zero-JS — data attributes
  6. Theming — CSS custom properties
  7. API Reference
  8. Common options (all components inherit these)
  9. Common handle API (every factory returns this)
  10. createLiquidGlass(target, options?)
  11. createLiquidButton(target, options?)
  12. createLiquidSwitch(target, options?)
  13. createLiquidSlider(target, options?)
  14. createLiquidCursor(container, options?)
  15. createLiquidInput(target, options?)
  16. createLiquidDial(target, options?)
  17. createLiquidTooltip(target, options)
  18. createLiquidProgress(target, options?)
  19. init(options?)
  20. TypeScript
  21. Advanced — low-level API
  22. Browser compatibility
  23. Build from source
  24. License
  25. Support
  26. Contributing
  27. Security

@avenra/liquid-glass

CI CodeQL npm version License: MIT

Physics-based liquid glass UI effect for the web. Snell's law refraction baked into an SVG displacement map, specular rim light, spring animations — zero runtime dependencies.

Browser note: The SVG displacement effect requires backdrop-filter: url() which is Chromium-only. All other browsers receive a CSS blur + saturate fallback that still looks like frosted glass.


Install

npm install @avenra/liquid-glass
<!-- CDN (production) -->
<script src="https://cdn.jsdelivr.net/npm/@avenra/liquid-glass/dist/liquid-glass.umd.min.js"></script>
<link
  rel="stylesheet"
  href="https://cdn.jsdelivr.net/npm/@avenra/liquid-glass/styles/liquid-glass.css"
/>

Quick start

npm / bundler

import { createLiquidButton, createLiquidSwitch, createLiquidSlider } from '@avenra/liquid-glass';
import '@avenra/liquid-glass/styles';

const btn = createLiquidButton('#myBtn', { label: 'Save' });
btn.on('click', (e: MouseEvent) => console.log('clicked'));

const sw = createLiquidSwitch('#toggle', { checked: false });
sw.on('change', ({ checked }) => console.log('switch:', checked));

const sl = createLiquidSlider('#slider', { min: 0, max: 100, value: 50 });
sl.on('input', ({ value }) => updateVolume(value));
sl.on('change', ({ value }) => savePreference(value)); // fires on release

CDN / script tag

<script src="https://cdn.jsdelivr.net/npm/@avenra/liquid-glass/dist/liquid-glass.umd.min.js"></script>
<link
  rel="stylesheet"
  href="https://cdn.jsdelivr.net/npm/@avenra/liquid-glass/styles/liquid-glass.css"
/>
<button id="btn"></button>
<script>
  const { createLiquidButton } = LiquidGlass;
  createLiquidButton('#btn', { label: 'Click me' }).on('click', () => alert('hello'));
</script>

Zero-JS — data attributes

<button data-liquid-button data-label="Subscribe"></button>
<div data-liquid-switch data-checked="true"></div>
<div data-liquid-slider data-min="0" data-max="100" data-value="50"></div>
<div data-liquid-glass></div>
import { init } from '@avenra/liquid-glass';
const handles = init(); // scans document and applies everything

Theming — CSS custom properties

Override these in your stylesheet to skin every glass element without touching JS:

:root {
  --lg-tint: rgba(255, 255, 255, 0.05); /* glass body fill           */
  --lg-border: rgba(255, 255, 255, 0.2); /* 1px edge border           */
  --lg-shadow-out: 0 10px 30px rgba(0, 0, 0, 0.3); /* drop shadow               */
  --lg-shadow-hi: inset 0 2px 5px rgba(255, 255, 255, 0.25); /* top highlight */
  --lg-shadow-lo: inset 0 -2px 5px rgba(0, 0, 0, 0.15); /* bottom shade  */
  --lg-accent: #3b82f6; /* slider fill / progress fill gradient start  */
  --lg-accent-2: #60a5fa; /* gradient end                                */
}

API Reference

Common options (all components inherit these)

OptionTypeDefaultDescription
bezelWidthnumber20Refractive bezel rim width in px
glassThicknessnumber80Virtual glass depth (larger = more lateral ray drift)
refractiveIndexnumber1.5Index of refraction n₂ (1.0 = no bend, 2.0 = strong)
profile'convexSquircle' | 'convexCircle' | 'concave' | fn'convexSquircle'Bezel surface shape
blurnumber0.5Pre-displacement Gaussian blur stdDeviation
saturationnumber1.3Post-displacement colour saturation multiplier
specularSlopenumber0.8Specular highlight intensity 0–1
filterMode'screen' | 'composite''screen'SVG filter blend pipeline
width / heightnumberautoOverride element dimensions for map generation
radiusnumber | nullautoCorner radius override (null = read from CSS)

Common handle API (every factory returns this)

handle.on(event, fn); // subscribe — returns handle (chainable)
handle.off(event, fn); // unsubscribe a specific listener
handle.once(event, fn); // subscribe once — auto-removed after first fire
handle.destroy(); // clean up all injected DOM and stop animations

All events include a destroy event fired when destroy() is called.


createLiquidGlass(target, options?)

Apply the glass effect to any element.

const glass = createLiquidGlass('#myCard', {
  bezelWidth: 24,
  glassThickness: 120,
  refractiveIndex: 2.0,
});

glass.on('resize', ({ width, height }) => console.log(width, height));
glass.refresh(); // rebuild maps (auto-called on resize)
glass.destroy();
EventPayload
resize{ width: number; height: number }
destroy—

createLiquidButton(target, options?)

const btn = createLiquidButton('#myBtn', { label: 'Save Changes' });
btn.on('click', (e: MouseEvent) => handleSave(e));
btn.setLabel('Saved ✓');
btn.destroy();
OptionTypeDescription
labelstringButton text
EventPayload
clickMouseEvent
mouseenter / mouseleaveMouseEvent
mousedown / mouseupMouseEvent
destroy—

createLiquidSwitch(target, options?)

const sw = createLiquidSwitch('#toggle', { checked: true });
sw.on('change', ({ checked }) => applyTheme(checked));
sw.checked = false; // programmatic setter
sw.toggle();
sw.destroy();
OptionTypeDefault
checkedbooleanfalse
EventPayload
change{ checked: boolean; element: Element }
destroy—

createLiquidSlider(target, options?)

const sl = createLiquidSlider('#slider', { min: 0, max: 100, value: 50, step: 5 });
sl.on('input', ({ value }) => updateUI(value)); // while dragging
sl.on('change', ({ value }) => saveToServer(value)); // on release
sl.value = 75; // programmatic setter
sl.destroy();
OptionTypeDefault
min / maxnumber0 / 100
valuenumber50
stepnumber1
EventPayload
input{ value: number; element: Element }
change{ value: number; element: Element }
destroy—

createLiquidCursor(container, options?)

Glass orb that replaces the system cursor inside container. The orb grows on hover, shrinks on click.

const cursor = createLiquidCursor('#stage', { size: 90 });
cursor.destroy(); // restores the default cursor
OptionTypeDefault
sizenumber90

createLiquidInput(target, options?)

Glass-wrapped <input> with micro-vibration on each keystroke and scale spring on focus.

const inp = createLiquidInput('#searchBox', {
  placeholder: 'Search…',
  type: 'text',
});
inp.on('input', ({ value }) => runSearch(value));
inp.on('focus', () => showSuggestions());
inp.value = 'prefilled text'; // getter/setter
inp.focus();
inp.destroy();
OptionTypeDefault
placeholderstring''
valuestring''
typestring'text'
EventPayload
focusFocusEvent
blurFocusEvent
input{ value: string; element: Element }
destroy—

createLiquidDial(target, options?)

Rotary knob — pointer drag spins it continuously.

const dial = createLiquidDial('#knob', { value: 0 });
dial.on('change', ({ angle }) => setFilter(angle));
dial.angle = 90; // programmatic setter (degrees)
dial.destroy();
OptionTypeDefault
valuenumber (degrees)0
EventPayload
change{ angle: number; element: Element }
destroy—

createLiquidTooltip(target, options)

Glass pill that springs above a trigger element on hover/focus.

// Assuming #myTooltip is positioned relative to its parent:
const tip = createLiquidTooltip('#myTooltip', {
  trigger: '#triggerBtn',
  text: 'More details',
});
tip.on('show', () => {});
tip.show(); // programmatic show
tip.hide(); // programmatic hide
tip.toggle();
tip.setText('Updated text');
tip.destroy();
OptionTypeRequired
triggerstring | Element✓
textstring—
EventPayload
show / hide—
destroy—

createLiquidProgress(target, options?)

Fluid progress bar with squish animation on value update.

const bar = createLiquidProgress('#progressBar', { value: 0 });
bar.on('change', ({ value }) => console.log(value + '%'));
bar.value = 65; // triggers spring + squish
bar.destroy();
OptionTypeDefault
valuenumber 0–1000
EventPayload
change{ value: number; element: Element }
destroy—

init(options?)

Scan the DOM for data-liquid-* attributes and apply the matching factory automatically.

import { init } from '@avenra/liquid-glass';

const handles = init(); // whole document
const handles = init({ root: '#myApp' }); // limit to subtree

Supported data attributes:

AttributeOptions via data-*
data-liquid-buttondata-label
data-liquid-switchdata-checked
data-liquid-sliderdata-min, data-max, data-value, data-step
data-liquid-glassdata-bezel-width, data-refractive-index, data-profile, etc.

Returns AnyHandle[] — one handle per matched element.


TypeScript

All types are exported from the package root:

import type {
  LiquidGlassHandle,
  LiquidGlassOptions,
  LiquidGlassEventMap,
  LiquidButtonHandle,
  LiquidButtonOptions,
  LiquidButtonEventMap,
  LiquidSwitchHandle,
  LiquidSwitchOptions,
  LiquidSwitchEventMap,
  LiquidSliderHandle,
  LiquidSliderOptions,
  LiquidSliderEventMap,
  LiquidCursorHandle,
  LiquidCursorOptions,
  LiquidInputHandle,
  LiquidInputOptions,
  LiquidInputEventMap,
  LiquidDialHandle,
  LiquidDialOptions,
  LiquidDialEventMap,
  LiquidTooltipHandle,
  LiquidTooltipOptions,
  LiquidTooltipEventMap,
  LiquidProgressHandle,
  LiquidProgressOptions,
  LiquidProgressEventMap,
  AnyHandle,
} from '@avenra/liquid-glass';

Advanced — low-level API

Build a fully custom glass component using the raw physics engine:

import {
  Spring,
  PROFILES,
  buildMaps,
  createFilterSVG,
  injectImages,
  setScale,
  nextFilterId,
  supportsBackdropFilter,
} from '@avenra/liquid-glass';

const { dispUrl, specUrl, maxDisplacement } = buildMaps({
  width: 200,
  height: 140,
  radius: 70,
  bezelWidth: 30,
  glassThickness: 150,
  refractiveIndex: 1.5,
});

const filterId = nextFilterId('my-lens');
const { svg, refs } = createFilterSVG({ filterId, width: 200, height: 140 });
injectImages(refs, dispUrl, specUrl);
myElement.appendChild(svg);

if (supportsBackdropFilter()) {
  innerEl.style.backdropFilter = `url("#${filterId}")`;
}

// Animation loop — spring the displacement scale
const spring = new Spring(maxDisplacement, 300, 20);
spring.setTarget(maxDisplacement * 1.5);
requestAnimationFrame(function loop() {
  setScale(refs, spring.update(1 / 60));
  if (!spring.isSettled()) requestAnimationFrame(loop);
});

Browser compatibility

FeatureChromeEdgeFirefoxSafari
SVG displacement + specular refraction✓✓——
CSS blur/saturate fallback✓✓✓✓

Build from source

npm install
npm run build    # outputs dist/
npm test         # build + run physics unit tests

License

MIT


Support

Need help? Please see our Support Guide.

Contributing

Contributions are welcome! Please read our Contributing Guide and Code of Conduct before submitting a pull request.

Security

If you discover a security vulnerability, please see our Security Policy.

版本与发布记录

@avenra/liquid-glass 共发布 3 个版本,首次发布于 2026/06/05;过去 12 个月发布了 3 个版本。

版本发布日期距今
1.2.0latest2026/06/154 个月前
1.1.02026/06/075 个月前
1.0.02026/06/055 个月前

关于 @avenra/liquid-glass 的常见问题

@avenra/liquid-glass 是什么?

Physics-based liquid glass UI effect — Snell's law SVG displacement maps, spring animations, zero runtime dependencies.

如何安装 @avenra/liquid-glass?

在项目目录运行 npm install @avenra/liquid-glass,或使用 pnpm add / yarn add / bun add。

@avenra/liquid-glass 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@avenra/liquid-glass 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@avenra/liquid-glass 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=18.0.0。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@avenra/liquid-glass 可以商用吗?

@avenra/liquid-glass 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@avenra/liquid-glass 的下载量有多少?

根据 npm 官方下载统计,@avenra/liquid-glass 在 2026/10/07 – 2026/11/05 这 30 天内被下载 6,127 次,日均约 204 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@avenra/liquid-glass 还在维护吗?

最新版本 1.2.0 发布于 2026/06/15(4 个月前);至今共发布 3 个版本,其中过去 12 个月发布了 3 个;首次发布于 2026/06/05。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。