lensfx 0.1.1 → 0.1.3

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.js CHANGED
@@ -1268,6 +1268,418 @@ var QRRefractionRenderer = class {
1268
1268
  gl.deleteTexture(this.paintingColorTexture);
1269
1269
  }
1270
1270
  };
1271
+
1272
+ // src/createLens.ts
1273
+ var SVG_NS = "http://www.w3.org/2000/svg";
1274
+ var nextId = 0;
1275
+ function createLens(target, options = {}) {
1276
+ if (typeof document === "undefined") {
1277
+ throw new Error("[lensfx] createLens can only be called in a browser environment.");
1278
+ }
1279
+ const resolved = typeof target === "string" ? document.querySelector(target) : target;
1280
+ if (!resolved) {
1281
+ throw new Error(`[lensfx] Target element "${target}" was not found.`);
1282
+ }
1283
+ const container = resolved;
1284
+ let currentOptions = { ...options };
1285
+ let targetEl = container;
1286
+ if (currentOptions.refractionTarget) {
1287
+ const customTarget = typeof currentOptions.refractionTarget === "string" ? container.querySelector(currentOptions.refractionTarget) || document.querySelector(currentOptions.refractionTarget) : currentOptions.refractionTarget;
1288
+ if (customTarget) {
1289
+ targetEl = customTarget;
1290
+ }
1291
+ }
1292
+ const computedStyle = window.getComputedStyle(container);
1293
+ const originalPosition = container.style.position;
1294
+ if (computedStyle.position === "static") {
1295
+ container.style.position = "relative";
1296
+ }
1297
+ const lensId = `lensfx-${++nextId}-${Math.random().toString(36).substring(2, 7)}`;
1298
+ const filterId = `${lensId}-filter`;
1299
+ const svgEl = document.createElementNS(SVG_NS, "svg");
1300
+ svgEl.setAttribute("class", "lensfx-svg-root");
1301
+ svgEl.setAttribute("aria-hidden", "true");
1302
+ svgEl.style.cssText = "position:absolute;width:0;height:0;overflow:hidden;pointer-events:none;left:0;top:0;";
1303
+ const defsEl = document.createElementNS(SVG_NS, "defs");
1304
+ const filterEl = document.createElementNS(SVG_NS, "filter");
1305
+ filterEl.id = filterId;
1306
+ filterEl.setAttribute("filterUnits", "objectBoundingBox");
1307
+ filterEl.setAttribute("primitiveUnits", "objectBoundingBox");
1308
+ filterEl.setAttribute("color-interpolation-filters", "sRGB");
1309
+ filterEl.setAttribute("x", "0");
1310
+ filterEl.setAttribute("y", "0");
1311
+ filterEl.setAttribute("width", "1");
1312
+ filterEl.setAttribute("height", "1");
1313
+ defsEl.appendChild(filterEl);
1314
+ svgEl.appendChild(defsEl);
1315
+ container.appendChild(svgEl);
1316
+ const overlayEl = document.createElement("div");
1317
+ overlayEl.className = "lensfx-lens-overlay";
1318
+ overlayEl.style.cssText = "position:absolute;top:0;left:0;pointer-events:none;box-sizing:border-box;will-change:transform;";
1319
+ container.appendChild(overlayEl);
1320
+ let mapDataUrl = currentOptions.displacementMapUrl || "";
1321
+ let currentMapSize = currentOptions.mapSize || 256;
1322
+ let generator = createDisplacementMapGeneratorSync(currentMapSize);
1323
+ let feImageEl = null;
1324
+ let currentMaskKey = null;
1325
+ let isDestroyed = false;
1326
+ let curX = currentOptions.x ?? 0.5;
1327
+ let curY = currentOptions.y ?? 0.5;
1328
+ function getDimensions() {
1329
+ const width = currentOptions.width ?? currentOptions.lensW ?? 160;
1330
+ const height = currentOptions.height ?? currentOptions.lensH ?? 100;
1331
+ const radius = currentOptions.radius ?? currentOptions.borderRadius ?? Math.min(width, height) / 4;
1332
+ return { width, height, radius };
1333
+ }
1334
+ function renderMap() {
1335
+ if (isDestroyed) return;
1336
+ if (currentOptions.displacementMapUrl) {
1337
+ mapDataUrl = currentOptions.displacementMapUrl;
1338
+ if (feImageEl) feImageEl.setAttribute("href", mapDataUrl);
1339
+ currentOptions.onMapChange?.(mapDataUrl);
1340
+ return;
1341
+ }
1342
+ const { width, height, radius } = getDimensions();
1343
+ const mapSize = currentOptions.mapSize || 256;
1344
+ if (mapSize !== currentMapSize) {
1345
+ generator.dispose();
1346
+ currentMapSize = mapSize;
1347
+ generator = createDisplacementMapGeneratorSync(currentMapSize);
1348
+ }
1349
+ const depth = currentOptions.depth ?? 2;
1350
+ const result = generator.generate({
1351
+ lensHalfWidth: width / 2,
1352
+ lensHalfHeight: height / 2,
1353
+ borderRadius: radius,
1354
+ depth,
1355
+ sdfBoundary: currentOptions.sdfBoundary ?? true,
1356
+ edgeFalloff: currentOptions.edgeFalloff ?? true,
1357
+ specularRotation: currentOptions.specularRotation ?? 45,
1358
+ glowStrength: currentOptions.glowStrength ?? 0.3,
1359
+ glowSpread: currentOptions.glowSpread ?? 1,
1360
+ glowExponent: currentOptions.glowExponent ?? 1.5,
1361
+ edgeStrength: currentOptions.edgeStrength ?? 0.4,
1362
+ edgeWidth: currentOptions.edgeWidth ?? 3,
1363
+ edgeExponent: 1.5,
1364
+ domeDepth: currentOptions.domeDepth ?? 6,
1365
+ splayAmount: currentOptions.splayAmount ?? 0.8
1366
+ });
1367
+ mapDataUrl = result.dataUrl;
1368
+ if (feImageEl) feImageEl.setAttribute("href", mapDataUrl);
1369
+ currentOptions.onMapChange?.(mapDataUrl);
1370
+ }
1371
+ function buildFilterPipeline() {
1372
+ while (filterEl.firstChild) {
1373
+ filterEl.removeChild(filterEl.firstChild);
1374
+ }
1375
+ currentOptions.depth ?? 2;
1376
+ const chroma = currentOptions.chroma ?? currentOptions.chromaAmount ?? 0.6;
1377
+ const scaleX = currentOptions.scaleX ?? 0.25;
1378
+ const scaleY = currentOptions.scaleY ?? 0.25;
1379
+ const maxScale = Math.max(scaleX, scaleY);
1380
+ const scaleRatioX = maxScale > 0 ? scaleX / maxScale : 0;
1381
+ const scaleRatioY = maxScale > 0 ? scaleY / maxScale : 0;
1382
+ const needsMatrix = scaleRatioX !== 1 || scaleRatioY !== 1;
1383
+ const matrixString = colorMatrixValues(scaleRatioX, scaleRatioY);
1384
+ const feFlood = document.createElementNS(SVG_NS, "feFlood");
1385
+ feFlood.setAttribute("flood-color", "rgb(128,128,128)");
1386
+ feFlood.setAttribute("flood-opacity", "1");
1387
+ feFlood.setAttribute("result", "mapBg");
1388
+ filterEl.appendChild(feFlood);
1389
+ feImageEl = document.createElementNS(SVG_NS, "feImage");
1390
+ feImageEl.setAttribute("href", mapDataUrl);
1391
+ feImageEl.setAttribute("preserveAspectRatio", "none");
1392
+ feImageEl.setAttribute("result", "rawMap");
1393
+ filterEl.appendChild(feImageEl);
1394
+ const feComp = document.createElementNS(SVG_NS, "feComposite");
1395
+ feComp.setAttribute("in", "rawMap");
1396
+ feComp.setAttribute("in2", "mapBg");
1397
+ feComp.setAttribute("operator", "over");
1398
+ feComp.setAttribute("result", "map");
1399
+ filterEl.appendChild(feComp);
1400
+ if (needsMatrix) {
1401
+ const feMat = document.createElementNS(SVG_NS, "feColorMatrix");
1402
+ feMat.setAttribute("in", "map");
1403
+ feMat.setAttribute("type", "matrix");
1404
+ feMat.setAttribute("values", matrixString);
1405
+ feMat.setAttribute("result", "scaledMap");
1406
+ filterEl.appendChild(feMat);
1407
+ }
1408
+ const mapSource = needsMatrix ? "scaledMap" : "map";
1409
+ let sourceGraphic = "SourceGraphic";
1410
+ if (currentOptions.blur && currentOptions.blur > 0) {
1411
+ const feBlur = document.createElementNS(SVG_NS, "feGaussianBlur");
1412
+ feBlur.setAttribute("in", "SourceGraphic");
1413
+ const cRect = container.getBoundingClientRect();
1414
+ const bw = cRect.width > 0 ? currentOptions.blur / cRect.width : 0.01;
1415
+ const bh = cRect.height > 0 ? currentOptions.blur / cRect.height : 0.01;
1416
+ feBlur.setAttribute("stdDeviation", `${bw} ${bh}`);
1417
+ feBlur.setAttribute("result", "blurred");
1418
+ filterEl.appendChild(feBlur);
1419
+ sourceGraphic = "blurred";
1420
+ }
1421
+ if (chroma > 0) {
1422
+ const dispR = document.createElementNS(SVG_NS, "feDisplacementMap");
1423
+ dispR.setAttribute("in", sourceGraphic);
1424
+ dispR.setAttribute("in2", mapSource);
1425
+ dispR.setAttribute("scale", String(maxScale * (1 + 0.2 * chroma)));
1426
+ dispR.setAttribute("xChannelSelector", "R");
1427
+ dispR.setAttribute("yChannelSelector", "G");
1428
+ filterEl.appendChild(dispR);
1429
+ const matR = document.createElementNS(SVG_NS, "feColorMatrix");
1430
+ matR.setAttribute("type", "matrix");
1431
+ matR.setAttribute("values", "1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0");
1432
+ matR.setAttribute("result", "dispR");
1433
+ filterEl.appendChild(matR);
1434
+ const dispG = document.createElementNS(SVG_NS, "feDisplacementMap");
1435
+ dispG.setAttribute("in", sourceGraphic);
1436
+ dispG.setAttribute("in2", mapSource);
1437
+ dispG.setAttribute("scale", String(maxScale * (1 + 0.1 * chroma)));
1438
+ dispG.setAttribute("xChannelSelector", "R");
1439
+ dispG.setAttribute("yChannelSelector", "G");
1440
+ filterEl.appendChild(dispG);
1441
+ const matG = document.createElementNS(SVG_NS, "feColorMatrix");
1442
+ matG.setAttribute("type", "matrix");
1443
+ matG.setAttribute("values", "0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0");
1444
+ matG.setAttribute("result", "dispG");
1445
+ filterEl.appendChild(matG);
1446
+ const dispB = document.createElementNS(SVG_NS, "feDisplacementMap");
1447
+ dispB.setAttribute("in", sourceGraphic);
1448
+ dispB.setAttribute("in2", mapSource);
1449
+ dispB.setAttribute("scale", String(maxScale));
1450
+ dispB.setAttribute("xChannelSelector", "R");
1451
+ dispB.setAttribute("yChannelSelector", "G");
1452
+ filterEl.appendChild(dispB);
1453
+ const matB = document.createElementNS(SVG_NS, "feColorMatrix");
1454
+ matB.setAttribute("type", "matrix");
1455
+ matB.setAttribute("values", "0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0");
1456
+ matB.setAttribute("result", "dispB");
1457
+ filterEl.appendChild(matB);
1458
+ const compRG = document.createElementNS(SVG_NS, "feComposite");
1459
+ compRG.setAttribute("in", "dispR");
1460
+ compRG.setAttribute("in2", "dispG");
1461
+ compRG.setAttribute("operator", "arithmetic");
1462
+ compRG.setAttribute("k1", "0");
1463
+ compRG.setAttribute("k2", "1");
1464
+ compRG.setAttribute("k3", "1");
1465
+ compRG.setAttribute("k4", "0");
1466
+ filterEl.appendChild(compRG);
1467
+ const compRGB = document.createElementNS(SVG_NS, "feComposite");
1468
+ compRGB.setAttribute("in2", "dispB");
1469
+ compRGB.setAttribute("operator", "arithmetic");
1470
+ compRGB.setAttribute("k1", "0");
1471
+ compRGB.setAttribute("k2", "1");
1472
+ compRGB.setAttribute("k3", "1");
1473
+ compRGB.setAttribute("k4", "0");
1474
+ compRGB.setAttribute("result", "lensResult");
1475
+ filterEl.appendChild(compRGB);
1476
+ } else {
1477
+ const dispSingle = document.createElementNS(SVG_NS, "feDisplacementMap");
1478
+ dispSingle.setAttribute("in", sourceGraphic);
1479
+ dispSingle.setAttribute("in2", mapSource);
1480
+ dispSingle.setAttribute("scale", String(maxScale));
1481
+ dispSingle.setAttribute("xChannelSelector", "R");
1482
+ dispSingle.setAttribute("yChannelSelector", "G");
1483
+ dispSingle.setAttribute("result", "lensResult");
1484
+ filterEl.appendChild(dispSingle);
1485
+ }
1486
+ const specStrength = currentOptions.specularStrength ?? 1;
1487
+ if (specStrength > 0) {
1488
+ const specMat = document.createElementNS(SVG_NS, "feColorMatrix");
1489
+ specMat.setAttribute("in", "map");
1490
+ specMat.setAttribute("type", "matrix");
1491
+ specMat.setAttribute("values", "0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 1 0 -0.50196");
1492
+ specMat.setAttribute("result", "specMask");
1493
+ filterEl.appendChild(specMat);
1494
+ const specComp = document.createElementNS(SVG_NS, "feComposite");
1495
+ specComp.setAttribute("in", "specMask");
1496
+ specComp.setAttribute("in2", "lensResult");
1497
+ specComp.setAttribute("operator", "arithmetic");
1498
+ specComp.setAttribute("k1", "0");
1499
+ specComp.setAttribute("k2", String(specStrength));
1500
+ specComp.setAttribute("k3", "1");
1501
+ specComp.setAttribute("k4", "0");
1502
+ specComp.setAttribute("result", "lensResult");
1503
+ filterEl.appendChild(specComp);
1504
+ }
1505
+ const lensMask = document.createElementNS(SVG_NS, "feFlood");
1506
+ lensMask.setAttribute("flood-color", "black");
1507
+ lensMask.setAttribute("flood-opacity", "1");
1508
+ lensMask.setAttribute("result", "lensMask");
1509
+ filterEl.appendChild(lensMask);
1510
+ const holeComp = document.createElementNS(SVG_NS, "feComposite");
1511
+ holeComp.setAttribute("in", "SourceGraphic");
1512
+ holeComp.setAttribute("in2", "lensMask");
1513
+ holeComp.setAttribute("operator", "out");
1514
+ holeComp.setAttribute("result", "holedSG");
1515
+ filterEl.appendChild(holeComp);
1516
+ const finalComp = document.createElementNS(SVG_NS, "feComposite");
1517
+ finalComp.setAttribute("in", "lensResult");
1518
+ finalComp.setAttribute("in2", "holedSG");
1519
+ finalComp.setAttribute("operator", "over");
1520
+ filterEl.appendChild(finalComp);
1521
+ }
1522
+ function updateLayout() {
1523
+ if (isDestroyed) return;
1524
+ const rect = container.getBoundingClientRect();
1525
+ const cw = rect.width || container.clientWidth || 300;
1526
+ const ch = rect.height || container.clientHeight || 200;
1527
+ const { width, height, radius } = getDimensions();
1528
+ const px = curX <= 1 && curX >= 0 ? curX * cw : curX;
1529
+ const py = curY <= 1 && curY >= 0 ? curY * ch : pyCoordinate(curY, ch);
1530
+ function pyCoordinate(val, containerH) {
1531
+ return val <= 1 && val >= 0 ? val * containerH : val;
1532
+ }
1533
+ const lensLeft = px - width / 2;
1534
+ const lensTop = py - height / 2;
1535
+ if (feImageEl && cw > 0 && ch > 0) {
1536
+ feImageEl.setAttribute("x", String(lensLeft / cw));
1537
+ feImageEl.setAttribute("y", String(lensTop / ch));
1538
+ feImageEl.setAttribute("width", String(width / cw));
1539
+ feImageEl.setAttribute("height", String(height / ch));
1540
+ }
1541
+ overlayEl.style.width = `${width}px`;
1542
+ overlayEl.style.height = `${height}px`;
1543
+ overlayEl.style.borderRadius = `${radius}px`;
1544
+ overlayEl.style.transform = `translate3d(${lensLeft}px, ${lensTop}px, 0)`;
1545
+ if (currentOptions.shadow === false) {
1546
+ overlayEl.style.boxShadow = "none";
1547
+ } else if (typeof currentOptions.shadow === "string") {
1548
+ overlayEl.style.boxShadow = currentOptions.shadow;
1549
+ } else {
1550
+ overlayEl.style.boxShadow = "0 8px 32px rgba(0, 0, 0, 0.2), 0 2px 8px rgba(0, 0, 0, 0.12), inset 0 1px 1px rgba(255, 255, 255, 0.35), inset 0 -1px 1px rgba(0, 0, 0, 0.15)";
1551
+ }
1552
+ if (currentOptions.border === false) {
1553
+ overlayEl.style.border = "none";
1554
+ } else if (typeof currentOptions.border === "string") {
1555
+ overlayEl.style.border = currentOptions.border;
1556
+ } else {
1557
+ overlayEl.style.border = "1px solid rgba(255, 255, 255, 0.2)";
1558
+ }
1559
+ const tintColor = currentOptions.tintColor ?? "rgba(255, 255, 255, 0.05)";
1560
+ overlayEl.style.background = tintColor;
1561
+ const tintBlur = currentOptions.tintBlur ?? 0;
1562
+ if (tintBlur > 0) {
1563
+ const blurCss = `blur(${tintBlur}px)`;
1564
+ overlayEl.style.backdropFilter = blurCss;
1565
+ overlayEl.style.setProperty("-webkit-backdrop-filter", blurCss);
1566
+ } else {
1567
+ overlayEl.style.backdropFilter = "none";
1568
+ overlayEl.style.setProperty("-webkit-backdrop-filter", "none");
1569
+ }
1570
+ const { uri, key } = getSvgMaskDataUri(width, height, radius);
1571
+ if (currentMaskKey !== key) {
1572
+ const maskCss = `url("${uri}")`;
1573
+ overlayEl.style.maskImage = maskCss;
1574
+ overlayEl.style.setProperty("-webkit-mask-image", maskCss);
1575
+ overlayEl.style.maskSize = "100% 100%";
1576
+ overlayEl.style.setProperty("-webkit-mask-size", "100% 100%");
1577
+ currentMaskKey = key;
1578
+ }
1579
+ const filterStyle = `url("#${filterId}")`;
1580
+ if (targetEl.style.filter !== filterStyle) {
1581
+ targetEl.style.filter = filterStyle;
1582
+ }
1583
+ }
1584
+ let isPointerActive = false;
1585
+ const onPointerMove = (e) => {
1586
+ if (!currentOptions.interactive || isDestroyed) return;
1587
+ const rect = container.getBoundingClientRect();
1588
+ const x = e.clientX - rect.left;
1589
+ const y = e.clientY - rect.top;
1590
+ setPosition(x, y);
1591
+ };
1592
+ function setInteractive(interactive) {
1593
+ currentOptions.interactive = interactive;
1594
+ if (interactive && !isPointerActive) {
1595
+ container.addEventListener("pointermove", onPointerMove);
1596
+ isPointerActive = true;
1597
+ } else if (!interactive && isPointerActive) {
1598
+ container.removeEventListener("pointermove", onPointerMove);
1599
+ isPointerActive = false;
1600
+ }
1601
+ }
1602
+ function setPosition(x, y) {
1603
+ curX = x;
1604
+ curY = y;
1605
+ currentOptions.onPositionChange?.(x, y);
1606
+ updateLayout();
1607
+ }
1608
+ function update(newOptions) {
1609
+ if (isDestroyed) return;
1610
+ ({ ...currentOptions });
1611
+ currentOptions = { ...currentOptions, ...newOptions };
1612
+ if (newOptions.x !== void 0) curX = newOptions.x;
1613
+ if (newOptions.y !== void 0) curY = newOptions.y;
1614
+ if (newOptions.interactive !== void 0) {
1615
+ setInteractive(newOptions.interactive);
1616
+ }
1617
+ const needsRegen = newOptions.width !== void 0 || newOptions.lensW !== void 0 || newOptions.height !== void 0 || newOptions.lensH !== void 0 || newOptions.radius !== void 0 || newOptions.borderRadius !== void 0 || newOptions.depth !== void 0 || newOptions.domeDepth !== void 0 || newOptions.specularRotation !== void 0 || newOptions.glowStrength !== void 0 || newOptions.glowSpread !== void 0 || newOptions.edgeStrength !== void 0 || newOptions.edgeWidth !== void 0 || newOptions.splayAmount !== void 0 || newOptions.displacementMapUrl !== void 0 || newOptions.mapSize !== void 0;
1618
+ if (needsRegen) {
1619
+ renderMap();
1620
+ }
1621
+ const needsPipelineRebuild = newOptions.chroma !== void 0 || newOptions.chromaAmount !== void 0 || newOptions.scaleX !== void 0 || newOptions.scaleY !== void 0 || newOptions.specularStrength !== void 0 || newOptions.blur !== void 0;
1622
+ if (needsPipelineRebuild || needsRegen) {
1623
+ buildFilterPipeline();
1624
+ }
1625
+ updateLayout();
1626
+ }
1627
+ let resizeObserver = null;
1628
+ if (typeof ResizeObserver !== "undefined") {
1629
+ resizeObserver = new ResizeObserver(() => {
1630
+ updateLayout();
1631
+ });
1632
+ resizeObserver.observe(container);
1633
+ }
1634
+ renderMap();
1635
+ buildFilterPipeline();
1636
+ updateLayout();
1637
+ if (currentOptions.interactive) {
1638
+ setInteractive(true);
1639
+ }
1640
+ function destroy() {
1641
+ if (isDestroyed) return;
1642
+ isDestroyed = true;
1643
+ if (isPointerActive) {
1644
+ container.removeEventListener("pointermove", onPointerMove);
1645
+ isPointerActive = false;
1646
+ }
1647
+ if (resizeObserver) {
1648
+ resizeObserver.disconnect();
1649
+ resizeObserver = null;
1650
+ }
1651
+ if (targetEl.style.filter.includes(filterId)) {
1652
+ targetEl.style.filter = "";
1653
+ }
1654
+ if (container.style.position === "relative" && originalPosition === "") {
1655
+ container.style.position = "";
1656
+ }
1657
+ if (svgEl.parentNode) {
1658
+ svgEl.parentNode.removeChild(svgEl);
1659
+ }
1660
+ if (overlayEl.parentNode) {
1661
+ overlayEl.parentNode.removeChild(overlayEl);
1662
+ }
1663
+ generator.dispose();
1664
+ }
1665
+ return {
1666
+ el: container,
1667
+ targetEl,
1668
+ svgEl,
1669
+ filterEl,
1670
+ overlayEl,
1671
+ get options() {
1672
+ return currentOptions;
1673
+ },
1674
+ get mapDataUrl() {
1675
+ return mapDataUrl;
1676
+ },
1677
+ setPosition,
1678
+ update,
1679
+ setInteractive,
1680
+ destroy
1681
+ };
1682
+ }
1271
1683
  function isMotionValue(val) {
1272
1684
  return typeof val === "object" && val !== null && "get" in val && "on" in val;
1273
1685
  }
@@ -3257,6 +3669,7 @@ exports.VideoRefractionRenderer = VideoRefractionRenderer;
3257
3669
  exports.colorMatrixValues = colorMatrixValues;
3258
3670
  exports.computeDomeConstants = computeDomeConstants;
3259
3671
  exports.createDisplacementMapGeneratorSync = createDisplacementMapGeneratorSync;
3672
+ exports.createLens = createLens;
3260
3673
  exports.domeGradient = domeGradient;
3261
3674
  exports.erf = erf;
3262
3675
  exports.generateDisplacementMapAsync = generateDisplacementMapAsync;