liqvued 0.2.0 → 0.2.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.
- package/README.md +9 -2
- package/dist/liqvued.css +1 -1
- package/dist/liqvued.mjs +169 -143
- package/dist/liqvued.umd.js +1 -1
- package/index.d.ts +43 -0
- package/package.json +17 -17
- package/src/Liqvued.vue +18 -8
- package/src/refraction.test.ts +36 -0
- package/src/refraction.ts +50 -0
- package/src/types.ts +2 -0
package/README.md
CHANGED
|
@@ -60,12 +60,13 @@ import { Liqvued } from 'liqvued'
|
|
|
60
60
|
| bezel | number | 22 | Width of the displacement bezel in pixels |
|
|
61
61
|
| thickness | number | 42 | Maximum displacement magnitude in pixels |
|
|
62
62
|
| refraction | number | 1 | Refraction index multiplier |
|
|
63
|
+
| refractionMode | string | `'edge'` | Refraction direction mode: `edge`, `center`, or `split` |
|
|
63
64
|
| magnification | number | 0 | Center magnification amount. Positive values enlarge, negative values shrink |
|
|
64
65
|
| magnificationFocus | number | 0.82 | Focus radius of the magnification area. Lower values concentrate the effect near center |
|
|
65
66
|
| blur | number | 0.4 | CSS backdrop-filter blur amount |
|
|
66
67
|
| surface | string | `'convex'` | Glass surface profile shape |
|
|
67
68
|
| specularOpacity | number | 0.45 | Opacity of the specular highlight |
|
|
68
|
-
| glareAngle | number
|
|
69
|
+
| glareAngle | number \| false | -60 | Light source angle in degrees for specular highlight. Set `false` to disable |
|
|
69
70
|
| glassBackground | string | — | Glass panel background color (auto-derived from `asProps.color` when available) |
|
|
70
71
|
| fallbackOnly | boolean | `false` | When `true`, disables the SVG displacement effect and uses only CSS blur |
|
|
71
72
|
| as | string \| Component | `'div'` | HTML tag or Vue component to render as (rendered as a child of the glass root) |
|
|
@@ -83,6 +84,12 @@ import { Liqvued } from 'liqvued'
|
|
|
83
84
|
- `noise` — irregular organic edge distortion
|
|
84
85
|
- `asymmetric` — biased profile with more weight toward one side of the edge band
|
|
85
86
|
|
|
87
|
+
### Refraction Modes
|
|
88
|
+
|
|
89
|
+
- `edge` — original behavior; displacement follows the rounded edge normal
|
|
90
|
+
- `center` — displacement points inward from each edge toward the shape centerline
|
|
91
|
+
- `split` — outer and inner bezel halves bend in opposite directions with a neutral midpoint
|
|
92
|
+
|
|
86
93
|
## Dynamic Element Rendering
|
|
87
94
|
|
|
88
95
|
Use the `as` prop to render the glass effect on any element or component:
|
|
@@ -106,7 +113,7 @@ The fallback applies `backdrop-filter: blur(12px)` with a subtle border and shad
|
|
|
106
113
|
|
|
107
114
|
## How It Works
|
|
108
115
|
|
|
109
|
-
The component generates a per-pixel displacement map as an RGBA PNG (red/green channels encode X/Y displacement vectors), injects it into an SVG `<feDisplacementMap>` filter, and applies it via `backdrop-filter` combined with CSS blur. The surface profile functions define the edge refraction
|
|
116
|
+
The component generates a per-pixel displacement map as an RGBA PNG (red/green channels encode X/Y displacement vectors), injects it into an SVG `<feDisplacementMap>` filter, and applies it via `backdrop-filter` combined with CSS blur. The surface profile functions define the edge refraction strength along the bezel, while `refractionMode` controls the displacement direction. `magnification` and `magnificationFocus` control an optional center lens effect. A separate specular highlight map is generated and composited via `feBlend` to simulate surface lighting.
|
|
110
117
|
|
|
111
118
|
## License
|
|
112
119
|
|
package/dist/liqvued.css
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
.liquid-glass[data-v-
|
|
1
|
+
.liquid-glass[data-v-5607a45c]{isolation:isolate;position:relative;overflow:hidden}.liquid-glass__svg[data-v-5607a45c]{pointer-events:none;width:100%;height:100%;position:absolute;inset:0}@supports not ((-webkit-backdrop-filter:url("#x")) or (backdrop-filter:url("#x"))){.liquid-glass[data-v-5607a45c]{-webkit-backdrop-filter:blur(12px);backdrop-filter:blur(12px)}}
|
|
2
2
|
/*$vite$:1*/
|
package/dist/liqvued.mjs
CHANGED
|
@@ -1,56 +1,78 @@
|
|
|
1
|
-
import { computed as C, createBlock as he, createCommentVNode as
|
|
2
|
-
function
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { computed as C, createBlock as he, createCommentVNode as pe, createElementBlock as ee, createElementVNode as _, defineComponent as ve, guardReactiveProps as ge, inject as me, nextTick as te, normalizeProps as Me, normalizeStyle as ye, onBeforeUnmount as be, onMounted as xe, openBlock as N, provide as _e, ref as k, renderSlot as ke, resolveDynamicComponent as we, unref as Se, watch as Be, withCtx as Ie } from "vue";
|
|
2
|
+
function Re(n, t, i, r, c, y, g) {
|
|
3
|
+
if (n === "center") return t;
|
|
4
|
+
const h = {
|
|
5
|
+
x: -t.x,
|
|
6
|
+
y: -t.y
|
|
7
|
+
};
|
|
8
|
+
if (n === "split") {
|
|
9
|
+
const o = g < 0.5 ? -1 : 1;
|
|
10
|
+
return {
|
|
11
|
+
x: h.x * o,
|
|
12
|
+
y: h.y * o
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
return h;
|
|
16
|
+
}
|
|
17
|
+
function $e(n, t) {
|
|
18
|
+
return n !== "split" ? 1 : Math.min(1, Math.abs(t - 0.5) * 2);
|
|
19
|
+
}
|
|
20
|
+
function q(n, t, i, r, c) {
|
|
21
|
+
const y = Math.min(c, Math.min(i, r) / 2), g = i / 2 - y, h = r / 2 - y, o = Math.abs(n - i / 2) - g, I = Math.abs(t - r / 2) - h;
|
|
22
|
+
return -(Math.min(Math.max(o, I), 0) + Math.hypot(Math.max(o, 0), Math.max(I, 0)) - y);
|
|
5
23
|
}
|
|
6
|
-
function
|
|
7
|
-
const
|
|
8
|
-
return
|
|
24
|
+
function Ce(n, t, i, r, c) {
|
|
25
|
+
const g = q(n - 1.5, t, i, r, c) - q(n + 1.5, t, i, r, c), h = q(n, t - 1.5, i, r, c) - q(n, t + 1.5, i, r, c), o = Math.hypot(g, h);
|
|
26
|
+
return o < 1e-10 ? {
|
|
9
27
|
x: 0,
|
|
10
28
|
y: -1
|
|
11
29
|
} : {
|
|
12
|
-
x:
|
|
13
|
-
y:
|
|
30
|
+
x: g / o,
|
|
31
|
+
y: h / o
|
|
14
32
|
};
|
|
15
33
|
}
|
|
16
|
-
var
|
|
34
|
+
var qe = {
|
|
17
35
|
key: 0,
|
|
18
36
|
class: "liquid-glass__svg",
|
|
19
37
|
"aria-hidden": "true"
|
|
20
|
-
},
|
|
38
|
+
}, Pe = [
|
|
21
39
|
"href",
|
|
22
40
|
"width",
|
|
23
41
|
"height"
|
|
24
|
-
],
|
|
42
|
+
], Fe = ["scale"], Oe = [
|
|
25
43
|
"href",
|
|
26
44
|
"width",
|
|
27
45
|
"height"
|
|
28
|
-
],
|
|
46
|
+
], De = /* @__PURE__ */ ve({
|
|
29
47
|
__name: "Liqvued",
|
|
30
48
|
props: {
|
|
31
49
|
as: { default: "div" },
|
|
32
50
|
asProps: { default: () => ({}) },
|
|
33
51
|
radius: { default: 32 },
|
|
34
|
-
borderRadius: {},
|
|
52
|
+
borderRadius: { default: void 0 },
|
|
35
53
|
bezel: { default: 22 },
|
|
36
54
|
thickness: { default: 42 },
|
|
37
55
|
refraction: { default: 1 },
|
|
56
|
+
refractionMode: { default: "edge" },
|
|
38
57
|
magnification: { default: 0 },
|
|
39
58
|
magnificationFocus: { default: 0.82 },
|
|
40
59
|
blur: { default: 0.4 },
|
|
41
60
|
surface: { default: "convex" },
|
|
42
61
|
specularOpacity: { default: 0.45 },
|
|
43
|
-
glareAngle: {
|
|
62
|
+
glareAngle: {
|
|
63
|
+
type: [Number, Boolean],
|
|
64
|
+
default: -60
|
|
65
|
+
},
|
|
44
66
|
glassBackground: { default: void 0 },
|
|
45
67
|
fallbackOnly: {
|
|
46
68
|
type: Boolean,
|
|
47
69
|
default: !1
|
|
48
70
|
}
|
|
49
71
|
},
|
|
50
|
-
setup(
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
const
|
|
72
|
+
setup(n) {
|
|
73
|
+
const t = n, i = k(null), r = k(1), c = k(1), y = k(""), g = k(""), h = k(1), o = typeof CSS < "u" && typeof navigator < "u" && CSS.supports?.("backdrop-filter", "url(#x)") && /Chrome/.test(navigator.userAgent), I = `${`lg-${Math.random().toString(36).slice(2)}`}-filter`, W = me("_liqvued", !1);
|
|
74
|
+
_e("_liqvued", !0);
|
|
75
|
+
const w = k(!1), V = {
|
|
54
76
|
0: 0,
|
|
55
77
|
sm: 4,
|
|
56
78
|
md: 8,
|
|
@@ -61,106 +83,109 @@ var Be = {
|
|
|
61
83
|
shaped: 16
|
|
62
84
|
};
|
|
63
85
|
function j() {
|
|
64
|
-
return
|
|
86
|
+
return i.value;
|
|
65
87
|
}
|
|
66
|
-
function
|
|
67
|
-
return Math.max(
|
|
88
|
+
function u(e, a = 0, s = 1) {
|
|
89
|
+
return Math.max(a, Math.min(s, e));
|
|
68
90
|
}
|
|
69
|
-
function
|
|
70
|
-
return e =
|
|
91
|
+
function R(e) {
|
|
92
|
+
return e = u(e), e * e * e * (e * (e * 6 - 15) + 10);
|
|
71
93
|
}
|
|
72
94
|
function P(e) {
|
|
73
|
-
return Math.pow(1 - Math.pow(1 -
|
|
95
|
+
return Math.pow(1 - Math.pow(1 - u(e), 4), 1 / 4);
|
|
74
96
|
}
|
|
75
|
-
function
|
|
76
|
-
return
|
|
97
|
+
function ae(e, a, s) {
|
|
98
|
+
return u(e + Math.sin(u(e) * Math.PI * a) * s);
|
|
77
99
|
}
|
|
78
|
-
function
|
|
79
|
-
const
|
|
80
|
-
return
|
|
100
|
+
function ne(e) {
|
|
101
|
+
const a = u(e);
|
|
102
|
+
return u(a + Math.sin(a * 39.1) * 0.06 + Math.sin(a * 91.7) * 0.035);
|
|
81
103
|
}
|
|
82
|
-
function
|
|
83
|
-
const
|
|
104
|
+
function re(e, a, s, v, b, S) {
|
|
105
|
+
const m = s / 2, M = v / 2, $ = Math.max(1, s / 2), x = Math.max(1, v / 2), D = (e - m) / $, z = (a - M) / x, l = Math.hypot(D, z), d = u(S, 0.2, 1.4), p = Math.pow(R(1 - u(l / d)), 1.6), f = b >= 0 ? 1 + b * p : 1 / (1 + Math.abs(b) * p);
|
|
84
106
|
return {
|
|
85
|
-
x: (e -
|
|
86
|
-
y: (
|
|
107
|
+
x: (e - m) / f - (e - m),
|
|
108
|
+
y: (a - M) / f - (a - M)
|
|
87
109
|
};
|
|
88
110
|
}
|
|
89
111
|
function H(e) {
|
|
90
|
-
const
|
|
91
|
-
switch (
|
|
112
|
+
const a = u(e);
|
|
113
|
+
switch (t.surface) {
|
|
92
114
|
case "concave":
|
|
93
115
|
case "bowl":
|
|
94
|
-
return 1 - P(
|
|
116
|
+
return 1 - P(a);
|
|
95
117
|
case "lip":
|
|
96
|
-
return P(
|
|
118
|
+
return P(a) * (1 - R(a)) + (1 - P(a)) * R(a);
|
|
97
119
|
case "bevel":
|
|
98
|
-
return
|
|
120
|
+
return a;
|
|
99
121
|
case "saddle":
|
|
100
|
-
return 0.5 + Math.sin((
|
|
122
|
+
return 0.5 + Math.sin((a - 0.5) * Math.PI) * 0.5;
|
|
101
123
|
case "ripple":
|
|
102
|
-
return
|
|
124
|
+
return ae(a, 3, 0.12);
|
|
103
125
|
case "noise":
|
|
104
|
-
return
|
|
126
|
+
return ne(a);
|
|
105
127
|
case "asymmetric":
|
|
106
|
-
return
|
|
128
|
+
return u(Math.pow(a, 0.65) * 0.65 + R(a) * 0.35);
|
|
107
129
|
case "convex":
|
|
108
|
-
return P(
|
|
130
|
+
return P(a);
|
|
109
131
|
}
|
|
110
132
|
}
|
|
111
|
-
function
|
|
133
|
+
function oe(e) {
|
|
112
134
|
return (H(e + 1e-3) - H(e - 1e-3)) / (2 * 1e-3);
|
|
113
135
|
}
|
|
114
|
-
function
|
|
115
|
-
const
|
|
116
|
-
return Math.tan(
|
|
136
|
+
function se(e) {
|
|
137
|
+
const v = Math.atan(e), b = 1 / 1.5 * Math.sin(v), S = Math.asin(u(b, -1, 1));
|
|
138
|
+
return Math.tan(v - S);
|
|
117
139
|
}
|
|
118
140
|
function J(e) {
|
|
119
141
|
return e.toDataURL("image/png");
|
|
120
142
|
}
|
|
121
|
-
function
|
|
122
|
-
if (
|
|
123
|
-
const e = Math.max(1, Math.round(
|
|
124
|
-
|
|
125
|
-
const
|
|
126
|
-
|
|
127
|
-
const
|
|
128
|
-
let
|
|
129
|
-
const
|
|
130
|
-
x: Math.cos(
|
|
131
|
-
y: Math.sin(
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
|
|
143
|
+
function A() {
|
|
144
|
+
if (t.fallbackOnly) return;
|
|
145
|
+
const e = Math.max(1, Math.round(r.value)), a = Math.max(1, Math.round(c.value)), s = document.createElement("canvas");
|
|
146
|
+
s.width = e, s.height = a;
|
|
147
|
+
const v = document.createElement("canvas");
|
|
148
|
+
v.width = e, v.height = a;
|
|
149
|
+
const b = s.getContext("2d"), S = v.getContext("2d"), m = b.createImageData(e, a), M = S.createImageData(e, a), $ = [];
|
|
150
|
+
let x = 1;
|
|
151
|
+
const D = t.glareAngle !== !1 ? {
|
|
152
|
+
x: Math.cos(t.glareAngle * Math.PI / 180),
|
|
153
|
+
y: Math.sin(t.glareAngle * Math.PI / 180)
|
|
154
|
+
} : {
|
|
155
|
+
x: 0,
|
|
156
|
+
y: 0
|
|
157
|
+
}, z = Math.max(1, Math.min(t.bezel, Math.min(e, a) * 0.28));
|
|
158
|
+
for (let l = 0; l < a; l++) for (let d = 0; d < e; d++) {
|
|
159
|
+
const p = q(d, l, e, a, F.value), f = u(p / z);
|
|
135
160
|
let L = 0, E = 0, X = 0;
|
|
136
|
-
if (
|
|
137
|
-
const
|
|
138
|
-
L =
|
|
139
|
-
const
|
|
140
|
-
X = Math.pow(
|
|
161
|
+
if (p >= 0 && p < z) {
|
|
162
|
+
const B = Ce(d, l, e, a, F.value), Y = se(oe(f)) * t.thickness * R(1 - f) * $e(t.refractionMode, f) * t.refraction, Z = Re(t.refractionMode, B, d, l, e, a, f);
|
|
163
|
+
L = Z.x * Y, E = Z.y * Y, x = Math.max(x, Math.abs(L), Math.abs(E));
|
|
164
|
+
const fe = u(B.x * D.x + B.y * D.y, 0, 1);
|
|
165
|
+
X = Math.pow(fe, 18) * t.specularOpacity;
|
|
141
166
|
}
|
|
142
|
-
if (
|
|
143
|
-
const
|
|
144
|
-
L +=
|
|
167
|
+
if (t.magnification !== 0 && p >= 0) {
|
|
168
|
+
const B = re(d, l, e, a, t.magnification, t.magnificationFocus);
|
|
169
|
+
L += B.x, E += B.y;
|
|
145
170
|
}
|
|
146
171
|
$.push([L, E]);
|
|
147
|
-
const U = (
|
|
148
|
-
|
|
172
|
+
const U = (l * e + d) * 4, de = Math.round(X * 255);
|
|
173
|
+
M.data[U] = 255, M.data[U + 1] = 255, M.data[U + 2] = 255, M.data[U + 3] = de;
|
|
149
174
|
}
|
|
150
|
-
|
|
151
|
-
const
|
|
152
|
-
|
|
153
|
-
}),
|
|
175
|
+
x = $.reduce((l, [d, p]) => Math.max(l, Math.abs(d), Math.abs(p)), 1), $.forEach(([l, d], p) => {
|
|
176
|
+
const f = p * 4;
|
|
177
|
+
m.data[f] = Math.round(128 + l / x * 127), m.data[f + 1] = Math.round(128 + d / x * 127), m.data[f + 2] = 128, m.data[f + 3] = 255;
|
|
178
|
+
}), b.putImageData(m, 0, 0), S.putImageData(M, 0, 0), y.value = J(s), g.value = J(v), h.value = x;
|
|
154
179
|
}
|
|
155
180
|
function K() {
|
|
156
181
|
const e = j();
|
|
157
|
-
e && (
|
|
182
|
+
e && (r.value = Math.max(1, e.offsetWidth), c.value = Math.max(1, e.offsetHeight));
|
|
158
183
|
}
|
|
159
|
-
const Q =
|
|
160
|
-
if (
|
|
161
|
-
const e =
|
|
162
|
-
return typeof e.rounded == "string" && e.rounded in V ? V[e.rounded] :
|
|
163
|
-
}),
|
|
184
|
+
const Q = o ? "rgba(255, 255, 255, 0.08)" : "rgba(255, 255, 255, 0.22)", F = C(() => {
|
|
185
|
+
if (t.radius !== 32) return t.radius;
|
|
186
|
+
const e = t.asProps;
|
|
187
|
+
return typeof e.rounded == "string" && e.rounded in V ? V[e.rounded] : t.radius;
|
|
188
|
+
}), ie = C(() => t.borderRadius || `${F.value}px`), ce = /* @__PURE__ */ new Set([
|
|
164
189
|
"primary",
|
|
165
190
|
"secondary",
|
|
166
191
|
"accent",
|
|
@@ -171,62 +196,63 @@ var Be = {
|
|
|
171
196
|
"surface",
|
|
172
197
|
"background"
|
|
173
198
|
]);
|
|
174
|
-
function
|
|
175
|
-
if (
|
|
199
|
+
function ue(e, a) {
|
|
200
|
+
if (ce.has(e)) return `rgba(var(--v-theme-${e}), ${a})`;
|
|
176
201
|
if (e.startsWith("#")) {
|
|
177
|
-
const
|
|
178
|
-
return `rgba(${parseInt(
|
|
202
|
+
const s = e.replace("#", "");
|
|
203
|
+
return `rgba(${parseInt(s.slice(0, 2), 16)}, ${parseInt(s.slice(2, 4), 16)}, ${parseInt(s.slice(4, 6), 16)}, ${a})`;
|
|
179
204
|
}
|
|
180
|
-
return e.startsWith("rgb") ? e.replace("rgb(", "rgba(").replace(")", `, ${
|
|
205
|
+
return e.startsWith("rgb") ? e.replace("rgb(", "rgba(").replace(")", `, ${a})`) : Q;
|
|
181
206
|
}
|
|
182
|
-
const
|
|
183
|
-
if (
|
|
184
|
-
const e =
|
|
185
|
-
return typeof e.color == "string" ?
|
|
186
|
-
}), O = C(() =>
|
|
187
|
-
borderRadius: `${
|
|
188
|
-
...
|
|
189
|
-
...
|
|
190
|
-
...
|
|
207
|
+
const T = C(() => {
|
|
208
|
+
if (t.glassBackground !== void 0) return t.glassBackground;
|
|
209
|
+
const e = t.asProps;
|
|
210
|
+
return typeof e.color == "string" ? ue(e.color, 0.6) : Q;
|
|
211
|
+
}), O = C(() => t.fallbackOnly || !o ? "blur(12px)" : `url(#${I}) blur(${t.blur}px)`), le = C(() => ({
|
|
212
|
+
borderRadius: `${ie.value}`,
|
|
213
|
+
...T.value !== void 0 ? { backgroundColor: T.value } : {},
|
|
214
|
+
...T.value !== void 0 ? { boxShadow: ["inset 0 0 0 1px rgba(255, 255, 255, 0.28)", "0 18px 48px rgba(0, 0, 0, 0.18)"].join(", ") } : {},
|
|
215
|
+
...w.value && !W ? {
|
|
191
216
|
backdropFilter: O.value,
|
|
192
217
|
WebkitBackdropFilter: O.value
|
|
193
218
|
} : {},
|
|
194
|
-
...
|
|
219
|
+
...w.value && W && !o ? {
|
|
195
220
|
backdropFilter: O.value,
|
|
196
221
|
WebkitBackdropFilter: O.value
|
|
197
222
|
} : {}
|
|
198
223
|
}));
|
|
199
224
|
let G = null;
|
|
200
|
-
return
|
|
201
|
-
if (await
|
|
202
|
-
K(),
|
|
203
|
-
K(),
|
|
225
|
+
return xe(async () => {
|
|
226
|
+
if (await te(), o) {
|
|
227
|
+
K(), A(), await te(), w.value = !0, G = new ResizeObserver(() => {
|
|
228
|
+
K(), A(), w.value = !0;
|
|
204
229
|
});
|
|
205
230
|
const e = j();
|
|
206
231
|
e && G.observe(e);
|
|
207
|
-
} else
|
|
208
|
-
}),
|
|
232
|
+
} else w.value = !0;
|
|
233
|
+
}), Be(() => [
|
|
209
234
|
F.value,
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
235
|
+
t.bezel,
|
|
236
|
+
t.thickness,
|
|
237
|
+
t.refraction,
|
|
238
|
+
t.refractionMode,
|
|
239
|
+
t.magnification,
|
|
240
|
+
t.magnificationFocus,
|
|
241
|
+
t.surface,
|
|
242
|
+
t.specularOpacity,
|
|
243
|
+
t.glareAngle,
|
|
244
|
+
t.fallbackOnly
|
|
219
245
|
], () => {
|
|
220
|
-
|
|
246
|
+
o && A(), w.value = !0;
|
|
221
247
|
}), be(() => {
|
|
222
248
|
G?.disconnect();
|
|
223
|
-
}), (e,
|
|
249
|
+
}), (e, a) => (N(), ee("div", {
|
|
224
250
|
ref_key: "root",
|
|
225
|
-
ref:
|
|
251
|
+
ref: i,
|
|
226
252
|
class: "liquid-glass",
|
|
227
|
-
style:
|
|
228
|
-
}, [
|
|
229
|
-
id:
|
|
253
|
+
style: ye(le.value)
|
|
254
|
+
}, [Se(o) ? (N(), ee("svg", qe, [_("defs", null, [_("filter", {
|
|
255
|
+
id: I,
|
|
230
256
|
"color-interpolation-filters": "sRGB",
|
|
231
257
|
filterUnits: "userSpaceOnUse",
|
|
232
258
|
x: "0",
|
|
@@ -234,53 +260,53 @@ var Be = {
|
|
|
234
260
|
width: "10000",
|
|
235
261
|
height: "10000"
|
|
236
262
|
}, [
|
|
237
|
-
|
|
238
|
-
href:
|
|
263
|
+
_("feImage", {
|
|
264
|
+
href: y.value,
|
|
239
265
|
x: "0",
|
|
240
266
|
y: "0",
|
|
241
|
-
width:
|
|
242
|
-
height:
|
|
267
|
+
width: r.value,
|
|
268
|
+
height: c.value,
|
|
243
269
|
result: "displacement_map"
|
|
244
|
-
}, null, 8,
|
|
245
|
-
|
|
270
|
+
}, null, 8, Pe),
|
|
271
|
+
a[0] || (a[0] = _("feGaussianBlur", {
|
|
246
272
|
in: "displacement_map",
|
|
247
273
|
stdDeviation: "2",
|
|
248
274
|
result: "smooth_disp"
|
|
249
275
|
}, null, -1)),
|
|
250
|
-
|
|
276
|
+
_("feDisplacementMap", {
|
|
251
277
|
in: "SourceGraphic",
|
|
252
278
|
in2: "smooth_disp",
|
|
253
|
-
scale:
|
|
279
|
+
scale: h.value,
|
|
254
280
|
xChannelSelector: "R",
|
|
255
281
|
yChannelSelector: "G",
|
|
256
282
|
result: "refracted"
|
|
257
|
-
}, null, 8,
|
|
258
|
-
|
|
259
|
-
href:
|
|
283
|
+
}, null, 8, Fe),
|
|
284
|
+
_("feImage", {
|
|
285
|
+
href: g.value,
|
|
260
286
|
x: "0",
|
|
261
287
|
y: "0",
|
|
262
|
-
width:
|
|
263
|
-
height:
|
|
288
|
+
width: r.value,
|
|
289
|
+
height: c.value,
|
|
264
290
|
result: "specular"
|
|
265
|
-
}, null, 8,
|
|
266
|
-
|
|
291
|
+
}, null, 8, Oe),
|
|
292
|
+
a[1] || (a[1] = _("feBlend", {
|
|
267
293
|
in: "refracted",
|
|
268
294
|
in2: "specular",
|
|
269
295
|
mode: "screen"
|
|
270
296
|
}, null, -1))
|
|
271
|
-
])])])) :
|
|
272
|
-
default:
|
|
297
|
+
])])])) : pe("", !0), (N(), he(we(n.as), Me(ge(n.asProps)), {
|
|
298
|
+
default: Ie(() => [ke(e.$slots, "default", {}, void 0, !0)]),
|
|
273
299
|
_: 3
|
|
274
300
|
}, 16))], 4));
|
|
275
301
|
}
|
|
276
|
-
}),
|
|
277
|
-
const
|
|
278
|
-
for (const [
|
|
279
|
-
return
|
|
280
|
-
},
|
|
281
|
-
|
|
302
|
+
}), ze = (n, t) => {
|
|
303
|
+
const i = n.__vccOpts || n;
|
|
304
|
+
for (const [r, c] of t) i[r] = c;
|
|
305
|
+
return i;
|
|
306
|
+
}, Le = /* @__PURE__ */ ze(De, [["__scopeId", "data-v-5607a45c"]]), Ue = { install(n) {
|
|
307
|
+
n.component("Liqvued", Le);
|
|
282
308
|
} };
|
|
283
309
|
export {
|
|
284
|
-
|
|
285
|
-
|
|
310
|
+
Le as Liqvued,
|
|
311
|
+
Ue as default
|
|
286
312
|
};
|
package/dist/liqvued.umd.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(function(
|
|
1
|
+
(function(b,n){typeof exports=="object"&&typeof module<"u"?n(exports,require("vue")):typeof define=="function"&&define.amd?define(["exports","vue"],n):(b=typeof globalThis<"u"?globalThis:b||self,n(b.Liqvued={},b.Vue))})(this,function(b,n){Object.defineProperties(b,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}});function Z(r,t,c,o,l,x,M){if(r==="center")return t;const p={x:-t.x,y:-t.y};if(r==="split"){const s=M<.5?-1:1;return{x:p.x*s,y:p.y*s}}return p}function ee(r,t){return r!=="split"?1:Math.min(1,Math.abs(t-.5)*2)}function I(r,t,c,o,l){const x=Math.min(l,Math.min(c,o)/2),M=c/2-x,p=o/2-x,s=Math.abs(r-c/2)-M,R=Math.abs(t-o/2)-p;return-(Math.min(Math.max(s,R),0)+Math.hypot(Math.max(s,0),Math.max(R,0))-x)}function te(r,t,c,o,l){const M=I(r-1.5,t,c,o,l)-I(r+1.5,t,c,o,l),p=I(r,t-1.5,c,o,l)-I(r,t+1.5,c,o,l),s=Math.hypot(M,p);return s<1e-10?{x:0,y:-1}:{x:M/s,y:p/s}}var ae={key:0,class:"liquid-glass__svg","aria-hidden":"true"},ne=["href","width","height"],re=["scale"],oe=["href","width","height"],se=(0,n.defineComponent)({__name:"Liqvued",props:{as:{default:"div"},asProps:{default:()=>({})},radius:{default:32},borderRadius:{default:void 0},bezel:{default:22},thickness:{default:42},refraction:{default:1},refractionMode:{default:"edge"},magnification:{default:0},magnificationFocus:{default:.82},blur:{default:.4},surface:{default:"convex"},specularOpacity:{default:.45},glareAngle:{type:[Number,Boolean],default:-60},glassBackground:{default:void 0},fallbackOnly:{type:Boolean,default:!1}},setup(r){const t=r,c=(0,n.ref)(null),o=(0,n.ref)(1),l=(0,n.ref)(1),x=(0,n.ref)(""),M=(0,n.ref)(""),p=(0,n.ref)(1),s=typeof CSS<"u"&&typeof navigator<"u"&&CSS.supports?.("backdrop-filter","url(#x)")&&/Chrome/.test(navigator.userAgent),R=`${`lg-${Math.random().toString(36).slice(2)}`}-filter`,A=(0,n.inject)("_liqvued",!1);(0,n.provide)("_liqvued",!0);const w=(0,n.ref)(!1),G={0:0,sm:4,md:8,lg:12,xl:16,pill:9999,circle:9999,shaped:16};function W(){return c.value}function u(e,a=0,i=1){return Math.max(a,Math.min(i,e))}function E(e){return e=u(e),e*e*e*(e*(e*6-15)+10)}function q(e){return Math.pow(1-Math.pow(1-u(e),4),1/4)}function le(e,a,i){return u(e+Math.sin(u(e)*Math.PI*a)*i)}function ue(e){const a=u(e);return u(a+Math.sin(a*39.1)*.06+Math.sin(a*91.7)*.035)}function de(e,a,i,g,_,B){const v=i/2,y=g/2,$=Math.max(1,i/2),k=Math.max(1,g/2),N=(e-v)/$,O=(a-y)/k,d=Math.hypot(N,O),f=u(B,.2,1.4),m=Math.pow(E(1-u(d/f)),1.6),h=_>=0?1+_*m:1/(1+Math.abs(_)*m);return{x:(e-v)/h-(e-v),y:(a-y)/h-(a-y)}}function j(e){const a=u(e);switch(t.surface){case"concave":case"bowl":return 1-q(a);case"lip":return q(a)*(1-E(a))+(1-q(a))*E(a);case"bevel":return a;case"saddle":return .5+Math.sin((a-.5)*Math.PI)*.5;case"ripple":return le(a,3,.12);case"noise":return ue(a);case"asymmetric":return u(Math.pow(a,.65)*.65+E(a)*.35);case"convex":return q(a)}}function fe(e){return(j(e+.001)-j(e-.001))/(2*.001)}function he(e){const g=Math.atan(e),_=1/1.5*Math.sin(g),B=Math.asin(u(_,-1,1));return Math.tan(g-B)}function H(e){return e.toDataURL("image/png")}function D(){if(t.fallbackOnly)return;const e=Math.max(1,Math.round(o.value)),a=Math.max(1,Math.round(l.value)),i=document.createElement("canvas");i.width=e,i.height=a;const g=document.createElement("canvas");g.width=e,g.height=a;const _=i.getContext("2d"),B=g.getContext("2d"),v=_.createImageData(e,a),y=B.createImageData(e,a),$=[];let k=1;const N=t.glareAngle!==!1?{x:Math.cos(t.glareAngle*Math.PI/180),y:Math.sin(t.glareAngle*Math.PI/180)}:{x:0,y:0},O=Math.max(1,Math.min(t.bezel,Math.min(e,a)*.28));for(let d=0;d<a;d++)for(let f=0;f<e;f++){const m=I(f,d,e,a,C.value),h=u(m/O);let V=0,F=0,Q=0;if(m>=0&&m<O){const S=te(f,d,e,a,C.value),X=he(fe(h))*t.thickness*E(1-h)*ee(t.refractionMode,h)*t.refraction,Y=Z(t.refractionMode,S,f,d,e,a,h);V=Y.x*X,F=Y.y*X,k=Math.max(k,Math.abs(V),Math.abs(F));const ye=u(S.x*N.x+S.y*N.y,0,1);Q=Math.pow(ye,18)*t.specularOpacity}if(t.magnification!==0&&m>=0){const S=de(f,d,e,a,t.magnification,t.magnificationFocus);V+=S.x,F+=S.y}$.push([V,F]);const T=(d*e+f)*4,ve=Math.round(Q*255);y.data[T]=255,y.data[T+1]=255,y.data[T+2]=255,y.data[T+3]=ve}k=$.reduce((d,[f,m])=>Math.max(d,Math.abs(f),Math.abs(m)),1),$.forEach(([d,f],m)=>{const h=m*4;v.data[h]=Math.round(128+d/k*127),v.data[h+1]=Math.round(128+f/k*127),v.data[h+2]=128,v.data[h+3]=255}),_.putImageData(v,0,0),B.putImageData(y,0,0),x.value=H(i),M.value=H(g),p.value=k}function J(){const e=W();e&&(o.value=Math.max(1,e.offsetWidth),l.value=Math.max(1,e.offsetHeight))}const K=s?"rgba(255, 255, 255, 0.08)":"rgba(255, 255, 255, 0.22)",C=(0,n.computed)(()=>{if(t.radius!==32)return t.radius;const e=t.asProps;return typeof e.rounded=="string"&&e.rounded in G?G[e.rounded]:t.radius}),pe=(0,n.computed)(()=>t.borderRadius||`${C.value}px`),me=new Set(["primary","secondary","accent","info","warning","error","success","surface","background"]);function ge(e,a){if(me.has(e))return`rgba(var(--v-theme-${e}), ${a})`;if(e.startsWith("#")){const i=e.replace("#","");return`rgba(${parseInt(i.slice(0,2),16)}, ${parseInt(i.slice(2,4),16)}, ${parseInt(i.slice(4,6),16)}, ${a})`}return e.startsWith("rgb")?e.replace("rgb(","rgba(").replace(")",`, ${a})`):K}const L=(0,n.computed)(()=>{if(t.glassBackground!==void 0)return t.glassBackground;const e=t.asProps;return typeof e.color=="string"?ge(e.color,.6):K}),P=(0,n.computed)(()=>t.fallbackOnly||!s?"blur(12px)":`url(#${R}) blur(${t.blur}px)`),Me=(0,n.computed)(()=>({borderRadius:`${pe.value}`,...L.value!==void 0?{backgroundColor:L.value}:{},...L.value!==void 0?{boxShadow:["inset 0 0 0 1px rgba(255, 255, 255, 0.28)","0 18px 48px rgba(0, 0, 0, 0.18)"].join(", ")}:{},...w.value&&!A?{backdropFilter:P.value,WebkitBackdropFilter:P.value}:{},...w.value&&A&&!s?{backdropFilter:P.value,WebkitBackdropFilter:P.value}:{}}));let z=null;return(0,n.onMounted)(async()=>{if(await(0,n.nextTick)(),s){J(),D(),await(0,n.nextTick)(),w.value=!0,z=new ResizeObserver(()=>{J(),D(),w.value=!0});const e=W();e&&z.observe(e)}else w.value=!0}),(0,n.watch)(()=>[C.value,t.bezel,t.thickness,t.refraction,t.refractionMode,t.magnification,t.magnificationFocus,t.surface,t.specularOpacity,t.glareAngle,t.fallbackOnly],()=>{s&&D(),w.value=!0}),(0,n.onBeforeUnmount)(()=>{z?.disconnect()}),(e,a)=>((0,n.openBlock)(),(0,n.createElementBlock)("div",{ref_key:"root",ref:c,class:"liquid-glass",style:(0,n.normalizeStyle)(Me.value)},[(0,n.unref)(s)?((0,n.openBlock)(),(0,n.createElementBlock)("svg",ae,[(0,n.createElementVNode)("defs",null,[(0,n.createElementVNode)("filter",{id:R,"color-interpolation-filters":"sRGB",filterUnits:"userSpaceOnUse",x:"0",y:"0",width:"10000",height:"10000"},[(0,n.createElementVNode)("feImage",{href:x.value,x:"0",y:"0",width:o.value,height:l.value,result:"displacement_map"},null,8,ne),a[0]||(a[0]=(0,n.createElementVNode)("feGaussianBlur",{in:"displacement_map",stdDeviation:"2",result:"smooth_disp"},null,-1)),(0,n.createElementVNode)("feDisplacementMap",{in:"SourceGraphic",in2:"smooth_disp",scale:p.value,xChannelSelector:"R",yChannelSelector:"G",result:"refracted"},null,8,re),(0,n.createElementVNode)("feImage",{href:M.value,x:"0",y:"0",width:o.value,height:l.value,result:"specular"},null,8,oe),a[1]||(a[1]=(0,n.createElementVNode)("feBlend",{in:"refracted",in2:"specular",mode:"screen"},null,-1))])])])):(0,n.createCommentVNode)("",!0),((0,n.openBlock)(),(0,n.createBlock)((0,n.resolveDynamicComponent)(r.as),(0,n.normalizeProps)((0,n.guardReactiveProps)(r.asProps)),{default:(0,n.withCtx)(()=>[(0,n.renderSlot)(e.$slots,"default",{},void 0,!0)]),_:3},16))],4))}}),ie=(r,t)=>{const c=r.__vccOpts||r;for(const[o,l]of t)c[o]=l;return c},U=ie(se,[["__scopeId","data-v-5607a45c"]]),ce={install(r){r.component("Liqvued",U)}};b.Liqvued=U,b.default=ce});
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Component } from 'vue'
|
|
2
|
+
|
|
3
|
+
export type Surface =
|
|
4
|
+
| 'convex'
|
|
5
|
+
| 'concave'
|
|
6
|
+
| 'lip'
|
|
7
|
+
| 'bowl'
|
|
8
|
+
| 'bevel'
|
|
9
|
+
| 'saddle'
|
|
10
|
+
| 'ripple'
|
|
11
|
+
| 'noise'
|
|
12
|
+
| 'asymmetric'
|
|
13
|
+
|
|
14
|
+
export type RefractionMode = 'edge' | 'center' | 'split'
|
|
15
|
+
|
|
16
|
+
export interface LiqvuedProps {
|
|
17
|
+
as?: string | Component
|
|
18
|
+
asProps?: Record<string, unknown>
|
|
19
|
+
radius?: number
|
|
20
|
+
borderRadius?: string
|
|
21
|
+
bezel?: number
|
|
22
|
+
thickness?: number
|
|
23
|
+
refraction?: number
|
|
24
|
+
refractionMode?: RefractionMode
|
|
25
|
+
magnification?: number
|
|
26
|
+
magnificationFocus?: number
|
|
27
|
+
blur?: number
|
|
28
|
+
surface?: Surface
|
|
29
|
+
specularOpacity?: number
|
|
30
|
+
glareAngle?: number | false
|
|
31
|
+
glassBackground?: string
|
|
32
|
+
fallbackOnly?: boolean
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export declare const Liqvued: new() => {
|
|
36
|
+
$props: LiqvuedProps
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
declare const _default: {
|
|
40
|
+
install(app: unknown): void
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export default _default
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "liqvued",
|
|
3
3
|
"types": "./index.d.ts",
|
|
4
|
-
"version": "0.2.
|
|
4
|
+
"version": "0.2.1",
|
|
5
5
|
"description": "Liquid-glass for Vue",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": {
|
|
@@ -49,32 +49,32 @@
|
|
|
49
49
|
"author": "Igor Voytovich",
|
|
50
50
|
"license": "MIT",
|
|
51
51
|
"repository": "https://github.com/IVoyt/liqvued",
|
|
52
|
-
"packageManager": "pnpm@
|
|
52
|
+
"packageManager": "pnpm@11.17.0",
|
|
53
53
|
"peerDependencies": {
|
|
54
54
|
"vue": "^3.5.14"
|
|
55
55
|
},
|
|
56
56
|
"devDependencies": {
|
|
57
|
-
"@intlify/unplugin-vue-i18n": "^11.
|
|
57
|
+
"@intlify/unplugin-vue-i18n": "^11.2.4",
|
|
58
58
|
"@rollup/plugin-alias": "^5.1.1",
|
|
59
59
|
"@stylistic/eslint-plugin": "^5.10.0",
|
|
60
|
-
"@types/node": "^
|
|
61
|
-
"@vitejs/plugin-vue": "^6.0.
|
|
62
|
-
"@vitejs/plugin-vue-jsx": "^5.1.
|
|
63
|
-
"eslint": "^10.
|
|
60
|
+
"@types/node": "^26.1.1",
|
|
61
|
+
"@vitejs/plugin-vue": "^6.0.8",
|
|
62
|
+
"@vitejs/plugin-vue-jsx": "^5.1.6",
|
|
63
|
+
"eslint": "^10.8.0",
|
|
64
64
|
"eslint-import-resolver-typescript": "^4.4.4",
|
|
65
65
|
"eslint-plugin-case-police": "^2.2.1",
|
|
66
66
|
"eslint-plugin-import": "^2.32.0",
|
|
67
|
-
"eslint-plugin-promise": "^7.
|
|
68
|
-
"eslint-plugin-sonarjs": "^4.0
|
|
69
|
-
"eslint-plugin-unicorn": "^
|
|
67
|
+
"eslint-plugin-promise": "^7.3.0",
|
|
68
|
+
"eslint-plugin-sonarjs": "^4.2.0",
|
|
69
|
+
"eslint-plugin-unicorn": "^72.0.0",
|
|
70
70
|
"eslint-plugin-vue": "^10.9.0",
|
|
71
|
-
"sass-embedded": "^1.
|
|
72
|
-
"tsx": "^4.
|
|
71
|
+
"sass-embedded": "^1.100.0",
|
|
72
|
+
"tsx": "^4.23.1",
|
|
73
73
|
"typescript": "^5.9.3",
|
|
74
|
-
"typescript-eslint": "^8.
|
|
75
|
-
"unplugin-vue-components": "^
|
|
76
|
-
"vite": "^8.
|
|
74
|
+
"typescript-eslint": "^8.65.0",
|
|
75
|
+
"unplugin-vue-components": "^32.1.0",
|
|
76
|
+
"vite": "^8.1.5",
|
|
77
77
|
"vue-eslint-parser": "^10.4.0",
|
|
78
|
-
"vue-tsc": "^3.
|
|
78
|
+
"vue-tsc": "^3.3.8"
|
|
79
79
|
}
|
|
80
|
-
}
|
|
80
|
+
}
|
package/src/Liqvued.vue
CHANGED
|
@@ -10,8 +10,9 @@ import {
|
|
|
10
10
|
ref,
|
|
11
11
|
watch,
|
|
12
12
|
} from 'vue'
|
|
13
|
+
import { refractionDirection, refractionModeStrength } from './refraction'
|
|
13
14
|
import { distanceToRoundedRectEdge, normalFromRoundedRect } from './shape'
|
|
14
|
-
import type { Surface } from './types'
|
|
15
|
+
import type { RefractionMode, Surface } from './types'
|
|
15
16
|
|
|
16
17
|
const props = withDefaults(defineProps<{
|
|
17
18
|
as?: string | Component
|
|
@@ -21,12 +22,13 @@ const props = withDefaults(defineProps<{
|
|
|
21
22
|
bezel?: number
|
|
22
23
|
thickness?: number
|
|
23
24
|
refraction?: number
|
|
25
|
+
refractionMode?: RefractionMode
|
|
24
26
|
magnification?: number
|
|
25
27
|
magnificationFocus?: number
|
|
26
28
|
blur?: number
|
|
27
29
|
surface?: Surface
|
|
28
30
|
specularOpacity?: number
|
|
29
|
-
glareAngle?: number
|
|
31
|
+
glareAngle?: number | false
|
|
30
32
|
glassBackground?: string
|
|
31
33
|
fallbackOnly?: boolean
|
|
32
34
|
}>(), {
|
|
@@ -34,9 +36,11 @@ const props = withDefaults(defineProps<{
|
|
|
34
36
|
asProps: () => ({}),
|
|
35
37
|
fallbackOnly: false,
|
|
36
38
|
radius: 32,
|
|
39
|
+
borderRadius: undefined,
|
|
37
40
|
bezel: 22,
|
|
38
41
|
thickness: 42,
|
|
39
42
|
refraction: 1,
|
|
43
|
+
refractionMode: 'edge',
|
|
40
44
|
magnification: 0,
|
|
41
45
|
magnificationFocus: 0.82,
|
|
42
46
|
blur: 0.4,
|
|
@@ -208,10 +212,12 @@ function buildMaps() {
|
|
|
208
212
|
const vectors: Array<[number, number]> = []
|
|
209
213
|
let max = 1
|
|
210
214
|
|
|
211
|
-
const light =
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
+
const light = props.glareAngle !== false
|
|
216
|
+
? {
|
|
217
|
+
x: Math.cos((props.glareAngle * Math.PI) / 180),
|
|
218
|
+
y: Math.sin((props.glareAngle * Math.PI) / 180),
|
|
219
|
+
}
|
|
220
|
+
: { x: 0, y: 0 }
|
|
215
221
|
const edgeBand = Math.max(
|
|
216
222
|
1,
|
|
217
223
|
Math.min(props.bezel, Math.min(w, h) * 0.28),
|
|
@@ -235,10 +241,13 @@ function buildMaps() {
|
|
|
235
241
|
bend *
|
|
236
242
|
props.thickness *
|
|
237
243
|
smootherStep(1 - t) *
|
|
244
|
+
refractionModeStrength(props.refractionMode, t) *
|
|
238
245
|
props.refraction
|
|
239
246
|
|
|
240
|
-
|
|
241
|
-
|
|
247
|
+
const direction = refractionDirection(props.refractionMode, n, x, y, w, h, t)
|
|
248
|
+
|
|
249
|
+
vx = direction.x * amount
|
|
250
|
+
vy = direction.y * amount
|
|
242
251
|
|
|
243
252
|
max = Math.max(max, Math.abs(vx), Math.abs(vy))
|
|
244
253
|
|
|
@@ -412,6 +421,7 @@ watch(
|
|
|
412
421
|
props.bezel,
|
|
413
422
|
props.thickness,
|
|
414
423
|
props.refraction,
|
|
424
|
+
props.refractionMode,
|
|
415
425
|
props.magnification,
|
|
416
426
|
props.magnificationFocus,
|
|
417
427
|
props.surface,
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { deepStrictEqual, ok } from 'node:assert'
|
|
2
|
+
import { refractionDirection, refractionModeStrength } from './refraction'
|
|
3
|
+
|
|
4
|
+
const normal = { x: 1, y: 0 }
|
|
5
|
+
|
|
6
|
+
deepStrictEqual(
|
|
7
|
+
refractionDirection('edge', normal, 20, 50, 100, 100, 0.25),
|
|
8
|
+
{ x: -1, y: -0 },
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
deepStrictEqual(
|
|
12
|
+
refractionDirection('center', normal, 20, 50, 100, 100, 0.25),
|
|
13
|
+
{ x: 1, y: 0 },
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
deepStrictEqual(
|
|
17
|
+
refractionDirection('center', { x: 0, y: 1 }, 50, 20, 100, 100, 0.25),
|
|
18
|
+
{ x: 0, y: 1 },
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
deepStrictEqual(
|
|
22
|
+
refractionDirection('center', { x: 0, y: 1 }, 20, 20, 100, 100, 0.25),
|
|
23
|
+
{ x: 0, y: 1 },
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
const outerSplit = refractionDirection('split', normal, 20, 50, 100, 100, 0.25)
|
|
27
|
+
const innerSplit = refractionDirection('split', normal, 20, 50, 100, 100, 0.75)
|
|
28
|
+
|
|
29
|
+
ok(outerSplit.x * innerSplit.x < 0)
|
|
30
|
+
deepStrictEqual(outerSplit, { x: 1, y: 0 })
|
|
31
|
+
deepStrictEqual(innerSplit, { x: -1, y: -0 })
|
|
32
|
+
|
|
33
|
+
deepStrictEqual(refractionModeStrength('center', 0.5), 1)
|
|
34
|
+
deepStrictEqual(refractionModeStrength('split', 0.5), 0)
|
|
35
|
+
ok(refractionModeStrength('split', 0.1) > refractionModeStrength('split', 0.4))
|
|
36
|
+
ok(refractionModeStrength('split', 0.9) > refractionModeStrength('split', 0.6))
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { RefractionMode } from './types'
|
|
2
|
+
|
|
3
|
+
export const refractionModeOptions = [
|
|
4
|
+
'edge',
|
|
5
|
+
'center',
|
|
6
|
+
'split',
|
|
7
|
+
] as const satisfies readonly RefractionMode[]
|
|
8
|
+
|
|
9
|
+
interface Vector {
|
|
10
|
+
x: number
|
|
11
|
+
y: number
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function refractionDirection(
|
|
15
|
+
mode: RefractionMode,
|
|
16
|
+
normal: Vector,
|
|
17
|
+
_x: number,
|
|
18
|
+
_y: number,
|
|
19
|
+
_width: number,
|
|
20
|
+
_height: number,
|
|
21
|
+
t: number,
|
|
22
|
+
): Vector {
|
|
23
|
+
if (mode === 'center') {
|
|
24
|
+
return normal
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const edge = {
|
|
28
|
+
x: -normal.x,
|
|
29
|
+
y: -normal.y,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (mode === 'split') {
|
|
33
|
+
const side = t < 0.5 ? -1 : 1
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
x: edge.x * side,
|
|
37
|
+
y: edge.y * side,
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return edge
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function refractionModeStrength(mode: RefractionMode, t: number): number {
|
|
45
|
+
if (mode !== 'split') {
|
|
46
|
+
return 1
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return Math.min(1, Math.abs(t - 0.5) * 2)
|
|
50
|
+
}
|