vireglass 3.1.1 → 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 +5 -1
- package/dist/dom.cjs +41 -16
- package/dist/dom.cjs.map +1 -1
- package/dist/dom.d.cts +6 -1
- package/dist/dom.d.ts +6 -1
- package/dist/dom.js +42 -18
- package/dist/dom.js.map +1 -1
- package/package.json +1 -1
- package/src/dom/displacement.ts +9 -2
- package/src/dom/index.ts +46 -16
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,7 +172,7 @@ 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
178
|
`sample` it does not probe). Repeating `setInterim(false)` is a no-op.
|
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;
|
|
@@ -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;
|