vireglass 3.1.0 → 3.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -3
- package/dist/dom.cjs +43 -18
- package/dist/dom.cjs.map +1 -1
- package/dist/dom.d.cts +8 -2
- package/dist/dom.d.ts +8 -2
- package/dist/dom.js +44 -20
- package/dist/dom.js.map +1 -1
- package/package.json +1 -1
- package/src/dom/displacement.ts +9 -2
- package/src/dom/index.ts +51 -19
package/README.md
CHANGED
|
@@ -153,6 +153,10 @@ glass.setMorph({ smoothing: 34, shape: { offsetX: 150, offsetY: -78, width: 170,
|
|
|
153
153
|
Two shapes joined through the smooth union — §5's merging and splitting. What the shapes mean is
|
|
154
154
|
your choreography; the material only knows how two silhouettes join.
|
|
155
155
|
|
|
156
|
+
`glass.setAppear(t)` brings the element in and out by how much light it bends, not by opacity. The
|
|
157
|
+
maps are baked once at full presence and appear is written in place on the filter, so driving it
|
|
158
|
+
every frame costs no bake and no new filter, at rest or interim.
|
|
159
|
+
|
|
156
160
|
### Morphing elements
|
|
157
161
|
|
|
158
162
|
```ts
|
|
@@ -168,15 +172,16 @@ backdrop probe, a full-resolution displacement map (and spectral map) and a new
|
|
|
168
172
|
That is the difference between 60 fps and 10 to 20. While interim, the element does none of it: it
|
|
169
173
|
reuses its last sample, keeps its filter, and the browser stretches the existing map over the live
|
|
170
174
|
box. The body, rim, shadow and custom properties still update every frame. A change of the morph
|
|
171
|
-
shapes passed to `setMorph
|
|
175
|
+
shapes passed to `setMorph` or of the system accessibility settings rebuilds the map
|
|
172
176
|
at reduced resolution and shows it in place, where the browser decodes lazily only once it has been
|
|
173
177
|
decoded. `setInterim(false)` does one full-resolution rebuild and one probe (with a supplied
|
|
174
|
-
`sample` it does not probe). Repeating
|
|
178
|
+
`sample` it does not probe). Repeating `setInterim(false)` is a no-op.
|
|
175
179
|
|
|
176
180
|
Always pair `setInterim(true)` with `setInterim(false)`. As a safety net an element that gets no call
|
|
177
181
|
at all for 2 seconds (no resize, morph, appear or update) leaves interim by itself, exactly as
|
|
178
182
|
`setInterim(false)` would, so a morph that never reported landing cannot leave the glass on a
|
|
179
|
-
stretched map. A pause longer than that in the middle of a morph ends interim early.
|
|
183
|
+
stretched map. A pause longer than that in the middle of a morph ends interim early. A gesture held
|
|
184
|
+
still sends no geometry, so a host that keeps a drag or a press open longer than that calls `setInterim(true)` again while it lasts: each call restarts the 2 seconds.
|
|
180
185
|
|
|
181
186
|
`renderStats()` returns `{ probes, fullBakes, reducedBakes, filterSwaps }`, counted since the last
|
|
182
187
|
`resetRenderStats()`. Every map counts: a rebuild with a spectral edge is two full bakes, one for
|
package/dist/dom.cjs
CHANGED
|
@@ -579,13 +579,15 @@ function bevelProfile(t) {
|
|
|
579
579
|
const raw = Math.min(t / Math.sqrt(Math.max(1 - t * t * BEVEL.sphere, BEVEL.floor)), BEVEL.slopeMax);
|
|
580
580
|
return raw / BEVEL.slopeMax;
|
|
581
581
|
}
|
|
582
|
+
function displacementScale(optics, geometry) {
|
|
583
|
+
return Math.min(optics.refraction * thicknessDp(geometry, optics) * SCALE_GAIN, MAX_SCALE);
|
|
584
|
+
}
|
|
582
585
|
function renderDisplacementPixels(optics, geometry, dpr, state = {}) {
|
|
583
586
|
const bevel = bevelDp(geometry, optics);
|
|
584
587
|
const density = mapDensity(geometry, bevel, dpr);
|
|
585
588
|
const width = Math.max(1, Math.round(geometry.width * density));
|
|
586
589
|
const height = Math.max(1, Math.round(geometry.height * density));
|
|
587
|
-
const
|
|
588
|
-
const scale = Math.min(rawScale, MAX_SCALE);
|
|
590
|
+
const scale = displacementScale(optics, geometry);
|
|
589
591
|
const smoothing = state.smoothing ?? 0;
|
|
590
592
|
const shapes = [state.morph, state.morph2, ...state.morphs ?? []];
|
|
591
593
|
const data = new Uint8ClampedArray(width * height * 4);
|
|
@@ -1807,6 +1809,9 @@ function getFilterHost(host) {
|
|
|
1807
1809
|
(host instanceof ShadowRoot ? host : host.body).appendChild(svg);
|
|
1808
1810
|
return svg;
|
|
1809
1811
|
}
|
|
1812
|
+
function writeAttr(node, name, value) {
|
|
1813
|
+
if (node.getAttribute(name) !== value) node.setAttribute(name, value);
|
|
1814
|
+
}
|
|
1810
1815
|
function buildFilterElement(id, mapUrl, optics, scale, spectral) {
|
|
1811
1816
|
const filter = document.createElementNS(SVG_NS, "filter");
|
|
1812
1817
|
filter.setAttribute("id", id);
|
|
@@ -1892,6 +1897,7 @@ function withSpectral(filter, spectral) {
|
|
|
1892
1897
|
product.setAttribute("in2", "hue");
|
|
1893
1898
|
product.setAttribute("operator", "arithmetic");
|
|
1894
1899
|
product.setAttribute("k1", String(spectral.headroom));
|
|
1900
|
+
product.setAttribute("data-vireglass-headroom", String(spectral.headroom));
|
|
1895
1901
|
product.setAttribute("k2", "0");
|
|
1896
1902
|
product.setAttribute("k3", "0");
|
|
1897
1903
|
product.setAttribute("k4", "0");
|
|
@@ -1949,6 +1955,7 @@ function attachGlass(el, opts = {}) {
|
|
|
1949
1955
|
let filterEl = null;
|
|
1950
1956
|
let destroyed = false;
|
|
1951
1957
|
let mapKey = "";
|
|
1958
|
+
let shownGeometry = null;
|
|
1952
1959
|
let interim = false;
|
|
1953
1960
|
let settling = false;
|
|
1954
1961
|
let lastSample = null;
|
|
@@ -2011,31 +2018,46 @@ function attachGlass(el, opts = {}) {
|
|
|
2011
2018
|
settling = true;
|
|
2012
2019
|
apply();
|
|
2013
2020
|
}
|
|
2014
|
-
function retarget(f, map, hue) {
|
|
2021
|
+
function retarget(f, map, hue, geometry) {
|
|
2015
2022
|
const images = f.querySelectorAll("feImage");
|
|
2016
2023
|
images[0]?.setAttribute("href", map.url);
|
|
2017
2024
|
if (hue) images[1]?.setAttribute("href", hue.url);
|
|
2025
|
+
shownGeometry = geometry;
|
|
2026
|
+
present(f);
|
|
2027
|
+
}
|
|
2028
|
+
function present(f) {
|
|
2029
|
+
if (!shownGeometry) return;
|
|
2030
|
+
const o = optics();
|
|
2031
|
+
const scale = displacementScale(o, shownGeometry);
|
|
2018
2032
|
for (const pass of f.querySelectorAll("feDisplacementMap")) {
|
|
2019
2033
|
const ratio = Number(pass.getAttribute("data-vireglass-ratio") ?? 1);
|
|
2020
|
-
pass
|
|
2034
|
+
writeAttr(pass, "scale", String(scale * (Number.isFinite(ratio) ? ratio : 1)));
|
|
2035
|
+
}
|
|
2036
|
+
const frost = f.querySelector("feGaussianBlur");
|
|
2037
|
+
if (frost) writeAttr(frost, "stdDeviation", String(Math.max(o.blur, 0)));
|
|
2038
|
+
const hue = f.querySelector('feComposite[in2="hue"]');
|
|
2039
|
+
if (hue) {
|
|
2040
|
+
const headroom = Number(hue.getAttribute("data-vireglass-headroom") ?? 1);
|
|
2041
|
+
writeAttr(hue, "k1", String(headroom * appear));
|
|
2042
|
+
writeAttr(hue, "k2", String(1 - appear));
|
|
2021
2043
|
}
|
|
2022
2044
|
}
|
|
2023
2045
|
function requestInterimMap() {
|
|
2024
2046
|
if (!svgSupported || !filterEl || destroyed) return;
|
|
2025
2047
|
const geometry = opts.geometry ?? measureGeometry(el);
|
|
2026
2048
|
if (isEmpty(geometry)) return;
|
|
2027
|
-
const
|
|
2049
|
+
const full = settledOptics();
|
|
2028
2050
|
const key = interimKey(
|
|
2029
2051
|
geometry.width,
|
|
2030
2052
|
geometry.height,
|
|
2031
2053
|
geometry.cornerRadius,
|
|
2032
2054
|
JSON.stringify([morph?.smoothing, morph?.shape, morph?.shape2, morph?.shapes]),
|
|
2033
|
-
|
|
2055
|
+
`${full.refraction}:${full.refractionScale}:${full.bevelDp}`
|
|
2034
2056
|
);
|
|
2035
2057
|
let rec = interimMaps.get(key);
|
|
2036
2058
|
if (!rec) {
|
|
2037
2059
|
bump("reducedBakes");
|
|
2038
|
-
rec = { map: buildDisplacementMap(
|
|
2060
|
+
rec = { map: buildDisplacementMap(full, geometry, Math.min(1, devicePixelRatio()), morphState()), ready: false, geometry };
|
|
2039
2061
|
interimMaps.set(key, rec);
|
|
2040
2062
|
}
|
|
2041
2063
|
const mine = seq += 1;
|
|
@@ -2044,7 +2066,7 @@ function attachGlass(el, opts = {}) {
|
|
|
2044
2066
|
const show = () => {
|
|
2045
2067
|
if (destroyed || filterEl !== forFilter || mine <= shownSeq) return;
|
|
2046
2068
|
shownSeq = mine;
|
|
2047
|
-
retarget(forFilter, record.map, null);
|
|
2069
|
+
retarget(forFilter, record.map, null, record.geometry);
|
|
2048
2070
|
};
|
|
2049
2071
|
if (record.ready) {
|
|
2050
2072
|
show();
|
|
@@ -2066,17 +2088,18 @@ function attachGlass(el, opts = {}) {
|
|
|
2066
2088
|
wasLight = light;
|
|
2067
2089
|
const body = opts.body === false ? null : bodyCss(settledOptics(), sample, light ? 1 : 0, accent, appear);
|
|
2068
2090
|
if (svgSupported) {
|
|
2069
|
-
const
|
|
2091
|
+
const full = settledOptics();
|
|
2092
|
+
const key = `m${morphKey()}|${geometry.width}x${geometry.height}r${geometry.cornerRadius}@${devicePixelRatio()}:${full.refraction}:${full.refractionScale}:${full.bevelDp}:${full.blur}`;
|
|
2070
2093
|
if (interim && filterEl) ; else if (isEmpty(geometry)) ; else if (key !== mapKey || !filterEl) {
|
|
2071
2094
|
const map = cached(`d|${key}`, () => {
|
|
2072
2095
|
bump("fullBakes");
|
|
2073
|
-
return buildDisplacementMap(
|
|
2096
|
+
return buildDisplacementMap(full, geometry, devicePixelRatio(), morphState());
|
|
2074
2097
|
});
|
|
2075
|
-
const hue = hasSpectralEdge(
|
|
2098
|
+
const hue = hasSpectralEdge(full) ? cached(`h|${key}`, () => {
|
|
2076
2099
|
bump("fullBakes");
|
|
2077
|
-
return buildSpectralMap(
|
|
2100
|
+
return buildSpectralMap(full, geometry, devicePixelRatio());
|
|
2078
2101
|
}) : null;
|
|
2079
|
-
const signature = filterSignature(
|
|
2102
|
+
const signature = filterSignature(full.blur, full.ior, full.iorSpread, !!hue);
|
|
2080
2103
|
if (settling && filterEl?.getAttribute("data-vireglass-signature") === signature) {
|
|
2081
2104
|
settling = false;
|
|
2082
2105
|
const mine = seq += 1;
|
|
@@ -2085,21 +2108,23 @@ function attachGlass(el, opts = {}) {
|
|
|
2085
2108
|
void Promise.all([decoded(map.url), hue ? decoded(hue.url) : void 0]).then(() => {
|
|
2086
2109
|
if (destroyed || filterEl !== forFilter || mapKey !== forKey || mine <= shownSeq) return;
|
|
2087
2110
|
shownSeq = mine;
|
|
2088
|
-
retarget(forFilter, map, hue);
|
|
2111
|
+
retarget(forFilter, map, hue, geometry);
|
|
2089
2112
|
});
|
|
2090
2113
|
} else {
|
|
2091
2114
|
settling = false;
|
|
2092
2115
|
invalidate();
|
|
2093
2116
|
const defs = getFilterHost(host).querySelector("defs");
|
|
2094
|
-
const next = buildFilterElement(id, map.url,
|
|
2117
|
+
const next = buildFilterElement(id, map.url, full, map.scale, hue);
|
|
2095
2118
|
next.setAttribute("data-vireglass-signature", signature);
|
|
2096
2119
|
if (filterEl?.parentNode) defs.replaceChild(next, filterEl);
|
|
2097
2120
|
else defs.appendChild(next);
|
|
2098
2121
|
bump("filterSwaps");
|
|
2099
2122
|
filterEl = next;
|
|
2123
|
+
shownGeometry = geometry;
|
|
2100
2124
|
}
|
|
2101
2125
|
mapKey = key;
|
|
2102
2126
|
}
|
|
2127
|
+
if (filterEl) present(filterEl);
|
|
2103
2128
|
el.style.setProperty("backdrop-filter", `url(#${id})`);
|
|
2104
2129
|
el.style.setProperty("-webkit-backdrop-filter", `url(#${id})`);
|
|
2105
2130
|
} else {
|
|
@@ -2300,7 +2325,6 @@ function attachGlass(el, opts = {}) {
|
|
|
2300
2325
|
if (next === appear) return;
|
|
2301
2326
|
appear = next;
|
|
2302
2327
|
apply();
|
|
2303
|
-
if (interim) requestInterimMap();
|
|
2304
2328
|
},
|
|
2305
2329
|
setAccent(next) {
|
|
2306
2330
|
accent = next ?? void 0;
|
|
@@ -2332,13 +2356,13 @@ function attachGlass(el, opts = {}) {
|
|
|
2332
2356
|
apply();
|
|
2333
2357
|
},
|
|
2334
2358
|
setInterim(on) {
|
|
2335
|
-
if (destroyed
|
|
2359
|
+
if (destroyed) return;
|
|
2336
2360
|
if (on) {
|
|
2337
2361
|
interim = true;
|
|
2338
2362
|
keepInterim();
|
|
2339
2363
|
return;
|
|
2340
2364
|
}
|
|
2341
|
-
leaveInterim();
|
|
2365
|
+
if (interim) leaveInterim();
|
|
2342
2366
|
},
|
|
2343
2367
|
setSample(next) {
|
|
2344
2368
|
supplied = next ?? void 0;
|
|
@@ -2363,6 +2387,7 @@ exports.clearMapCache = clearMapCache;
|
|
|
2363
2387
|
exports.compositeLayers = compositeLayers;
|
|
2364
2388
|
exports.contrastRimCss = contrastRimCss;
|
|
2365
2389
|
exports.createGlassGroup = createGlassGroup;
|
|
2390
|
+
exports.displacementScale = displacementScale;
|
|
2366
2391
|
exports.hasSpectralEdge = hasSpectralEdge;
|
|
2367
2392
|
exports.lightConicAngle = lightConicAngle;
|
|
2368
2393
|
exports.mapCacheSize = mapCacheSize;
|