Skip to content

Jeromearsene/levita

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

162 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Levita — Lightweight 3D tilt & parallax library with accelerometer support

CI npm npm downloads bundle size StackBlitz license demo

  • CSS-driven — No requestAnimationFrame loop. CSS custom properties + compositor = GPU-accelerated.
  • ~2KB gzipped — Core engine only.
  • Accelerometer — Auto-detects gyroscope, handles iOS permissions transparently.
  • Multi-layer parallaxdata-levita-offset on children for depth layering.
  • Framework first — Official, lightweight wrappers for React, Vue, Svelte & Angular. See examples.
  • Styling agnostic — Works with any CSS framework (Tailwind, Bootstrap, etc.) or vanilla CSS. See examples.

Table of Contents

Ecosystem

Levita is designed to be framework-agnostic. Choose your flavor:

Framework Min Version Size (gzip) Playground
Vanilla JS Vanilla JS - core size Try Vanilla JS on StackBlitz
React React react min version react size Try React on StackBlitz
Vue Vue vue min version vue size Try Vue on StackBlitz
Svelte Svelte svelte min version svelte size Try Svelte on StackBlitz
Angular Angular angular min version angular size Try Angular on StackBlitz

Effects

Tilt Glare
Tilt Glare
Shadow Combined
Shadow Combined

Install

# Core (vanilla JS/TS)
npm install levita-js

# React wrapper
npm install @levita-js/react

# Vue wrapper
npm install @levita-js/vue

# Svelte wrapper
npm install @levita-js/svelte

# Angular wrapper
npm install @levita-js/angular

Quick Start

Vanilla

import { Levita } from "levita-js";
import "levita-js/style.css";

new Levita(document.getElementById("card"), {
  glare: true,
  shadow: true,
});

Open Vanilla JS example in StackBlitz

React

import { Tilt } from "@levita-js/react";
import "@levita-js/react/style.css";

function Card() {
  return (
    <Tilt glare shadow>
      <h2>Hello</h2>
    </Tilt>
  );
}

Open React example in StackBlitz

Vue

<script setup>
import { Tilt } from "@levita-js/vue";
import "@levita-js/vue/style.css";
</script>

<template>
  <Tilt glare shadow>
    <h2>Hello</h2>
  </Tilt>
</template>

Open Vue example in StackBlitz

Svelte

<script>
  import { tilt } from "@levita-js/svelte";
  import "@levita-js/svelte/style.css";
</script>

<div use:tilt={{ glare: true, shadow: true }}>
  <h2>Hello</h2>
</div>

Open Svelte example in StackBlitz

Angular

import { LevitaDirective } from "@levita-js/angular";
import "@levita-js/angular/style.css";

@Component({
  selector: "app-card",
  standalone: true,
  imports: [LevitaDirective],
  template: `
    <div [levita]="{ glare: true, shadow: true }">
      <h2>Hello</h2>
    </div>
  `,
})
export class CardComponent {}

Open Angular example in StackBlitz

Parallax Layers

Add data-levita-offset to children for multi-depth parallax. Positive values come forward, negative go back:

<div id="scene">
  <img data-levita-offset="-5" src="bg.png" />
  <img data-levita-offset="0" src="mid.png" />
  <img data-levita-offset="10" src="fg.png" />
</div>
new Levita(document.getElementById("scene"));

Grouped Instances

You can make multiple Levita instances react to the same pointer movement by using the eventsEl option. This is useful for grids where all cards should tilt together:

const container = document.getElementById("grid-container");
const cards = document.querySelectorAll(".card");

for (const card of cards) {
  new Levita(card, { eventsEl: container });
}

Accelerometer

Levita auto-detects accelerometer support:

  • Android — works immediately, no permission needed.
  • iOS 13+ — permission requested on first touch (silent fallback if denied).
// Auto mode (default) — handles everything
new Levita(el, { gyroscope: "auto" });

// Manual mode — you control when to ask
const instance = new Levita(el, { gyroscope: true });
button.addEventListener("click", async () => {
  const granted = await instance.requestPermission();
  console.log("Gyroscope:", granted ? "enabled" : "denied");
});

// Disabled
new Levita(el, { gyroscope: false });

Fine-tuning gyroscope behavior

new Levita(el, {
  gyroscope: "auto",
  gyroRange: 40,       // More reactive (less tilt needed)
  gyroSmoothing: 0.1,  // Smoother movement
});
  • gyroRange — Total physical tilt range in degrees mapped to full effect. Lower = more reactive. Default: 60.
  • gyroSmoothing — Exponential moving average factor. Lower = smoother but more latent. Default: 0.15.

Options

Option Type Default Description
max number 15 Max tilt angle in degrees
perspective number 1000 CSS perspective in px
scale number 1.05 Scale factor on hover
speed number 200 Transition duration in ms
easing string 'ease-out' CSS easing function
reverse boolean false Invert tilt direction
axis 'x' | 'y' | null null Lock to single axis
reset boolean true Reset on pointer leave
glare boolean false Enable glare effect
maxGlare number 0.5 Max glare opacity (0-1)
shadow boolean false Enable dynamic shadow
gyroscope 'auto' | boolean 'auto' Accelerometer mode
gyroRange number 60 Physical tilt range in degrees
gyroSmoothing number 0.15 Sensor smoothing factor (0-1)
disabled boolean false Disable the effect
eventsEl HTMLElement | null null Element to listen for events on

Events

const instance = new Levita(el);

instance.on("move", ({ x, y, percentX, percentY }) => {
  console.log(`Tilt: ${x}°, ${y}°`);
});

instance.on("enter", () => console.log("Pointer entered"));
instance.on("leave", () => console.log("Pointer left"));

// Remove listener
instance.off("move", handler);

Methods

instance.enable(); // Re-enable after disable
instance.disable(); // Pause and reset
instance.destroy(); // Full cleanup
await instance.requestPermission(); // Manual gyroscope permission

How It Works

Most tilt libraries (like vanilla-tilt) run a requestAnimationFrame loop that recalculates and applies the transform matrix on every frame in JavaScript. This means JS is active between every frame, and high-polling-rate mice (1000Hz+) can flood the main thread with style recalculations.

Levita takes a different approach with CSS custom properties:

  1. Pointer or accelerometer input fires → JS computes the tilt angle
  2. Updates are coalesced via requestAnimationFrame — even if the mouse fires 1000 events/sec, only one DOM update happens per frame
  3. JS sets --levita-x and --levita-y as CSS custom properties on the element
  4. A CSS transform rule reads those properties, and transition smooths the movement — entirely on the GPU compositor thread
pointer/gyro event (may fire at 1000Hz)
  │
  ▼
rAF coalescing (1 update per frame)
  │
  ▼
JS sets --levita-x, --levita-y  ← only JS work
  │
  ▼
CSS transform + transition       ← GPU compositor, no JS

The result:

  • No JS between frames — JavaScript only runs when input changes, once per frame
  • GPU-accelerated — the browser's compositor thread handles the animation
  • High-polling-rate safe — 1000Hz mice don't saturate the main thread
  • Lower CPU usage — measured via Vitest bench, see Benchmarks

Comparison

Feature Levita Atropos vanilla-tilt
Bundle size (gzip) ~2KB ~2KB ~3-4KB
Animation strategy CSS custom properties CSS transitions rAF loop
Tree-shakeable
Multi-layer parallax ✅ (data attrs) ✅ (data attrs)
Accelerometer Auto + manual (calibrated) Partial (no calibration)
Grouped instances ✅ (eventsEl) ✅ (stretchX/Y/Z) ✅ (mouse-event-element)
Runtime option update ✅ (update())
React Official wrapper Official wrapper Community
Vue Official wrapper Web Component Community
Svelte Official wrapper
Angular Official wrapper Web Component Community
TypeScript Native (source in TS) Declaration file DefinitelyTyped
Last published 2026 2023 2021

Benchmarks

Measured with Vitest bench (happy-dom):

Scenario ops/s
Basic init + destroy bench
Init with glare + shadow bench
Init with 5 parallax layers bench
Pointer move update bench
Pointer move with glare + shadow bench

Run locally: pnpm bench

Automatically updated at each release.

Contributing

Levita is a monorepo managed with pnpm. For details on how to set up the development environment, run tests, and understand our release workflow, check out our Development Guide.

Sponsors

If you find Levita useful, consider supporting its development:

Buy Me a Coffee GitHub Sponsors

Star History

Star History Chart

Roadmap

Interested in what's coming next? You can follow the development progress and upcoming features on our public project board:

Levita Project Board

License

MIT