levita-js 0.1.5 → 0.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/dist/index.cjs +62 -29
- package/dist/index.d.cts +21 -1
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +21 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +62 -29
- package/dist/index.mjs.map +1 -1
- package/dist/style.css +1 -1
- package/package.json +2 -1
package/dist/index.cjs
CHANGED
|
@@ -192,6 +192,8 @@ var MotionSensor = class MotionSensor {
|
|
|
192
192
|
smoothing;
|
|
193
193
|
lastX = 0;
|
|
194
194
|
lastY = 0;
|
|
195
|
+
baseBeta = null;
|
|
196
|
+
baseGamma = null;
|
|
195
197
|
/**
|
|
196
198
|
* @param onMove - Callback receiving normalized { x, y } values
|
|
197
199
|
* @param axis - Restrict input to a single axis, or null for both
|
|
@@ -243,6 +245,8 @@ var MotionSensor = class MotionSensor {
|
|
|
243
245
|
start = () => {
|
|
244
246
|
if (this.active || !this.permitted) return;
|
|
245
247
|
this.active = true;
|
|
248
|
+
this.baseBeta = null;
|
|
249
|
+
this.baseGamma = null;
|
|
246
250
|
window.addEventListener("deviceorientation", this.handleOrientation);
|
|
247
251
|
};
|
|
248
252
|
/** Stop listening and remove the deviceorientation event listener. */
|
|
@@ -266,9 +270,15 @@ var MotionSensor = class MotionSensor {
|
|
|
266
270
|
const evt = e;
|
|
267
271
|
const beta = evt.beta ?? 0;
|
|
268
272
|
const gamma = evt.gamma ?? 0;
|
|
273
|
+
if (this.baseBeta === null) {
|
|
274
|
+
this.baseBeta = beta;
|
|
275
|
+
this.baseGamma = gamma;
|
|
276
|
+
}
|
|
277
|
+
const relativeBeta = beta - this.baseBeta;
|
|
278
|
+
const relativeGamma = gamma - this.baseGamma;
|
|
269
279
|
const range = this.maxAngle - this.minAngle;
|
|
270
|
-
const rawX = this.axis === "y" ? 0 : this.clamp(
|
|
271
|
-
const rawY = this.axis === "x" ? 0 : this.clamp(
|
|
280
|
+
const rawX = this.axis === "y" ? 0 : this.clamp(relativeGamma / (range / 2));
|
|
281
|
+
const rawY = this.axis === "x" ? 0 : this.clamp(relativeBeta / (range / 2));
|
|
272
282
|
this.lastX = this.lastX + (rawX - this.lastX) * this.smoothing;
|
|
273
283
|
this.lastY = this.lastY + (rawY - this.lastY) * this.smoothing;
|
|
274
284
|
this.onMove({
|
|
@@ -298,7 +308,6 @@ var PointerSensor = class {
|
|
|
298
308
|
axis;
|
|
299
309
|
active = false;
|
|
300
310
|
rect = null;
|
|
301
|
-
isPointerDown = false;
|
|
302
311
|
constructor(el, onMove, onEnter, onLeave, axis, eventsEl = null) {
|
|
303
312
|
this.eventsEl = eventsEl ?? el;
|
|
304
313
|
this.onMove = onMove;
|
|
@@ -333,14 +342,10 @@ var PointerSensor = class {
|
|
|
333
342
|
this.axis = axis;
|
|
334
343
|
};
|
|
335
344
|
handlePointerDown = (e) => {
|
|
336
|
-
this.isPointerDown = true;
|
|
337
|
-
this.eventsEl.setPointerCapture(e.pointerId);
|
|
338
345
|
this.handlePointerEnter();
|
|
339
346
|
this.handlePointerMove(e);
|
|
340
347
|
};
|
|
341
|
-
handlePointerUp = (
|
|
342
|
-
this.isPointerDown = false;
|
|
343
|
-
this.eventsEl.releasePointerCapture(e.pointerId);
|
|
348
|
+
handlePointerUp = () => {
|
|
344
349
|
this.handlePointerLeave();
|
|
345
350
|
};
|
|
346
351
|
/**
|
|
@@ -349,13 +354,10 @@ var PointerSensor = class {
|
|
|
349
354
|
*/
|
|
350
355
|
handlePointerMove = (e) => {
|
|
351
356
|
if (!this.rect) this.rect = this.eventsEl.getBoundingClientRect();
|
|
352
|
-
|
|
353
|
-
const
|
|
354
|
-
const
|
|
355
|
-
|
|
356
|
-
this.handlePointerLeave();
|
|
357
|
-
return;
|
|
358
|
-
}
|
|
357
|
+
const width = this.eventsEl.offsetWidth || this.rect.width;
|
|
358
|
+
const height = this.eventsEl.offsetHeight || this.rect.height;
|
|
359
|
+
const rawX = (e.clientX - this.rect.left) / width;
|
|
360
|
+
const rawY = (e.clientY - this.rect.top) / height;
|
|
359
361
|
const clampedX = Math.max(0, Math.min(1, rawX));
|
|
360
362
|
const clampedY = Math.max(0, Math.min(1, rawY));
|
|
361
363
|
const x = this.axis === "y" ? 0 : clampedX * 2 - 1;
|
|
@@ -404,6 +406,7 @@ var Levita = class {
|
|
|
404
406
|
listeners = /* @__PURE__ */ new Map();
|
|
405
407
|
destroyed = false;
|
|
406
408
|
gyroscopeRequested = false;
|
|
409
|
+
rafId = null;
|
|
407
410
|
/**
|
|
408
411
|
* @param el - The DOM element to apply the tilt effect to
|
|
409
412
|
* @param options - Configuration options (all optional, sensible defaults)
|
|
@@ -439,6 +442,23 @@ var Levita = class {
|
|
|
439
442
|
this.motionSensor.start();
|
|
440
443
|
}
|
|
441
444
|
};
|
|
445
|
+
/**
|
|
446
|
+
* Update options at runtime without destroying/recreating the instance.
|
|
447
|
+
*
|
|
448
|
+
* Only "lightweight" options are supported — those that don't require
|
|
449
|
+
* DOM mutations. Options like `glare`, `shadow`, `gyroscope`, `eventsEl`,
|
|
450
|
+
* `disabled`, and `maxGlare` require a full destroy/recreate cycle
|
|
451
|
+
* (or use `enable()`/`disable()` for toggling the effect).
|
|
452
|
+
*/
|
|
453
|
+
update = (options) => {
|
|
454
|
+
if (this.destroyed) return;
|
|
455
|
+
Object.assign(this.options, options);
|
|
456
|
+
this.applyBaseProperties();
|
|
457
|
+
if (options.axis !== void 0) {
|
|
458
|
+
this.pointerSensor.setAxis(options.axis);
|
|
459
|
+
this.motionSensor?.setAxis(options.axis);
|
|
460
|
+
}
|
|
461
|
+
};
|
|
442
462
|
/** Re-enable the tilt effect after a `disable()` call. */
|
|
443
463
|
enable = () => {
|
|
444
464
|
if (this.destroyed) return;
|
|
@@ -475,6 +495,7 @@ var Levita = class {
|
|
|
475
495
|
destroy = () => {
|
|
476
496
|
if (this.destroyed) return;
|
|
477
497
|
this.destroyed = true;
|
|
498
|
+
if (this.rafId) cancelAnimationFrame(this.rafId);
|
|
478
499
|
this.pointerSensor.stop();
|
|
479
500
|
this.motionSensor?.stop();
|
|
480
501
|
this.glareEffect?.destroy();
|
|
@@ -512,26 +533,34 @@ var Levita = class {
|
|
|
512
533
|
/**
|
|
513
534
|
* Process normalized sensor input and update CSS custom properties.
|
|
514
535
|
* Maps sensor X/Y to rotateY/rotateX (swapped, rotateX = vertical tilt).
|
|
536
|
+
*
|
|
537
|
+
* Note: We use requestAnimationFrame to throttle updates. High-polling rate mice
|
|
538
|
+
* can fire hundreds of events per second, which would saturate the main thread
|
|
539
|
+
* with CSS variable updates and style recalculations. This ensures we only
|
|
540
|
+
* update the DOM once per browser frame.
|
|
515
541
|
*/
|
|
516
542
|
handleSensorInput = (input) => {
|
|
517
543
|
if (this.options.disabled) return;
|
|
518
544
|
const multiplier = this.options.reverse ? -1 : 1;
|
|
519
545
|
const x = input.y * this.options.max * multiplier;
|
|
520
546
|
const y = input.x * this.options.max * multiplier * -1;
|
|
521
|
-
this.
|
|
522
|
-
this.
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
547
|
+
if (this.rafId) cancelAnimationFrame(this.rafId);
|
|
548
|
+
this.rafId = requestAnimationFrame(() => {
|
|
549
|
+
this.el.style.setProperty("--levita-x", `${x}deg`);
|
|
550
|
+
this.el.style.setProperty("--levita-y", `${y}deg`);
|
|
551
|
+
this.el.style.setProperty("--levita-percent-x", String(input.x));
|
|
552
|
+
this.el.style.setProperty("--levita-percent-y", String(input.y));
|
|
553
|
+
this.glareEffect?.update(input.x, input.y);
|
|
554
|
+
this.shadowEffect?.update(input.x, input.y);
|
|
555
|
+
const values = {
|
|
556
|
+
x,
|
|
557
|
+
y,
|
|
558
|
+
percentX: input.x,
|
|
559
|
+
percentY: input.y
|
|
560
|
+
};
|
|
561
|
+
this.emit("move", values);
|
|
562
|
+
this.rafId = null;
|
|
563
|
+
});
|
|
535
564
|
};
|
|
536
565
|
handleEnter = () => {
|
|
537
566
|
this.setTransition(false);
|
|
@@ -539,6 +568,10 @@ var Levita = class {
|
|
|
539
568
|
this.emit("enter", void 0);
|
|
540
569
|
};
|
|
541
570
|
handleLeave = () => {
|
|
571
|
+
if (this.rafId) {
|
|
572
|
+
cancelAnimationFrame(this.rafId);
|
|
573
|
+
this.rafId = null;
|
|
574
|
+
}
|
|
542
575
|
this.setTransition(true);
|
|
543
576
|
if (this.options.reset) this.resetTransform();
|
|
544
577
|
this.emit("leave", void 0);
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
//#region src/types.d.ts
|
|
2
2
|
type Axis = "x" | "y" | null;
|
|
3
3
|
type GyroscopeMode = "auto" | boolean;
|
|
4
|
+
/**
|
|
5
|
+
* Options that can be updated at runtime via `update()`.
|
|
6
|
+
* Limited to lightweight options that don't require DOM mutations.
|
|
7
|
+
*/
|
|
8
|
+
type UpdatableOptions = Pick<LevitaOptions, "max" | "scale" | "speed" | "easing" | "perspective" | "reverse" | "axis" | "reset">;
|
|
4
9
|
interface LevitaOptions {
|
|
5
10
|
/** Max tilt angle in degrees. Default: 15 */
|
|
6
11
|
max: number;
|
|
@@ -86,6 +91,7 @@ declare class Levita {
|
|
|
86
91
|
private listeners;
|
|
87
92
|
private destroyed;
|
|
88
93
|
private gyroscopeRequested;
|
|
94
|
+
private rafId;
|
|
89
95
|
/**
|
|
90
96
|
* @param el - The DOM element to apply the tilt effect to
|
|
91
97
|
* @param options - Configuration options (all optional, sensible defaults)
|
|
@@ -96,6 +102,15 @@ declare class Levita {
|
|
|
96
102
|
* and switch from pointer to motion sensor if granted.
|
|
97
103
|
*/
|
|
98
104
|
private handleFirstTouch;
|
|
105
|
+
/**
|
|
106
|
+
* Update options at runtime without destroying/recreating the instance.
|
|
107
|
+
*
|
|
108
|
+
* Only "lightweight" options are supported — those that don't require
|
|
109
|
+
* DOM mutations. Options like `glare`, `shadow`, `gyroscope`, `eventsEl`,
|
|
110
|
+
* `disabled`, and `maxGlare` require a full destroy/recreate cycle
|
|
111
|
+
* (or use `enable()`/`disable()` for toggling the effect).
|
|
112
|
+
*/
|
|
113
|
+
update: (options: Partial<UpdatableOptions>) => void;
|
|
99
114
|
/** Re-enable the tilt effect after a `disable()` call. */
|
|
100
115
|
enable: () => void;
|
|
101
116
|
/** Pause the tilt effect and reset the element to its neutral position. */
|
|
@@ -131,6 +146,11 @@ declare class Levita {
|
|
|
131
146
|
/**
|
|
132
147
|
* Process normalized sensor input and update CSS custom properties.
|
|
133
148
|
* Maps sensor X/Y to rotateY/rotateX (swapped, rotateX = vertical tilt).
|
|
149
|
+
*
|
|
150
|
+
* Note: We use requestAnimationFrame to throttle updates. High-polling rate mice
|
|
151
|
+
* can fire hundreds of events per second, which would saturate the main thread
|
|
152
|
+
* with CSS variable updates and style recalculations. This ensures we only
|
|
153
|
+
* update the DOM once per browser frame.
|
|
134
154
|
*/
|
|
135
155
|
private handleSensorInput;
|
|
136
156
|
private handleEnter;
|
|
@@ -145,5 +165,5 @@ declare class Levita {
|
|
|
145
165
|
private removeBaseProperties;
|
|
146
166
|
}
|
|
147
167
|
//#endregion
|
|
148
|
-
export { type Axis, DEFAULT_OPTIONS, type EventCallback, type GyroscopeMode, Levita, type LevitaEventMap, type LevitaOptions, OPTION_KEYS, type TiltValues, buildOptions };
|
|
168
|
+
export { type Axis, DEFAULT_OPTIONS, type EventCallback, type GyroscopeMode, Levita, type LevitaEventMap, type LevitaOptions, OPTION_KEYS, type TiltValues, type UpdatableOptions, buildOptions };
|
|
149
169
|
//# sourceMappingURL=index.d.cts.map
|
package/dist/index.d.cts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/types.ts","../src/constants.ts","../src/levita.ts"],"mappings":";KAAY,IAAA;AAAA,KACA,aAAA;AAAA,
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../src/types.ts","../src/constants.ts","../src/levita.ts"],"mappings":";KAAY,IAAA;AAAA,KACA,aAAA;;;;;KAMA,gBAAA,GAAmB,IAAA,CAC9B,aAAA;AAAA,UAIgB,aAAA;;EAEhB,GAAA;EAbwB;EAexB,WAAA;EAT2B;EAW3B,KAAA;EAX8B;EAa9B,KAAA;EARgB;EAUhB,MAAA;;EAEA,OAAA;EAYW;EAVX,IAAA,EAAM,IAAA;EAce;EAZrB,KAAA;EAdA;EAgBA,KAAA;EAZA;EAcA,QAAA;EAVA;EAYA,MAAA;EARA;EAUA,SAAA,EAAW,aAAA;EARX;EAUA,QAAA;EANA;EAQA,QAAA,EAAU,WAAA;AAAA;AAAA,UAGM,UAAA;EALhB;EAOA,CAAA;EALU;EAOV,CAAA;EAPqB;EASrB,QAAA;EAN0B;EAQ1B,QAAA;AAAA;AAAA,UAGgB,cAAA;EAChB,IAAA,EAAM,UAAA;EACN,KAAA;EACA,KAAA;AAAA;AAAA,KAGW,aAAA,OAAoB,IAAA,EAAM,CAAA;;;AA5DtC;AAAA,cCGa,WAAA,kBAA6B,aAAA;;;;ADF1C;cCuBa,YAAA,GAAgB,MAAA,EAAQ,OAAA,CAAQ,aAAA,MAAiB,OAAA,CAAQ,aAAA;AAAA,cAWzD,eAAA,EAAiB,aAAA;;;ADnC9B;;;;;AACA;;;;;AAMA;;;;;AAKA;AAZA,cEgCa,MAAA;EAAA,QACJ,EAAA;EAAA,QACA,OAAA;EAAA,QACA,aAAA;EAAA,QACA,YAAA;EAAA,QACA,WAAA;EAAA,QACA,YAAA;EAAA,QACA,MAAA;EAAA,QACA,SAAA;EAAA,QACA,SAAA;EAAA,QACA,kBAAA;EAAA,QACA,KAAA;EFnBR;;;;cEyBY,EAAA,EAAI,WAAA,EAAa,OAAA,GAAS,OAAA,CAAQ,aAAA;EFjB9C;;;;EAAA,QE8DQ,gBAAA;EFtDR;;;;AAGD;;;;EEuEC,MAAA,GAAU,OAAA,EAAS,OAAA,CAAQ,gBAAA;EFnE3B;EE+EA,MAAA;EF3EA;EEkFA,OAAA;EFlFQ;AAGT;;;;;EE4FC,iBAAA,QAA8B,OAAA;EF1F9B;;;;EEyGA,OAAA;EFrGwB;;;;;;EE6HxB,EAAA,mBAAsB,cAAA,EACrB,KAAA,EAAO,CAAA,EACP,QAAA,EAAU,aAAA,CAAc,cAAA,CAAe,CAAA;EF/HF;;;;ACzDvC;;ECsMC,GAAA,mBAAuB,cAAA,EACtB,KAAA,EAAO,CAAA,EACP,QAAA,EAAU,aAAA,CAAc,cAAA,CAAe,CAAA;EDxMC;EAAA,QC8MjC,IAAA;EDzLI;;;;;;;;;EAAA,QC2MJ,iBAAA;EAAA,QA+BA,WAAA;EAAA,QAMA,WAAA;EDhPqD;EAAA,QC6PrD,aAAA;ED7P0E;EAAA,QCkQ1E,cAAA;EDvPI;EAAA,QCkQJ,mBAAA;;UAOA,oBAAA;AAAA"}
|
package/dist/index.d.mts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
//#region src/types.d.ts
|
|
2
2
|
type Axis = "x" | "y" | null;
|
|
3
3
|
type GyroscopeMode = "auto" | boolean;
|
|
4
|
+
/**
|
|
5
|
+
* Options that can be updated at runtime via `update()`.
|
|
6
|
+
* Limited to lightweight options that don't require DOM mutations.
|
|
7
|
+
*/
|
|
8
|
+
type UpdatableOptions = Pick<LevitaOptions, "max" | "scale" | "speed" | "easing" | "perspective" | "reverse" | "axis" | "reset">;
|
|
4
9
|
interface LevitaOptions {
|
|
5
10
|
/** Max tilt angle in degrees. Default: 15 */
|
|
6
11
|
max: number;
|
|
@@ -86,6 +91,7 @@ declare class Levita {
|
|
|
86
91
|
private listeners;
|
|
87
92
|
private destroyed;
|
|
88
93
|
private gyroscopeRequested;
|
|
94
|
+
private rafId;
|
|
89
95
|
/**
|
|
90
96
|
* @param el - The DOM element to apply the tilt effect to
|
|
91
97
|
* @param options - Configuration options (all optional, sensible defaults)
|
|
@@ -96,6 +102,15 @@ declare class Levita {
|
|
|
96
102
|
* and switch from pointer to motion sensor if granted.
|
|
97
103
|
*/
|
|
98
104
|
private handleFirstTouch;
|
|
105
|
+
/**
|
|
106
|
+
* Update options at runtime without destroying/recreating the instance.
|
|
107
|
+
*
|
|
108
|
+
* Only "lightweight" options are supported — those that don't require
|
|
109
|
+
* DOM mutations. Options like `glare`, `shadow`, `gyroscope`, `eventsEl`,
|
|
110
|
+
* `disabled`, and `maxGlare` require a full destroy/recreate cycle
|
|
111
|
+
* (or use `enable()`/`disable()` for toggling the effect).
|
|
112
|
+
*/
|
|
113
|
+
update: (options: Partial<UpdatableOptions>) => void;
|
|
99
114
|
/** Re-enable the tilt effect after a `disable()` call. */
|
|
100
115
|
enable: () => void;
|
|
101
116
|
/** Pause the tilt effect and reset the element to its neutral position. */
|
|
@@ -131,6 +146,11 @@ declare class Levita {
|
|
|
131
146
|
/**
|
|
132
147
|
* Process normalized sensor input and update CSS custom properties.
|
|
133
148
|
* Maps sensor X/Y to rotateY/rotateX (swapped, rotateX = vertical tilt).
|
|
149
|
+
*
|
|
150
|
+
* Note: We use requestAnimationFrame to throttle updates. High-polling rate mice
|
|
151
|
+
* can fire hundreds of events per second, which would saturate the main thread
|
|
152
|
+
* with CSS variable updates and style recalculations. This ensures we only
|
|
153
|
+
* update the DOM once per browser frame.
|
|
134
154
|
*/
|
|
135
155
|
private handleSensorInput;
|
|
136
156
|
private handleEnter;
|
|
@@ -145,5 +165,5 @@ declare class Levita {
|
|
|
145
165
|
private removeBaseProperties;
|
|
146
166
|
}
|
|
147
167
|
//#endregion
|
|
148
|
-
export { type Axis, DEFAULT_OPTIONS, type EventCallback, type GyroscopeMode, Levita, type LevitaEventMap, type LevitaOptions, OPTION_KEYS, type TiltValues, buildOptions };
|
|
168
|
+
export { type Axis, DEFAULT_OPTIONS, type EventCallback, type GyroscopeMode, Levita, type LevitaEventMap, type LevitaOptions, OPTION_KEYS, type TiltValues, type UpdatableOptions, buildOptions };
|
|
149
169
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/constants.ts","../src/levita.ts"],"mappings":";KAAY,IAAA;AAAA,KACA,aAAA;AAAA,
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/constants.ts","../src/levita.ts"],"mappings":";KAAY,IAAA;AAAA,KACA,aAAA;;;;;KAMA,gBAAA,GAAmB,IAAA,CAC9B,aAAA;AAAA,UAIgB,aAAA;;EAEhB,GAAA;EAbwB;EAexB,WAAA;EAT2B;EAW3B,KAAA;EAX8B;EAa9B,KAAA;EARgB;EAUhB,MAAA;;EAEA,OAAA;EAYW;EAVX,IAAA,EAAM,IAAA;EAce;EAZrB,KAAA;EAdA;EAgBA,KAAA;EAZA;EAcA,QAAA;EAVA;EAYA,MAAA;EARA;EAUA,SAAA,EAAW,aAAA;EARX;EAUA,QAAA;EANA;EAQA,QAAA,EAAU,WAAA;AAAA;AAAA,UAGM,UAAA;EALhB;EAOA,CAAA;EALU;EAOV,CAAA;EAPqB;EASrB,QAAA;EAN0B;EAQ1B,QAAA;AAAA;AAAA,UAGgB,cAAA;EAChB,IAAA,EAAM,UAAA;EACN,KAAA;EACA,KAAA;AAAA;AAAA,KAGW,aAAA,OAAoB,IAAA,EAAM,CAAA;;;AA5DtC;AAAA,cCGa,WAAA,kBAA6B,aAAA;;;;ADF1C;cCuBa,YAAA,GAAgB,MAAA,EAAQ,OAAA,CAAQ,aAAA,MAAiB,OAAA,CAAQ,aAAA;AAAA,cAWzD,eAAA,EAAiB,aAAA;;;ADnC9B;;;;;AACA;;;;;AAMA;;;;;AAKA;AAZA,cEgCa,MAAA;EAAA,QACJ,EAAA;EAAA,QACA,OAAA;EAAA,QACA,aAAA;EAAA,QACA,YAAA;EAAA,QACA,WAAA;EAAA,QACA,YAAA;EAAA,QACA,MAAA;EAAA,QACA,SAAA;EAAA,QACA,SAAA;EAAA,QACA,kBAAA;EAAA,QACA,KAAA;EFnBR;;;;cEyBY,EAAA,EAAI,WAAA,EAAa,OAAA,GAAS,OAAA,CAAQ,aAAA;EFjB9C;;;;EAAA,QE8DQ,gBAAA;EFtDR;;;;AAGD;;;;EEuEC,MAAA,GAAU,OAAA,EAAS,OAAA,CAAQ,gBAAA;EFnE3B;EE+EA,MAAA;EF3EA;EEkFA,OAAA;EFlFQ;AAGT;;;;;EE4FC,iBAAA,QAA8B,OAAA;EF1F9B;;;;EEyGA,OAAA;EFrGwB;;;;;;EE6HxB,EAAA,mBAAsB,cAAA,EACrB,KAAA,EAAO,CAAA,EACP,QAAA,EAAU,aAAA,CAAc,cAAA,CAAe,CAAA;EF/HF;;;;ACzDvC;;ECsMC,GAAA,mBAAuB,cAAA,EACtB,KAAA,EAAO,CAAA,EACP,QAAA,EAAU,aAAA,CAAc,cAAA,CAAe,CAAA;EDxMC;EAAA,QC8MjC,IAAA;EDzLI;;;;;;;;;EAAA,QC2MJ,iBAAA;EAAA,QA+BA,WAAA;EAAA,QAMA,WAAA;EDhPqD;EAAA,QC6PrD,aAAA;ED7P0E;EAAA,QCkQ1E,cAAA;EDvPI;EAAA,QCkQJ,mBAAA;;UAOA,oBAAA;AAAA"}
|
package/dist/index.mjs
CHANGED
|
@@ -190,6 +190,8 @@ var MotionSensor = class MotionSensor {
|
|
|
190
190
|
smoothing;
|
|
191
191
|
lastX = 0;
|
|
192
192
|
lastY = 0;
|
|
193
|
+
baseBeta = null;
|
|
194
|
+
baseGamma = null;
|
|
193
195
|
/**
|
|
194
196
|
* @param onMove - Callback receiving normalized { x, y } values
|
|
195
197
|
* @param axis - Restrict input to a single axis, or null for both
|
|
@@ -241,6 +243,8 @@ var MotionSensor = class MotionSensor {
|
|
|
241
243
|
start = () => {
|
|
242
244
|
if (this.active || !this.permitted) return;
|
|
243
245
|
this.active = true;
|
|
246
|
+
this.baseBeta = null;
|
|
247
|
+
this.baseGamma = null;
|
|
244
248
|
window.addEventListener("deviceorientation", this.handleOrientation);
|
|
245
249
|
};
|
|
246
250
|
/** Stop listening and remove the deviceorientation event listener. */
|
|
@@ -264,9 +268,15 @@ var MotionSensor = class MotionSensor {
|
|
|
264
268
|
const evt = e;
|
|
265
269
|
const beta = evt.beta ?? 0;
|
|
266
270
|
const gamma = evt.gamma ?? 0;
|
|
271
|
+
if (this.baseBeta === null) {
|
|
272
|
+
this.baseBeta = beta;
|
|
273
|
+
this.baseGamma = gamma;
|
|
274
|
+
}
|
|
275
|
+
const relativeBeta = beta - this.baseBeta;
|
|
276
|
+
const relativeGamma = gamma - this.baseGamma;
|
|
267
277
|
const range = this.maxAngle - this.minAngle;
|
|
268
|
-
const rawX = this.axis === "y" ? 0 : this.clamp(
|
|
269
|
-
const rawY = this.axis === "x" ? 0 : this.clamp(
|
|
278
|
+
const rawX = this.axis === "y" ? 0 : this.clamp(relativeGamma / (range / 2));
|
|
279
|
+
const rawY = this.axis === "x" ? 0 : this.clamp(relativeBeta / (range / 2));
|
|
270
280
|
this.lastX = this.lastX + (rawX - this.lastX) * this.smoothing;
|
|
271
281
|
this.lastY = this.lastY + (rawY - this.lastY) * this.smoothing;
|
|
272
282
|
this.onMove({
|
|
@@ -296,7 +306,6 @@ var PointerSensor = class {
|
|
|
296
306
|
axis;
|
|
297
307
|
active = false;
|
|
298
308
|
rect = null;
|
|
299
|
-
isPointerDown = false;
|
|
300
309
|
constructor(el, onMove, onEnter, onLeave, axis, eventsEl = null) {
|
|
301
310
|
this.eventsEl = eventsEl ?? el;
|
|
302
311
|
this.onMove = onMove;
|
|
@@ -331,14 +340,10 @@ var PointerSensor = class {
|
|
|
331
340
|
this.axis = axis;
|
|
332
341
|
};
|
|
333
342
|
handlePointerDown = (e) => {
|
|
334
|
-
this.isPointerDown = true;
|
|
335
|
-
this.eventsEl.setPointerCapture(e.pointerId);
|
|
336
343
|
this.handlePointerEnter();
|
|
337
344
|
this.handlePointerMove(e);
|
|
338
345
|
};
|
|
339
|
-
handlePointerUp = (
|
|
340
|
-
this.isPointerDown = false;
|
|
341
|
-
this.eventsEl.releasePointerCapture(e.pointerId);
|
|
346
|
+
handlePointerUp = () => {
|
|
342
347
|
this.handlePointerLeave();
|
|
343
348
|
};
|
|
344
349
|
/**
|
|
@@ -347,13 +352,10 @@ var PointerSensor = class {
|
|
|
347
352
|
*/
|
|
348
353
|
handlePointerMove = (e) => {
|
|
349
354
|
if (!this.rect) this.rect = this.eventsEl.getBoundingClientRect();
|
|
350
|
-
|
|
351
|
-
const
|
|
352
|
-
const
|
|
353
|
-
|
|
354
|
-
this.handlePointerLeave();
|
|
355
|
-
return;
|
|
356
|
-
}
|
|
355
|
+
const width = this.eventsEl.offsetWidth || this.rect.width;
|
|
356
|
+
const height = this.eventsEl.offsetHeight || this.rect.height;
|
|
357
|
+
const rawX = (e.clientX - this.rect.left) / width;
|
|
358
|
+
const rawY = (e.clientY - this.rect.top) / height;
|
|
357
359
|
const clampedX = Math.max(0, Math.min(1, rawX));
|
|
358
360
|
const clampedY = Math.max(0, Math.min(1, rawY));
|
|
359
361
|
const x = this.axis === "y" ? 0 : clampedX * 2 - 1;
|
|
@@ -402,6 +404,7 @@ var Levita = class {
|
|
|
402
404
|
listeners = /* @__PURE__ */ new Map();
|
|
403
405
|
destroyed = false;
|
|
404
406
|
gyroscopeRequested = false;
|
|
407
|
+
rafId = null;
|
|
405
408
|
/**
|
|
406
409
|
* @param el - The DOM element to apply the tilt effect to
|
|
407
410
|
* @param options - Configuration options (all optional, sensible defaults)
|
|
@@ -437,6 +440,23 @@ var Levita = class {
|
|
|
437
440
|
this.motionSensor.start();
|
|
438
441
|
}
|
|
439
442
|
};
|
|
443
|
+
/**
|
|
444
|
+
* Update options at runtime without destroying/recreating the instance.
|
|
445
|
+
*
|
|
446
|
+
* Only "lightweight" options are supported — those that don't require
|
|
447
|
+
* DOM mutations. Options like `glare`, `shadow`, `gyroscope`, `eventsEl`,
|
|
448
|
+
* `disabled`, and `maxGlare` require a full destroy/recreate cycle
|
|
449
|
+
* (or use `enable()`/`disable()` for toggling the effect).
|
|
450
|
+
*/
|
|
451
|
+
update = (options) => {
|
|
452
|
+
if (this.destroyed) return;
|
|
453
|
+
Object.assign(this.options, options);
|
|
454
|
+
this.applyBaseProperties();
|
|
455
|
+
if (options.axis !== void 0) {
|
|
456
|
+
this.pointerSensor.setAxis(options.axis);
|
|
457
|
+
this.motionSensor?.setAxis(options.axis);
|
|
458
|
+
}
|
|
459
|
+
};
|
|
440
460
|
/** Re-enable the tilt effect after a `disable()` call. */
|
|
441
461
|
enable = () => {
|
|
442
462
|
if (this.destroyed) return;
|
|
@@ -473,6 +493,7 @@ var Levita = class {
|
|
|
473
493
|
destroy = () => {
|
|
474
494
|
if (this.destroyed) return;
|
|
475
495
|
this.destroyed = true;
|
|
496
|
+
if (this.rafId) cancelAnimationFrame(this.rafId);
|
|
476
497
|
this.pointerSensor.stop();
|
|
477
498
|
this.motionSensor?.stop();
|
|
478
499
|
this.glareEffect?.destroy();
|
|
@@ -510,26 +531,34 @@ var Levita = class {
|
|
|
510
531
|
/**
|
|
511
532
|
* Process normalized sensor input and update CSS custom properties.
|
|
512
533
|
* Maps sensor X/Y to rotateY/rotateX (swapped, rotateX = vertical tilt).
|
|
534
|
+
*
|
|
535
|
+
* Note: We use requestAnimationFrame to throttle updates. High-polling rate mice
|
|
536
|
+
* can fire hundreds of events per second, which would saturate the main thread
|
|
537
|
+
* with CSS variable updates and style recalculations. This ensures we only
|
|
538
|
+
* update the DOM once per browser frame.
|
|
513
539
|
*/
|
|
514
540
|
handleSensorInput = (input) => {
|
|
515
541
|
if (this.options.disabled) return;
|
|
516
542
|
const multiplier = this.options.reverse ? -1 : 1;
|
|
517
543
|
const x = input.y * this.options.max * multiplier;
|
|
518
544
|
const y = input.x * this.options.max * multiplier * -1;
|
|
519
|
-
this.
|
|
520
|
-
this.
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
545
|
+
if (this.rafId) cancelAnimationFrame(this.rafId);
|
|
546
|
+
this.rafId = requestAnimationFrame(() => {
|
|
547
|
+
this.el.style.setProperty("--levita-x", `${x}deg`);
|
|
548
|
+
this.el.style.setProperty("--levita-y", `${y}deg`);
|
|
549
|
+
this.el.style.setProperty("--levita-percent-x", String(input.x));
|
|
550
|
+
this.el.style.setProperty("--levita-percent-y", String(input.y));
|
|
551
|
+
this.glareEffect?.update(input.x, input.y);
|
|
552
|
+
this.shadowEffect?.update(input.x, input.y);
|
|
553
|
+
const values = {
|
|
554
|
+
x,
|
|
555
|
+
y,
|
|
556
|
+
percentX: input.x,
|
|
557
|
+
percentY: input.y
|
|
558
|
+
};
|
|
559
|
+
this.emit("move", values);
|
|
560
|
+
this.rafId = null;
|
|
561
|
+
});
|
|
533
562
|
};
|
|
534
563
|
handleEnter = () => {
|
|
535
564
|
this.setTransition(false);
|
|
@@ -537,6 +566,10 @@ var Levita = class {
|
|
|
537
566
|
this.emit("enter", void 0);
|
|
538
567
|
};
|
|
539
568
|
handleLeave = () => {
|
|
569
|
+
if (this.rafId) {
|
|
570
|
+
cancelAnimationFrame(this.rafId);
|
|
571
|
+
this.rafId = null;
|
|
572
|
+
}
|
|
540
573
|
this.setTransition(true);
|
|
541
574
|
if (this.options.reset) this.resetTransform();
|
|
542
575
|
this.emit("leave", void 0);
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/constants.ts","../src/effects/glare.ts","../src/effects/shadow.ts","../src/layers.ts","../src/sensors/motion.ts","../src/sensors/pointer.ts","../src/levita.ts"],"sourcesContent":["import type { LevitaOptions } from \"./types.js\";\n\n/** All keys of `LevitaOptions`, derived from `DEFAULT_OPTIONS`. */\nexport const OPTION_KEYS: readonly (keyof LevitaOptions)[] = [\n\t\"max\",\n\t\"perspective\",\n\t\"scale\",\n\t\"speed\",\n\t\"easing\",\n\t\"reverse\",\n\t\"axis\",\n\t\"reset\",\n\t\"glare\",\n\t\"maxGlare\",\n\t\"shadow\",\n\t\"gyroscope\",\n\t\"disabled\",\n\t\"eventsEl\",\n] as const;\n\n/**\n * Build a partial `LevitaOptions` object from a source,\n * including only keys that are explicitly defined.\n */\nexport const buildOptions = (source: Partial<LevitaOptions>): Partial<LevitaOptions> => {\n\tconst options: Partial<LevitaOptions> = {};\n\tfor (const key of OPTION_KEYS) {\n\t\tif (source[key] !== undefined) {\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: generic key assignment\n\t\t\t(options as any)[key] = source[key];\n\t\t}\n\t}\n\treturn options;\n};\n\nexport const DEFAULT_OPTIONS: LevitaOptions = {\n\tmax: 15,\n\tperspective: 1000,\n\tscale: 1.05,\n\tspeed: 200,\n\teasing: \"ease-out\",\n\treverse: false,\n\taxis: null,\n\treset: true,\n\tglare: false,\n\tmaxGlare: 0.5,\n\tshadow: false,\n\tgyroscope: \"auto\",\n\tdisabled: false,\n\teventsEl: null,\n};\n","/**\n * Creates a radial gradient overlay that follows the tilt position,\n * simulating light reflection on the surface.\n *\n * Injects two DOM elements (.levita-glare > .levita-glare-inner) into\n * the target element. Position and opacity are driven by CSS custom\n * properties — no JS runs per animation frame.\n */\nexport class GlareEffect {\n\tprivate container: HTMLElement;\n\tprivate inner: HTMLElement;\n\tprivate maxOpacity: number;\n\n\t/**\n\t * @param el - The element to attach the glare overlay to\n\t * @param maxOpacity - Maximum glare opacity (0-1)\n\t */\n\tconstructor(el: HTMLElement, maxOpacity: number) {\n\t\tthis.maxOpacity = maxOpacity;\n\n\t\tthis.container = document.createElement(\"div\");\n\t\tthis.container.classList.add(\"levita-glare\");\n\n\t\tthis.inner = document.createElement(\"div\");\n\t\tthis.inner.classList.add(\"levita-glare-inner\");\n\n\t\tthis.container.appendChild(this.inner);\n\t\tel.appendChild(this.container);\n\n\t\tif (!el.style.position || el.style.position === \"static\") {\n\t\t\tel.style.position = \"relative\";\n\t\t}\n\t}\n\n\t/**\n\t * Update glare position and intensity based on normalized tilt values.\n\t * Sets CSS custom properties that the stylesheet uses for rendering.\n\t *\n\t * @param normalizedX - Horizontal position [-1, 1]\n\t * @param normalizedY - Vertical position [-1, 1]\n\t */\n\tupdate = (normalizedX: number, normalizedY: number): void => {\n\t\tconst glareX = ((normalizedX + 1) / 2) * 100;\n\t\tconst glareY = ((normalizedY + 1) / 2) * 100;\n\t\tconst intensity = Math.sqrt(normalizedX ** 2 + normalizedY ** 2) / Math.SQRT2;\n\n\t\tthis.inner.style.setProperty(\"--levita-glare-x\", `${glareX}%`);\n\t\tthis.inner.style.setProperty(\"--levita-glare-y\", `${glareY}%`);\n\t\tthis.inner.style.setProperty(\"--levita-glare-opacity\", `${intensity * this.maxOpacity}`);\n\t};\n\n\t/** Remove the glare DOM elements from the parent. */\n\tdestroy = (): void => {\n\t\tthis.container.remove();\n\t};\n}\n","/**\n * Adds a dynamic drop shadow that shifts based on the tilt angle,\n * reinforcing the 3D depth illusion.\n *\n * Uses CSS custom properties (--levita-shadow-x, --levita-shadow-y)\n * combined with `filter: drop-shadow()` — no JS runs per animation frame.\n */\nexport class ShadowEffect {\n\tprivate el: HTMLElement;\n\tprivate maxOffset: number;\n\n\t/**\n\t * @param el - The element to apply the shadow to\n\t * @param maxOffset - Maximum shadow offset in pixels (default: 20)\n\t */\n\tconstructor(el: HTMLElement, maxOffset = 20) {\n\t\tthis.el = el;\n\t\tthis.maxOffset = maxOffset;\n\t\tthis.el.classList.add(\"levita-shadow\");\n\t}\n\n\t/**\n\t * Update shadow offset based on normalized tilt values.\n\t *\n\t * @param normalizedX - Horizontal position [-1, 1]\n\t * @param normalizedY - Vertical position [-1, 1]\n\t */\n\tupdate = (normalizedX: number, normalizedY: number): void => {\n\t\tconst shadowX = normalizedX * this.maxOffset;\n\t\tconst shadowY = normalizedY * this.maxOffset;\n\n\t\tthis.el.style.setProperty(\"--levita-shadow-x\", `${shadowX}px`);\n\t\tthis.el.style.setProperty(\"--levita-shadow-y\", `${shadowY}px`);\n\t};\n\n\t/** Remove the shadow class and clean up CSS custom properties. */\n\tdestroy = (): void => {\n\t\tthis.el.classList.remove(\"levita-shadow\");\n\t\tthis.el.style.removeProperty(\"--levita-shadow-x\");\n\t\tthis.el.style.removeProperty(\"--levita-shadow-y\");\n\t};\n}\n","/** Represents a child element with a parallax depth offset. */\nexport interface Layer {\n\t/** The DOM element */\n\tel: HTMLElement;\n\t/** The depth offset value (positive = forward, negative = back) */\n\toffset: number;\n}\n\n/**\n * Scan a container for children with `data-levita-offset` attributes\n * and set the `--levita-offset` CSS custom property on each one.\n *\n * The CSS stylesheet uses this variable with `translateZ()` to position\n * layers at different depths — no JS runs per animation frame.\n *\n * @param container - The parent element to scan for layer children\n * @returns Array of discovered layers with their elements and offsets\n */\nexport const scanLayers = (container: HTMLElement): Layer[] => {\n\tconst elements = container.querySelectorAll<HTMLElement>(\"[data-levita-offset]\");\n\tconst layers: Layer[] = [];\n\n\tfor (const el of elements) {\n\t\tconst raw = el.dataset.levitaOffset;\n\t\tconst offset = Number.parseFloat(raw ?? \"0\");\n\t\tif (!Number.isNaN(offset)) {\n\t\t\tel.style.setProperty(\"--levita-offset\", String(offset));\n\t\t\tlayers.push({ el, offset });\n\t\t}\n\t}\n\n\treturn layers;\n};\n\n/**\n * Remove the `--levita-offset` CSS custom property from all layer elements.\n *\n * @param layers - The layers to clean up\n */\nexport const cleanupLayers = (layers: Layer[]): void => {\n\tfor (const layer of layers) {\n\t\tlayer.el.style.removeProperty(\"--levita-offset\");\n\t}\n};\n","import type { Axis } from \"../types.js\";\nimport type { SensorCallback } from \"./pointer.js\";\n\ninterface DeviceOrientationEvt extends Event {\n\tbeta: number | null;\n\tgamma: number | null;\n}\n\n/**\n * Reads device orientation (accelerometer/gyroscope) and normalizes\n * the tilt angles to a [-1, 1] range.\n *\n * Handles iOS 13+ permission flow via async `requestPermission()`.\n * On Android, permission is granted automatically.\n *\n * Uses exponential moving average for smoothing raw sensor data.\n */\nexport class MotionSensor {\n\tprivate onMove: SensorCallback;\n\tprivate axis: Axis;\n\tprivate active = false;\n\tprivate permitted = false;\n\tprivate minAngle: number;\n\tprivate maxAngle: number;\n\tprivate smoothing: number;\n\tprivate lastX = 0;\n\tprivate lastY = 0;\n\n\t/**\n\t * @param onMove - Callback receiving normalized { x, y } values\n\t * @param axis - Restrict input to a single axis, or null for both\n\t * @param minAngle - Minimum device angle mapped to -1 (default: -45)\n\t * @param maxAngle - Maximum device angle mapped to 1 (default: 45)\n\t * @param smoothing - Exponential moving average factor 0-1 (default: 0.15, lower = smoother)\n\t */\n\tconstructor(onMove: SensorCallback, axis: Axis, minAngle = -45, maxAngle = 45, smoothing = 0.15) {\n\t\tthis.onMove = onMove;\n\t\tthis.axis = axis;\n\t\tthis.minAngle = minAngle;\n\t\tthis.maxAngle = maxAngle;\n\t\tthis.smoothing = smoothing;\n\t}\n\n\t/** Check if the DeviceOrientationEvent API is available in this environment. */\n\tstatic isSupported = (): boolean => {\n\t\treturn typeof window !== \"undefined\" && \"DeviceOrientationEvent\" in window;\n\t};\n\n\t/**\n\t * Check if explicit permission is required (iOS 13+).\n\t * On Android and desktop, this returns false.\n\t */\n\tstatic needsPermission = (): boolean => {\n\t\treturn (\n\t\t\ttypeof DeviceOrientationEvent !== \"undefined\" && \"requestPermission\" in DeviceOrientationEvent\n\t\t);\n\t};\n\n\t/**\n\t * Request permission to access device orientation.\n\t * On Android, resolves immediately to true.\n\t * On iOS 13+, triggers the native permission dialog.\n\t * Must be called from a user gesture on iOS.\n\t *\n\t * @returns Whether permission was granted\n\t */\n\trequestPermission = async (): Promise<boolean> => {\n\t\tif (!MotionSensor.isSupported()) return false;\n\n\t\tif (!MotionSensor.needsPermission()) {\n\t\t\tthis.permitted = true;\n\t\t\treturn true;\n\t\t}\n\n\t\ttry {\n\t\t\tconst DOE = DeviceOrientationEvent as unknown as {\n\t\t\t\trequestPermission: () => Promise<string>;\n\t\t\t};\n\t\t\tconst result = await DOE.requestPermission();\n\t\t\tthis.permitted = result === \"granted\";\n\t\t\treturn this.permitted;\n\t\t} catch {\n\t\t\tthis.permitted = false;\n\t\t\treturn false;\n\t\t}\n\t};\n\n\t/** Start listening to deviceorientation events. Requires prior permission. */\n\tstart = (): void => {\n\t\tif (this.active || !this.permitted) return;\n\t\tthis.active = true;\n\t\twindow.addEventListener(\"deviceorientation\", this.handleOrientation);\n\t};\n\n\t/** Stop listening and remove the deviceorientation event listener. */\n\tstop = (): void => {\n\t\tif (!this.active) return;\n\t\tthis.active = false;\n\t\twindow.removeEventListener(\"deviceorientation\", this.handleOrientation);\n\t};\n\n\t/** Update the axis lock at runtime. */\n\tsetAxis = (axis: Axis): void => {\n\t\tthis.axis = axis;\n\t};\n\n\t/**\n\t * Normalize device orientation angles to [-1, 1] and apply\n\t * exponential moving average smoothing.\n\t *\n\t * beta = X-axis rotation [-180, 180] (front-back tilt)\n\t * gamma = Y-axis rotation [-90, 90] (left-right tilt)\n\t */\n\tprivate handleOrientation = (e: Event): void => {\n\t\tconst evt = e as DeviceOrientationEvt;\n\t\tconst beta = evt.beta ?? 0;\n\t\tconst gamma = evt.gamma ?? 0;\n\n\t\tconst range = this.maxAngle - this.minAngle;\n\t\tconst rawX = this.axis === \"y\" ? 0 : this.clamp(((gamma - this.minAngle) / range) * 2 - 1);\n\t\tconst rawY = this.axis === \"x\" ? 0 : this.clamp(((beta - this.minAngle) / range) * 2 - 1);\n\n\t\tthis.lastX = this.lastX + (rawX - this.lastX) * this.smoothing;\n\t\tthis.lastY = this.lastY + (rawY - this.lastY) * this.smoothing;\n\n\t\tthis.onMove({ x: this.lastX, y: this.lastY });\n\t};\n\n\t/** Clamp a value to the [-1, 1] range. */\n\tprivate clamp = (value: number): number => {\n\t\treturn Math.max(-1, Math.min(1, value));\n\t};\n}\n","import type { Axis } from \"../types.js\";\n\n/** Normalized sensor output with values in [-1, 1] range. */\nexport interface SensorOutput {\n\t/** Normalized X position [-1, 1] */\n\tx: number;\n\t/** Normalized Y position [-1, 1] */\n\ty: number;\n}\n\n/** Callback invoked when sensor detects movement. */\nexport type SensorCallback = (values: SensorOutput) => void;\n\n/**\n * Tracks pointer (mouse/touch) position over an element and normalizes\n * it to a [-1, 1] range relative to the element's bounds.\n *\n * Uses PointerEvent for unified mouse + touch input.\n */\nexport class PointerSensor {\n\tprivate eventsEl: HTMLElement;\n\tprivate onMove: SensorCallback;\n\tprivate onEnter: (() => void) | null;\n\tprivate onLeave: (() => void) | null;\n\tprivate axis: Axis;\n\tprivate active = false;\n\tprivate rect: DOMRect | null = null;\n\tprivate isPointerDown = false;\n\n\tconstructor(\n\t\tel: HTMLElement,\n\t\tonMove: SensorCallback,\n\t\tonEnter: (() => void) | null,\n\t\tonLeave: (() => void) | null,\n\t\taxis: Axis,\n\t\teventsEl: HTMLElement | null = null,\n\t) {\n\t\tthis.eventsEl = eventsEl ?? el;\n\t\tthis.onMove = onMove;\n\t\tthis.onEnter = onEnter;\n\t\tthis.onLeave = onLeave;\n\t\tthis.axis = axis;\n\t}\n\n\t/** Start listening to pointer events. */\n\tstart = (): void => {\n\t\tif (this.active) return;\n\t\tthis.active = true;\n\t\tthis.eventsEl.addEventListener(\"pointermove\", this.handlePointerMove);\n\t\tthis.eventsEl.addEventListener(\"pointerenter\", this.handlePointerEnter);\n\t\tthis.eventsEl.addEventListener(\"pointerleave\", this.handlePointerLeave);\n\t\tthis.eventsEl.addEventListener(\"pointerdown\", this.handlePointerDown);\n\t\tthis.eventsEl.addEventListener(\"pointerup\", this.handlePointerUp);\n\t\tthis.eventsEl.addEventListener(\"pointercancel\", this.handlePointerUp);\n\t};\n\n\t/** Stop listening and remove all pointer event listeners. */\n\tstop = (): void => {\n\t\tif (!this.active) return;\n\t\tthis.active = false;\n\t\tthis.eventsEl.removeEventListener(\"pointermove\", this.handlePointerMove);\n\t\tthis.eventsEl.removeEventListener(\"pointerenter\", this.handlePointerEnter);\n\t\tthis.eventsEl.removeEventListener(\"pointerleave\", this.handlePointerLeave);\n\t\tthis.eventsEl.removeEventListener(\"pointerdown\", this.handlePointerDown);\n\t\tthis.eventsEl.removeEventListener(\"pointerup\", this.handlePointerUp);\n\t\tthis.eventsEl.removeEventListener(\"pointercancel\", this.handlePointerUp);\n\t};\n\n\t/** Update the axis lock at runtime. */\n\tsetAxis = (axis: Axis): void => {\n\t\tthis.axis = axis;\n\t};\n\n\tprivate handlePointerDown = (e: PointerEvent): void => {\n\t\tthis.isPointerDown = true;\n\t\tthis.eventsEl.setPointerCapture(e.pointerId);\n\t\tthis.handlePointerEnter();\n\t\tthis.handlePointerMove(e);\n\t};\n\n\tprivate handlePointerUp = (e: PointerEvent): void => {\n\t\tthis.isPointerDown = false;\n\t\tthis.eventsEl.releasePointerCapture(e.pointerId);\n\t\tthis.handlePointerLeave();\n\t};\n\n\t/**\n\t * Compute normalized [-1, 1] position from pointer coordinates\n\t * relative to the element's bounding rect. Respects axis locking.\n\t */\n\tprivate handlePointerMove = (e: PointerEvent): void => {\n\t\t// On touch devices, pointermove might be the only event we get after pointerdown\n\t\tif (!this.rect) {\n\t\t\tthis.rect = this.eventsEl.getBoundingClientRect();\n\t\t}\n\t\tif (!this.rect) return;\n\n\t\tconst rawX = (e.clientX - this.rect.left) / this.rect.width;\n\t\tconst rawY = (e.clientY - this.rect.top) / this.rect.height;\n\n\t\t// Check if pointer is still within bounds (especially for mouse move without down)\n\t\tconst isOut = rawX < 0 || rawX > 1 || rawY < 0 || rawY > 1;\n\n\t\tif (isOut && !this.isPointerDown) {\n\t\t\tthis.handlePointerLeave();\n\t\t\treturn;\n\t\t}\n\n\t\t// Clamp values if pointer is captured but outside bounds\n\t\tconst clampedX = Math.max(0, Math.min(1, rawX));\n\t\tconst clampedY = Math.max(0, Math.min(1, rawY));\n\n\t\tconst x = this.axis === \"y\" ? 0 : clampedX * 2 - 1;\n\t\tconst y = this.axis === \"x\" ? 0 : clampedY * 2 - 1;\n\n\t\tthis.onMove({ x, y });\n\t};\n\n\tprivate handlePointerEnter = (): void => {\n\t\tthis.rect = this.eventsEl.getBoundingClientRect();\n\t\tthis.onEnter?.();\n\t};\n\n\tprivate handlePointerLeave = (): void => {\n\t\tthis.rect = null;\n\t\tthis.onLeave?.();\n\t};\n}\n","import { DEFAULT_OPTIONS } from \"./constants.js\";\nimport { GlareEffect } from \"./effects/glare.js\";\nimport { ShadowEffect } from \"./effects/shadow.js\";\nimport type { Layer } from \"./layers.js\";\nimport { cleanupLayers, scanLayers } from \"./layers.js\";\nimport { MotionSensor } from \"./sensors/motion.js\";\nimport type { SensorOutput } from \"./sensors/pointer.js\";\nimport { PointerSensor } from \"./sensors/pointer.js\";\nimport type { EventCallback, LevitaEventMap, LevitaOptions, TiltValues } from \"./types.js\";\n\n/**\n * Main entry point for the Levita 3D tilt effect.\n *\n * Orchestrates sensors (pointer, accelerometer), visual effects (glare, shadow),\n * and multi-layer parallax. All rendering is driven by CSS custom properties —\n * no requestAnimationFrame loop runs during interaction.\n *\n * @example\n * ```ts\n * import { Levita } from 'levita-js';\n * import 'levita-js/style.css';\n *\n * const tilt = new Levita(element, { glare: true, shadow: true });\n * tilt.on('move', ({ x, y }) => console.log(x, y));\n * ```\n */\nexport class Levita {\n\tprivate el: HTMLElement;\n\tprivate options: LevitaOptions;\n\tprivate pointerSensor: PointerSensor;\n\tprivate motionSensor: MotionSensor | null = null;\n\tprivate glareEffect: GlareEffect | null = null;\n\tprivate shadowEffect: ShadowEffect | null = null;\n\tprivate layers: Layer[] = [];\n\tprivate listeners = new Map<string, Set<EventCallback<unknown>>>();\n\tprivate destroyed = false;\n\tprivate gyroscopeRequested = false;\n\n\t/**\n\t * @param el - The DOM element to apply the tilt effect to\n\t * @param options - Configuration options (all optional, sensible defaults)\n\t */\n\tconstructor(el: HTMLElement, options: Partial<LevitaOptions> = {}) {\n\t\tthis.el = el;\n\t\tthis.options = { ...DEFAULT_OPTIONS, ...options };\n\n\t\tthis.el.classList.add(\"levita\");\n\t\tthis.applyBaseProperties();\n\n\t\tthis.layers = scanLayers(this.el);\n\n\t\tif (this.options.glare) {\n\t\t\tthis.glareEffect = new GlareEffect(this.el, this.options.maxGlare);\n\t\t}\n\t\tif (this.options.shadow) {\n\t\t\tthis.shadowEffect = new ShadowEffect(this.el);\n\t\t}\n\n\t\tthis.pointerSensor = new PointerSensor(\n\t\t\tthis.el,\n\t\t\t(values) => this.handleSensorInput(values),\n\t\t\t() => this.handleEnter(),\n\t\t\t() => this.handleLeave(),\n\t\t\tthis.options.axis,\n\t\t\tthis.options.eventsEl,\n\t\t);\n\n\t\tif (this.options.gyroscope !== false && MotionSensor.isSupported()) {\n\t\t\tthis.motionSensor = new MotionSensor(\n\t\t\t\t(values) => this.handleSensorInput(values),\n\t\t\t\tthis.options.axis,\n\t\t\t);\n\n\t\t\tif (this.options.gyroscope === \"auto\") {\n\t\t\t\tthis.el.addEventListener(\"click\", this.handleFirstTouch, { once: true });\n\t\t\t}\n\t\t}\n\n\t\tif (!this.options.disabled) {\n\t\t\tthis.enable();\n\t\t}\n\t}\n\n\t/**\n\t * On first touch (iOS auto mode), request accelerometer permission\n\t * and switch from pointer to motion sensor if granted.\n\t */\n\tprivate handleFirstTouch = async (): Promise<void> => {\n\t\tif (this.destroyed || this.gyroscopeRequested || !this.motionSensor) return;\n\t\tthis.gyroscopeRequested = true;\n\n\t\tconst granted = await this.motionSensor.requestPermission();\n\t\tif (granted) {\n\t\t\tthis.pointerSensor.stop();\n\t\t\tthis.setTransition(false);\n\t\t\tthis.motionSensor.start();\n\t\t}\n\t};\n\n\t/** Re-enable the tilt effect after a `disable()` call. */\n\tenable = (): void => {\n\t\tif (this.destroyed) return;\n\t\tthis.options.disabled = false;\n\t\tthis.pointerSensor.start();\n\t};\n\n\t/** Pause the tilt effect and reset the element to its neutral position. */\n\tdisable = (): void => {\n\t\tthis.options.disabled = true;\n\t\tthis.pointerSensor.stop();\n\t\tthis.motionSensor?.stop();\n\t\tthis.resetTransform();\n\t};\n\n\t/**\n\t * Manually request accelerometer permission (for `gyroscope: true` mode).\n\t * Must be called from a user gesture on iOS 13+.\n\t *\n\t * @returns Whether permission was granted\n\t */\n\trequestPermission = async (): Promise<boolean> => {\n\t\tif (!this.motionSensor) return false;\n\t\tconst granted = await this.motionSensor.requestPermission();\n\t\tif (granted) {\n\t\t\tthis.pointerSensor.stop();\n\t\t\tthis.setTransition(false);\n\t\t\tthis.motionSensor.start();\n\t\t}\n\t\treturn granted;\n\t};\n\n\t/**\n\t * Fully clean up: stop sensors, remove effects, restore the element\n\t * to its original state. The instance cannot be reused after this.\n\t */\n\tdestroy = (): void => {\n\t\tif (this.destroyed) return;\n\t\tthis.destroyed = true;\n\n\t\tthis.pointerSensor.stop();\n\t\tthis.motionSensor?.stop();\n\t\tthis.glareEffect?.destroy();\n\t\tthis.shadowEffect?.destroy();\n\t\tcleanupLayers(this.layers);\n\n\t\tthis.el.classList.remove(\"levita\");\n\t\tthis.removeBaseProperties();\n\t\tthis.listeners.clear();\n\n\t\tthis.el.removeEventListener(\"click\", this.handleFirstTouch);\n\t};\n\n\t/**\n\t * Register an event listener.\n\t *\n\t * @param event - Event name: 'move', 'enter', or 'leave'\n\t * @param callback - Handler function\n\t */\n\ton = <K extends keyof LevitaEventMap>(\n\t\tevent: K,\n\t\tcallback: EventCallback<LevitaEventMap[K]>,\n\t): void => {\n\t\tif (!this.listeners.has(event)) {\n\t\t\tthis.listeners.set(event, new Set());\n\t\t}\n\t\tthis.listeners.get(event)?.add(callback as EventCallback<unknown>);\n\t};\n\n\t/**\n\t * Remove a previously registered event listener.\n\t *\n\t * @param event - Event name\n\t * @param callback - The exact handler reference passed to `on()`\n\t */\n\toff = <K extends keyof LevitaEventMap>(\n\t\tevent: K,\n\t\tcallback: EventCallback<LevitaEventMap[K]>,\n\t): void => {\n\t\tthis.listeners.get(event)?.delete(callback as EventCallback<unknown>);\n\t};\n\n\t/** Emit an event to all registered listeners. */\n\tprivate emit = <K extends keyof LevitaEventMap>(event: K, data: LevitaEventMap[K]): void => {\n\t\tconst callbacks = this.listeners.get(event);\n\t\tif (callbacks) {\n\t\t\tfor (const cb of callbacks) {\n\t\t\t\tcb(data);\n\t\t\t}\n\t\t}\n\t};\n\n\t/**\n\t * Process normalized sensor input and update CSS custom properties.\n\t * Maps sensor X/Y to rotateY/rotateX (swapped, rotateX = vertical tilt).\n\t */\n\tprivate handleSensorInput = (input: SensorOutput): void => {\n\t\tif (this.options.disabled) return;\n\n\t\tconst multiplier = this.options.reverse ? -1 : 1;\n\t\tconst x = input.y * this.options.max * multiplier;\n\t\tconst y = input.x * this.options.max * multiplier * -1;\n\n\t\tthis.el.style.setProperty(\"--levita-x\", `${x}deg`);\n\t\tthis.el.style.setProperty(\"--levita-y\", `${y}deg`);\n\t\tthis.el.style.setProperty(\"--levita-scale\", String(this.options.scale));\n\t\tthis.el.style.setProperty(\"--levita-percent-x\", String(input.x));\n\t\tthis.el.style.setProperty(\"--levita-percent-y\", String(input.y));\n\n\t\tthis.glareEffect?.update(input.x, input.y);\n\t\tthis.shadowEffect?.update(input.x, input.y);\n\n\t\tconst values: TiltValues = {\n\t\t\tx,\n\t\t\ty,\n\t\t\tpercentX: input.x,\n\t\t\tpercentY: input.y,\n\t\t};\n\t\tthis.emit(\"move\", values);\n\t};\n\n\tprivate handleEnter = (): void => {\n\t\tthis.setTransition(false);\n\t\tthis.el.style.setProperty(\"--levita-scale\", String(this.options.scale));\n\t\tthis.emit(\"enter\", undefined);\n\t};\n\n\tprivate handleLeave = (): void => {\n\t\tthis.setTransition(true);\n\t\tif (this.options.reset) {\n\t\t\tthis.resetTransform();\n\t\t}\n\t\tthis.emit(\"leave\", undefined);\n\t};\n\n\t/** Toggle the CSS transition on or off. */\n\tprivate setTransition = (on: boolean): void => {\n\t\tthis.el.style.setProperty(\"--levita-speed\", on ? `${this.options.speed}ms` : \"0ms\");\n\t};\n\n\t/** Reset the element to its neutral (non-tilted) position. */\n\tprivate resetTransform = (): void => {\n\t\tthis.el.style.setProperty(\"--levita-x\", \"0deg\");\n\t\tthis.el.style.setProperty(\"--levita-y\", \"0deg\");\n\t\tthis.el.style.setProperty(\"--levita-scale\", \"1\");\n\t\tthis.el.style.setProperty(\"--levita-percent-x\", \"0\");\n\t\tthis.el.style.setProperty(\"--levita-percent-y\", \"0\");\n\t\tthis.glareEffect?.update(0, 0);\n\t\tthis.shadowEffect?.update(0, 0);\n\t};\n\n\t/** Apply initial CSS custom properties from options. */\n\tprivate applyBaseProperties = (): void => {\n\t\tthis.el.style.setProperty(\"--levita-perspective\", `${this.options.perspective}px`);\n\t\tthis.el.style.setProperty(\"--levita-speed\", `${this.options.speed}ms`);\n\t\tthis.el.style.setProperty(\"--levita-easing\", this.options.easing);\n\t};\n\n\t/** Remove all Levita CSS custom properties from the element. */\n\tprivate removeBaseProperties = (): void => {\n\t\tthis.el.style.removeProperty(\"--levita-perspective\");\n\t\tthis.el.style.removeProperty(\"--levita-speed\");\n\t\tthis.el.style.removeProperty(\"--levita-easing\");\n\t\tthis.el.style.removeProperty(\"--levita-x\");\n\t\tthis.el.style.removeProperty(\"--levita-y\");\n\t\tthis.el.style.removeProperty(\"--levita-scale\");\n\t\tthis.el.style.removeProperty(\"--levita-percent-x\");\n\t\tthis.el.style.removeProperty(\"--levita-percent-y\");\n\t};\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/constants.ts","../src/effects/glare.ts","../src/effects/shadow.ts","../src/layers.ts","../src/sensors/motion.ts","../src/sensors/pointer.ts","../src/levita.ts"],"sourcesContent":["import type { LevitaOptions } from \"./types.js\";\n\n/** All keys of `LevitaOptions`, derived from `DEFAULT_OPTIONS`. */\nexport const OPTION_KEYS: readonly (keyof LevitaOptions)[] = [\n\t\"max\",\n\t\"perspective\",\n\t\"scale\",\n\t\"speed\",\n\t\"easing\",\n\t\"reverse\",\n\t\"axis\",\n\t\"reset\",\n\t\"glare\",\n\t\"maxGlare\",\n\t\"shadow\",\n\t\"gyroscope\",\n\t\"disabled\",\n\t\"eventsEl\",\n] as const;\n\n/**\n * Build a partial `LevitaOptions` object from a source,\n * including only keys that are explicitly defined.\n */\nexport const buildOptions = (source: Partial<LevitaOptions>): Partial<LevitaOptions> => {\n\tconst options: Partial<LevitaOptions> = {};\n\tfor (const key of OPTION_KEYS) {\n\t\tif (source[key] !== undefined) {\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: generic key assignment\n\t\t\t(options as any)[key] = source[key];\n\t\t}\n\t}\n\treturn options;\n};\n\nexport const DEFAULT_OPTIONS: LevitaOptions = {\n\tmax: 15,\n\tperspective: 1000,\n\tscale: 1.05,\n\tspeed: 200,\n\teasing: \"ease-out\",\n\treverse: false,\n\taxis: null,\n\treset: true,\n\tglare: false,\n\tmaxGlare: 0.5,\n\tshadow: false,\n\tgyroscope: \"auto\",\n\tdisabled: false,\n\teventsEl: null,\n};\n","/**\n * Creates a radial gradient overlay that follows the tilt position,\n * simulating light reflection on the surface.\n *\n * Injects two DOM elements (.levita-glare > .levita-glare-inner) into\n * the target element. Position and opacity are driven by CSS custom\n * properties — no JS runs per animation frame.\n */\nexport class GlareEffect {\n\tprivate container: HTMLElement;\n\tprivate inner: HTMLElement;\n\tprivate maxOpacity: number;\n\n\t/**\n\t * @param el - The element to attach the glare overlay to\n\t * @param maxOpacity - Maximum glare opacity (0-1)\n\t */\n\tconstructor(el: HTMLElement, maxOpacity: number) {\n\t\tthis.maxOpacity = maxOpacity;\n\n\t\tthis.container = document.createElement(\"div\");\n\t\tthis.container.classList.add(\"levita-glare\");\n\n\t\tthis.inner = document.createElement(\"div\");\n\t\tthis.inner.classList.add(\"levita-glare-inner\");\n\n\t\tthis.container.appendChild(this.inner);\n\t\tel.appendChild(this.container);\n\n\t\tif (!el.style.position || el.style.position === \"static\") {\n\t\t\tel.style.position = \"relative\";\n\t\t}\n\t}\n\n\t/**\n\t * Update glare position and intensity based on normalized tilt values.\n\t * Sets CSS custom properties that the stylesheet uses for rendering.\n\t *\n\t * @param normalizedX - Horizontal position [-1, 1]\n\t * @param normalizedY - Vertical position [-1, 1]\n\t */\n\tupdate = (normalizedX: number, normalizedY: number): void => {\n\t\tconst glareX = ((normalizedX + 1) / 2) * 100;\n\t\tconst glareY = ((normalizedY + 1) / 2) * 100;\n\t\tconst intensity = Math.sqrt(normalizedX ** 2 + normalizedY ** 2) / Math.SQRT2;\n\n\t\tthis.inner.style.setProperty(\"--levita-glare-x\", `${glareX}%`);\n\t\tthis.inner.style.setProperty(\"--levita-glare-y\", `${glareY}%`);\n\t\tthis.inner.style.setProperty(\"--levita-glare-opacity\", `${intensity * this.maxOpacity}`);\n\t};\n\n\t/** Remove the glare DOM elements from the parent. */\n\tdestroy = (): void => {\n\t\tthis.container.remove();\n\t};\n}\n","/**\n * Adds a dynamic drop shadow that shifts based on the tilt angle,\n * reinforcing the 3D depth illusion.\n *\n * Uses CSS custom properties (--levita-shadow-x, --levita-shadow-y)\n * combined with `filter: drop-shadow()` — no JS runs per animation frame.\n */\nexport class ShadowEffect {\n\tprivate el: HTMLElement;\n\tprivate maxOffset: number;\n\n\t/**\n\t * @param el - The element to apply the shadow to\n\t * @param maxOffset - Maximum shadow offset in pixels (default: 20)\n\t */\n\tconstructor(el: HTMLElement, maxOffset = 20) {\n\t\tthis.el = el;\n\t\tthis.maxOffset = maxOffset;\n\t\tthis.el.classList.add(\"levita-shadow\");\n\t}\n\n\t/**\n\t * Update shadow offset based on normalized tilt values.\n\t *\n\t * @param normalizedX - Horizontal position [-1, 1]\n\t * @param normalizedY - Vertical position [-1, 1]\n\t */\n\tupdate = (normalizedX: number, normalizedY: number): void => {\n\t\tconst shadowX = normalizedX * this.maxOffset;\n\t\tconst shadowY = normalizedY * this.maxOffset;\n\n\t\tthis.el.style.setProperty(\"--levita-shadow-x\", `${shadowX}px`);\n\t\tthis.el.style.setProperty(\"--levita-shadow-y\", `${shadowY}px`);\n\t};\n\n\t/** Remove the shadow class and clean up CSS custom properties. */\n\tdestroy = (): void => {\n\t\tthis.el.classList.remove(\"levita-shadow\");\n\t\tthis.el.style.removeProperty(\"--levita-shadow-x\");\n\t\tthis.el.style.removeProperty(\"--levita-shadow-y\");\n\t};\n}\n","/** Represents a child element with a parallax depth offset. */\nexport interface Layer {\n\t/** The DOM element */\n\tel: HTMLElement;\n\t/** The depth offset value (positive = forward, negative = back) */\n\toffset: number;\n}\n\n/**\n * Scan a container for children with `data-levita-offset` attributes\n * and set the `--levita-offset` CSS custom property on each one.\n *\n * The CSS stylesheet uses this variable with `translateZ()` to position\n * layers at different depths — no JS runs per animation frame.\n *\n * @param container - The parent element to scan for layer children\n * @returns Array of discovered layers with their elements and offsets\n */\nexport const scanLayers = (container: HTMLElement): Layer[] => {\n\tconst elements = container.querySelectorAll<HTMLElement>(\"[data-levita-offset]\");\n\tconst layers: Layer[] = [];\n\n\tfor (const el of elements) {\n\t\tconst raw = el.dataset.levitaOffset;\n\t\tconst offset = Number.parseFloat(raw ?? \"0\");\n\t\tif (!Number.isNaN(offset)) {\n\t\t\tel.style.setProperty(\"--levita-offset\", String(offset));\n\t\t\tlayers.push({ el, offset });\n\t\t}\n\t}\n\n\treturn layers;\n};\n\n/**\n * Remove the `--levita-offset` CSS custom property from all layer elements.\n *\n * @param layers - The layers to clean up\n */\nexport const cleanupLayers = (layers: Layer[]): void => {\n\tfor (const layer of layers) {\n\t\tlayer.el.style.removeProperty(\"--levita-offset\");\n\t}\n};\n","import type { Axis } from \"../types.js\";\nimport type { SensorCallback } from \"./pointer.js\";\n\ninterface DeviceOrientationEvt extends Event {\n\tbeta: number | null;\n\tgamma: number | null;\n}\n\n/**\n * Reads device orientation (accelerometer/gyroscope) and normalizes\n * the tilt angles to a [-1, 1] range.\n *\n * Handles iOS 13+ permission flow via async `requestPermission()`.\n * On Android, permission is granted automatically.\n *\n * Uses exponential moving average for smoothing raw sensor data.\n */\nexport class MotionSensor {\n\tprivate onMove: SensorCallback;\n\tprivate axis: Axis;\n\tprivate active = false;\n\tprivate permitted = false;\n\tprivate minAngle: number;\n\tprivate maxAngle: number;\n\tprivate smoothing: number;\n\tprivate lastX = 0;\n\tprivate lastY = 0;\n\tprivate baseBeta: number | null = null;\n\tprivate baseGamma: number | null = null;\n\n\t/**\n\t * @param onMove - Callback receiving normalized { x, y } values\n\t * @param axis - Restrict input to a single axis, or null for both\n\t * @param minAngle - Minimum device angle mapped to -1 (default: -45)\n\t * @param maxAngle - Maximum device angle mapped to 1 (default: 45)\n\t * @param smoothing - Exponential moving average factor 0-1 (default: 0.15, lower = smoother)\n\t */\n\tconstructor(onMove: SensorCallback, axis: Axis, minAngle = -45, maxAngle = 45, smoothing = 0.15) {\n\t\tthis.onMove = onMove;\n\t\tthis.axis = axis;\n\t\tthis.minAngle = minAngle;\n\t\tthis.maxAngle = maxAngle;\n\t\tthis.smoothing = smoothing;\n\t}\n\n\t/** Check if the DeviceOrientationEvent API is available in this environment. */\n\tstatic isSupported = (): boolean => {\n\t\treturn typeof window !== \"undefined\" && \"DeviceOrientationEvent\" in window;\n\t};\n\n\t/**\n\t * Check if explicit permission is required (iOS 13+).\n\t * On Android and desktop, this returns false.\n\t */\n\tstatic needsPermission = (): boolean => {\n\t\treturn (\n\t\t\ttypeof DeviceOrientationEvent !== \"undefined\" && \"requestPermission\" in DeviceOrientationEvent\n\t\t);\n\t};\n\n\t/**\n\t * Request permission to access device orientation.\n\t * On Android, resolves immediately to true.\n\t * On iOS 13+, triggers the native permission dialog.\n\t * Must be called from a user gesture on iOS.\n\t *\n\t * @returns Whether permission was granted\n\t */\n\trequestPermission = async (): Promise<boolean> => {\n\t\tif (!MotionSensor.isSupported()) return false;\n\n\t\tif (!MotionSensor.needsPermission()) {\n\t\t\tthis.permitted = true;\n\t\t\treturn true;\n\t\t}\n\n\t\ttry {\n\t\t\tconst DOE = DeviceOrientationEvent as unknown as {\n\t\t\t\trequestPermission: () => Promise<string>;\n\t\t\t};\n\t\t\tconst result = await DOE.requestPermission();\n\t\t\tthis.permitted = result === \"granted\";\n\t\t\treturn this.permitted;\n\t\t} catch {\n\t\t\tthis.permitted = false;\n\t\t\treturn false;\n\t\t}\n\t};\n\n\t/** Start listening to deviceorientation events. Requires prior permission. */\n\tstart = (): void => {\n\t\tif (this.active || !this.permitted) return;\n\t\tthis.active = true;\n\t\tthis.baseBeta = null;\n\t\tthis.baseGamma = null;\n\t\twindow.addEventListener(\"deviceorientation\", this.handleOrientation);\n\t};\n\n\t/** Stop listening and remove the deviceorientation event listener. */\n\tstop = (): void => {\n\t\tif (!this.active) return;\n\t\tthis.active = false;\n\t\twindow.removeEventListener(\"deviceorientation\", this.handleOrientation);\n\t};\n\n\t/** Update the axis lock at runtime. */\n\tsetAxis = (axis: Axis): void => {\n\t\tthis.axis = axis;\n\t};\n\n\t/**\n\t * Normalize device orientation angles to [-1, 1] and apply\n\t * exponential moving average smoothing.\n\t *\n\t * beta = X-axis rotation [-180, 180] (front-back tilt)\n\t * gamma = Y-axis rotation [-90, 90] (left-right tilt)\n\t */\n\tprivate handleOrientation = (e: Event): void => {\n\t\tconst evt = e as DeviceOrientationEvt;\n\t\tconst beta = evt.beta ?? 0;\n\t\tconst gamma = evt.gamma ?? 0;\n\n\t\t// Calibrate: capture the first reading as the neutral position\n\t\tif (this.baseBeta === null) {\n\t\t\tthis.baseBeta = beta;\n\t\t\tthis.baseGamma = gamma;\n\t\t}\n\n\t\t// Compute tilt relative to initial device position\n\t\tconst relativeBeta = beta - this.baseBeta;\n\t\tconst relativeGamma = gamma - (this.baseGamma as number);\n\n\t\tconst range = this.maxAngle - this.minAngle;\n\t\tconst rawX = this.axis === \"y\" ? 0 : this.clamp(relativeGamma / (range / 2));\n\t\tconst rawY = this.axis === \"x\" ? 0 : this.clamp(relativeBeta / (range / 2));\n\n\t\tthis.lastX = this.lastX + (rawX - this.lastX) * this.smoothing;\n\t\tthis.lastY = this.lastY + (rawY - this.lastY) * this.smoothing;\n\n\t\tthis.onMove({ x: this.lastX, y: this.lastY });\n\t};\n\n\t/** Clamp a value to the [-1, 1] range. */\n\tprivate clamp = (value: number): number => {\n\t\treturn Math.max(-1, Math.min(1, value));\n\t};\n}\n","import type { Axis } from \"../types.js\";\n\n/** Normalized sensor output with values in [-1, 1] range. */\nexport interface SensorOutput {\n\t/** Normalized X position [-1, 1] */\n\tx: number;\n\t/** Normalized Y position [-1, 1] */\n\ty: number;\n}\n\n/** Callback invoked when sensor detects movement. */\nexport type SensorCallback = (values: SensorOutput) => void;\n\n/**\n * Tracks pointer (mouse/touch) position over an element and normalizes\n * it to a [-1, 1] range relative to the element's bounds.\n *\n * Uses PointerEvent for unified mouse + touch input.\n */\nexport class PointerSensor {\n\tprivate eventsEl: HTMLElement;\n\tprivate onMove: SensorCallback;\n\tprivate onEnter: (() => void) | null;\n\tprivate onLeave: (() => void) | null;\n\tprivate axis: Axis;\n\tprivate active = false;\n\tprivate rect: DOMRect | null = null;\n\n\tconstructor(\n\t\tel: HTMLElement,\n\t\tonMove: SensorCallback,\n\t\tonEnter: (() => void) | null,\n\t\tonLeave: (() => void) | null,\n\t\taxis: Axis,\n\t\teventsEl: HTMLElement | null = null,\n\t) {\n\t\tthis.eventsEl = eventsEl ?? el;\n\t\tthis.onMove = onMove;\n\t\tthis.onEnter = onEnter;\n\t\tthis.onLeave = onLeave;\n\t\tthis.axis = axis;\n\t}\n\n\t/** Start listening to pointer events. */\n\tstart = (): void => {\n\t\tif (this.active) return;\n\t\tthis.active = true;\n\t\tthis.eventsEl.addEventListener(\"pointermove\", this.handlePointerMove);\n\t\tthis.eventsEl.addEventListener(\"pointerenter\", this.handlePointerEnter);\n\t\tthis.eventsEl.addEventListener(\"pointerleave\", this.handlePointerLeave);\n\t\tthis.eventsEl.addEventListener(\"pointerdown\", this.handlePointerDown);\n\t\tthis.eventsEl.addEventListener(\"pointerup\", this.handlePointerUp);\n\t\tthis.eventsEl.addEventListener(\"pointercancel\", this.handlePointerUp);\n\t};\n\n\t/** Stop listening and remove all pointer event listeners. */\n\tstop = (): void => {\n\t\tif (!this.active) return;\n\t\tthis.active = false;\n\t\tthis.eventsEl.removeEventListener(\"pointermove\", this.handlePointerMove);\n\t\tthis.eventsEl.removeEventListener(\"pointerenter\", this.handlePointerEnter);\n\t\tthis.eventsEl.removeEventListener(\"pointerleave\", this.handlePointerLeave);\n\t\tthis.eventsEl.removeEventListener(\"pointerdown\", this.handlePointerDown);\n\t\tthis.eventsEl.removeEventListener(\"pointerup\", this.handlePointerUp);\n\t\tthis.eventsEl.removeEventListener(\"pointercancel\", this.handlePointerUp);\n\t};\n\n\t/** Update the axis lock at runtime. */\n\tsetAxis = (axis: Axis): void => {\n\t\tthis.axis = axis;\n\t};\n\n\tprivate handlePointerDown = (e: PointerEvent): void => {\n\t\tthis.handlePointerEnter();\n\t\tthis.handlePointerMove(e);\n\t};\n\n\tprivate handlePointerUp = (): void => {\n\t\tthis.handlePointerLeave();\n\t};\n\n\t/**\n\t * Compute normalized [-1, 1] position from pointer coordinates\n\t * relative to the element's bounding rect. Respects axis locking.\n\t */\n\tprivate handlePointerMove = (e: PointerEvent): void => {\n\t\tif (!this.rect) {\n\t\t\tthis.rect = this.eventsEl.getBoundingClientRect();\n\t\t}\n\n\t\t// Use offsetWidth/Height for more stable dimensions (not affected by 3D transforms)\n\t\tconst width = this.eventsEl.offsetWidth || this.rect.width;\n\t\tconst height = this.eventsEl.offsetHeight || this.rect.height;\n\n\t\tconst rawX = (e.clientX - this.rect.left) / width;\n\t\tconst rawY = (e.clientY - this.rect.top) / height;\n\n\t\t// Clamp values if pointer is captured but outside bounds\n\t\tconst clampedX = Math.max(0, Math.min(1, rawX));\n\t\tconst clampedY = Math.max(0, Math.min(1, rawY));\n\n\t\tconst x = this.axis === \"y\" ? 0 : clampedX * 2 - 1;\n\t\tconst y = this.axis === \"x\" ? 0 : clampedY * 2 - 1;\n\n\t\tthis.onMove({ x, y });\n\t};\n\n\tprivate handlePointerEnter = (): void => {\n\t\t// Refresh rect on enter. If it's already in motion, this might be slightly off,\n\t\t// but it's still better than refreshing it on every move.\n\t\tthis.rect = this.eventsEl.getBoundingClientRect();\n\t\tthis.onEnter?.();\n\t};\n\n\tprivate handlePointerLeave = (): void => {\n\t\tthis.rect = null;\n\t\tthis.onLeave?.();\n\t};\n}\n","import { DEFAULT_OPTIONS } from \"./constants.js\";\nimport { GlareEffect } from \"./effects/glare.js\";\nimport { ShadowEffect } from \"./effects/shadow.js\";\nimport type { Layer } from \"./layers.js\";\nimport { cleanupLayers, scanLayers } from \"./layers.js\";\nimport { MotionSensor } from \"./sensors/motion.js\";\nimport type { SensorOutput } from \"./sensors/pointer.js\";\nimport { PointerSensor } from \"./sensors/pointer.js\";\nimport type {\n\tEventCallback,\n\tLevitaEventMap,\n\tLevitaOptions,\n\tTiltValues,\n\tUpdatableOptions,\n} from \"./types.js\";\n\n/**\n * Main entry point for the Levita 3D tilt effect.\n *\n * Orchestrates sensors (pointer, accelerometer), visual effects (glare, shadow),\n * and multi-layer parallax. All rendering is driven by CSS custom properties —\n * no requestAnimationFrame loop runs during interaction.\n *\n * @example\n * ```ts\n * import { Levita } from 'levita-js';\n * import 'levita-js/style.css';\n *\n * const tilt = new Levita(element, { glare: true, shadow: true });\n * tilt.on('move', ({ x, y }) => console.log(x, y));\n * ```\n */\nexport class Levita {\n\tprivate el: HTMLElement;\n\tprivate options: LevitaOptions;\n\tprivate pointerSensor: PointerSensor;\n\tprivate motionSensor: MotionSensor | null = null;\n\tprivate glareEffect: GlareEffect | null = null;\n\tprivate shadowEffect: ShadowEffect | null = null;\n\tprivate layers: Layer[] = [];\n\tprivate listeners = new Map<string, Set<EventCallback<unknown>>>();\n\tprivate destroyed = false;\n\tprivate gyroscopeRequested = false;\n\tprivate rafId: number | null = null;\n\n\t/**\n\t * @param el - The DOM element to apply the tilt effect to\n\t * @param options - Configuration options (all optional, sensible defaults)\n\t */\n\tconstructor(el: HTMLElement, options: Partial<LevitaOptions> = {}) {\n\t\tthis.el = el;\n\t\tthis.options = { ...DEFAULT_OPTIONS, ...options };\n\n\t\tthis.el.classList.add(\"levita\");\n\t\tthis.applyBaseProperties();\n\n\t\tthis.layers = scanLayers(this.el);\n\n\t\tif (this.options.glare) {\n\t\t\tthis.glareEffect = new GlareEffect(this.el, this.options.maxGlare);\n\t\t}\n\t\tif (this.options.shadow) {\n\t\t\tthis.shadowEffect = new ShadowEffect(this.el);\n\t\t}\n\n\t\tthis.pointerSensor = new PointerSensor(\n\t\t\tthis.el,\n\t\t\t(values) => this.handleSensorInput(values),\n\t\t\t() => this.handleEnter(),\n\t\t\t() => this.handleLeave(),\n\t\t\tthis.options.axis,\n\t\t\tthis.options.eventsEl,\n\t\t);\n\n\t\tif (this.options.gyroscope !== false && MotionSensor.isSupported()) {\n\t\t\tthis.motionSensor = new MotionSensor(\n\t\t\t\t(values) => this.handleSensorInput(values),\n\t\t\t\tthis.options.axis,\n\t\t\t);\n\n\t\t\tif (this.options.gyroscope === \"auto\") {\n\t\t\t\tthis.el.addEventListener(\"click\", this.handleFirstTouch, { once: true });\n\t\t\t}\n\t\t}\n\n\t\tif (!this.options.disabled) {\n\t\t\tthis.enable();\n\t\t}\n\t}\n\n\t/**\n\t * On first touch (iOS auto mode), request accelerometer permission\n\t * and switch from pointer to motion sensor if granted.\n\t */\n\tprivate handleFirstTouch = async (): Promise<void> => {\n\t\tif (this.destroyed || this.gyroscopeRequested || !this.motionSensor) return;\n\t\tthis.gyroscopeRequested = true;\n\n\t\tconst granted = await this.motionSensor.requestPermission();\n\t\tif (granted) {\n\t\t\tthis.pointerSensor.stop();\n\t\t\tthis.setTransition(false);\n\t\t\tthis.motionSensor.start();\n\t\t}\n\t};\n\n\t/**\n\t * Update options at runtime without destroying/recreating the instance.\n\t *\n\t * Only \"lightweight\" options are supported — those that don't require\n\t * DOM mutations. Options like `glare`, `shadow`, `gyroscope`, `eventsEl`,\n\t * `disabled`, and `maxGlare` require a full destroy/recreate cycle\n\t * (or use `enable()`/`disable()` for toggling the effect).\n\t */\n\tupdate = (options: Partial<UpdatableOptions>): void => {\n\t\tif (this.destroyed) return;\n\t\tObject.assign(this.options, options);\n\t\tthis.applyBaseProperties();\n\n\t\tif (options.axis !== undefined) {\n\t\t\tthis.pointerSensor.setAxis(options.axis);\n\t\t\tthis.motionSensor?.setAxis(options.axis);\n\t\t}\n\t};\n\n\t/** Re-enable the tilt effect after a `disable()` call. */\n\tenable = (): void => {\n\t\tif (this.destroyed) return;\n\t\tthis.options.disabled = false;\n\t\tthis.pointerSensor.start();\n\t};\n\n\t/** Pause the tilt effect and reset the element to its neutral position. */\n\tdisable = (): void => {\n\t\tthis.options.disabled = true;\n\t\tthis.pointerSensor.stop();\n\t\tthis.motionSensor?.stop();\n\t\tthis.resetTransform();\n\t};\n\n\t/**\n\t * Manually request accelerometer permission (for `gyroscope: true` mode).\n\t * Must be called from a user gesture on iOS 13+.\n\t *\n\t * @returns Whether permission was granted\n\t */\n\trequestPermission = async (): Promise<boolean> => {\n\t\tif (!this.motionSensor) return false;\n\t\tconst granted = await this.motionSensor.requestPermission();\n\t\tif (granted) {\n\t\t\tthis.pointerSensor.stop();\n\t\t\tthis.setTransition(false);\n\t\t\tthis.motionSensor.start();\n\t\t}\n\t\treturn granted;\n\t};\n\n\t/**\n\t * Fully clean up: stop sensors, remove effects, restore the element\n\t * to its original state. The instance cannot be reused after this.\n\t */\n\tdestroy = (): void => {\n\t\tif (this.destroyed) return;\n\t\tthis.destroyed = true;\n\n\t\tif (this.rafId) cancelAnimationFrame(this.rafId);\n\t\tthis.pointerSensor.stop();\n\t\tthis.motionSensor?.stop();\n\t\tthis.glareEffect?.destroy();\n\t\tthis.shadowEffect?.destroy();\n\t\tcleanupLayers(this.layers);\n\n\t\tthis.el.classList.remove(\"levita\");\n\t\tthis.removeBaseProperties();\n\t\tthis.listeners.clear();\n\n\t\tthis.el.removeEventListener(\"click\", this.handleFirstTouch);\n\t};\n\n\t/**\n\t * Register an event listener.\n\t *\n\t * @param event - Event name: 'move', 'enter', or 'leave'\n\t * @param callback - Handler function\n\t */\n\ton = <K extends keyof LevitaEventMap>(\n\t\tevent: K,\n\t\tcallback: EventCallback<LevitaEventMap[K]>,\n\t): void => {\n\t\tif (!this.listeners.has(event)) {\n\t\t\tthis.listeners.set(event, new Set());\n\t\t}\n\t\tthis.listeners.get(event)?.add(callback as EventCallback<unknown>);\n\t};\n\n\t/**\n\t * Remove a previously registered event listener.\n\t *\n\t * @param event - Event name\n\t * @param callback - The exact handler reference passed to `on()`\n\t */\n\toff = <K extends keyof LevitaEventMap>(\n\t\tevent: K,\n\t\tcallback: EventCallback<LevitaEventMap[K]>,\n\t): void => {\n\t\tthis.listeners.get(event)?.delete(callback as EventCallback<unknown>);\n\t};\n\n\t/** Emit an event to all registered listeners. */\n\tprivate emit = <K extends keyof LevitaEventMap>(event: K, data: LevitaEventMap[K]): void => {\n\t\tconst callbacks = this.listeners.get(event);\n\t\tif (callbacks) {\n\t\t\tfor (const cb of callbacks) {\n\t\t\t\tcb(data);\n\t\t\t}\n\t\t}\n\t};\n\n\t/**\n\t * Process normalized sensor input and update CSS custom properties.\n\t * Maps sensor X/Y to rotateY/rotateX (swapped, rotateX = vertical tilt).\n\t *\n\t * Note: We use requestAnimationFrame to throttle updates. High-polling rate mice\n\t * can fire hundreds of events per second, which would saturate the main thread\n\t * with CSS variable updates and style recalculations. This ensures we only\n\t * update the DOM once per browser frame.\n\t */\n\tprivate handleSensorInput = (input: SensorOutput): void => {\n\t\tif (this.options.disabled) return;\n\n\t\tconst multiplier = this.options.reverse ? -1 : 1;\n\t\tconst x = input.y * this.options.max * multiplier;\n\t\tconst y = input.x * this.options.max * multiplier * -1;\n\n\t\t// Cancel any pending update from the same frame to avoid style thrashing\n\t\tif (this.rafId) cancelAnimationFrame(this.rafId);\n\n\t\tthis.rafId = requestAnimationFrame(() => {\n\t\t\tthis.el.style.setProperty(\"--levita-x\", `${x}deg`);\n\t\t\tthis.el.style.setProperty(\"--levita-y\", `${y}deg`);\n\t\t\tthis.el.style.setProperty(\"--levita-percent-x\", String(input.x));\n\t\t\tthis.el.style.setProperty(\"--levita-percent-y\", String(input.y));\n\n\t\t\tthis.glareEffect?.update(input.x, input.y);\n\t\t\tthis.shadowEffect?.update(input.x, input.y);\n\n\t\t\tconst values: TiltValues = {\n\t\t\t\tx,\n\t\t\t\ty,\n\t\t\t\tpercentX: input.x,\n\t\t\t\tpercentY: input.y,\n\t\t\t};\n\t\t\tthis.emit(\"move\", values);\n\n\t\t\tthis.rafId = null;\n\t\t});\n\t};\n\n\tprivate handleEnter = (): void => {\n\t\tthis.setTransition(false);\n\t\tthis.el.style.setProperty(\"--levita-scale\", String(this.options.scale));\n\t\tthis.emit(\"enter\", undefined);\n\t};\n\n\tprivate handleLeave = (): void => {\n\t\tif (this.rafId) {\n\t\t\tcancelAnimationFrame(this.rafId);\n\t\t\tthis.rafId = null;\n\t\t}\n\t\tthis.setTransition(true);\n\t\tif (this.options.reset) {\n\t\t\tthis.resetTransform();\n\t\t}\n\t\tthis.emit(\"leave\", undefined);\n\t};\n\n\t/** Toggle the CSS transition on or off. */\n\tprivate setTransition = (on: boolean): void => {\n\t\tthis.el.style.setProperty(\"--levita-speed\", on ? `${this.options.speed}ms` : \"0ms\");\n\t};\n\n\t/** Reset the element to its neutral (non-tilted) position. */\n\tprivate resetTransform = (): void => {\n\t\tthis.el.style.setProperty(\"--levita-x\", \"0deg\");\n\t\tthis.el.style.setProperty(\"--levita-y\", \"0deg\");\n\t\tthis.el.style.setProperty(\"--levita-scale\", \"1\");\n\t\tthis.el.style.setProperty(\"--levita-percent-x\", \"0\");\n\t\tthis.el.style.setProperty(\"--levita-percent-y\", \"0\");\n\t\tthis.glareEffect?.update(0, 0);\n\t\tthis.shadowEffect?.update(0, 0);\n\t};\n\n\t/** Apply initial CSS custom properties from options. */\n\tprivate applyBaseProperties = (): void => {\n\t\tthis.el.style.setProperty(\"--levita-perspective\", `${this.options.perspective}px`);\n\t\tthis.el.style.setProperty(\"--levita-speed\", `${this.options.speed}ms`);\n\t\tthis.el.style.setProperty(\"--levita-easing\", this.options.easing);\n\t};\n\n\t/** Remove all Levita CSS custom properties from the element. */\n\tprivate removeBaseProperties = (): void => {\n\t\tthis.el.style.removeProperty(\"--levita-perspective\");\n\t\tthis.el.style.removeProperty(\"--levita-speed\");\n\t\tthis.el.style.removeProperty(\"--levita-easing\");\n\t\tthis.el.style.removeProperty(\"--levita-x\");\n\t\tthis.el.style.removeProperty(\"--levita-y\");\n\t\tthis.el.style.removeProperty(\"--levita-scale\");\n\t\tthis.el.style.removeProperty(\"--levita-percent-x\");\n\t\tthis.el.style.removeProperty(\"--levita-percent-y\");\n\t};\n}\n"],"mappings":"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"}
|
package/dist/style.css
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "levita-js",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Lightweight 3D tilt & parallax with accelerometer support",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
@@ -50,6 +50,7 @@
|
|
|
50
50
|
},
|
|
51
51
|
"scripts": {
|
|
52
52
|
"build": "tsdown && cp src/style.css dist/style.css",
|
|
53
|
+
"type-check": "tsc --noEmit",
|
|
53
54
|
"dev": "tsdown --watch"
|
|
54
55
|
}
|
|
55
56
|
}
|