All skills
freshtechbro avatar

/lightweight-3d-effects

@1da73fe

Lightweight 3D effects for decorative elements and micro-interactions using Zdog, Vanta.js, and Vanilla-Tilt.js. Use this skill when adding pseudo-3D illustrations, animated backgrounds, parallax tilt effects, decorative 3D elements, or subtle depth effects without heavy frameworks. Triggers on tasks involving Zdog pseudo-3D, Vanta.js backgrounds, Vanilla-Tilt parallax, card tilt effects, hero section animations, or lightweight landing page visuals. Ideal for performance-focused designs.

Use this Skill: https://skilld.dev/gh/freshtechbro/claudedesignskills/lightweight-3d-effects

This session only. Nothing lands on disk.

referencestilt_patterns.md

≈5.2k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Vanilla-Tilt.js Patterns Reference

Complete reference for Vanilla-Tilt.js - smooth 3D tilt effect for any DOM element.

Version: 1.8.1 License: MIT GitHub: https://github.com/micku7zu/vanilla-tilt.js


Table of Contents

  1. Getting Started
  2. Configuration Options
  3. Methods
  4. Events
  5. Common Patterns
  6. Advanced Techniques
  7. Framework Integration
  8. Performance

Getting Started

CDN Installation

<script src="https://cdnjs.cloudflare.com/ajax/libs/vanilla-tilt/1.8.1/vanilla-tilt.min.js"></script>

<div class="tilt-card" data-tilt></div>

<script>
VanillaTilt.init(document.querySelector(".tilt-card"));
</script>

NPM Installation

npm install vanilla-tilt
import VanillaTilt from 'vanilla-tilt';

VanillaTilt.init(document.querySelector(".tilt-card"));

Basic Usage

HTML Attribute (No JavaScript):

<div class="tilt-card" data-tilt></div>

JavaScript Initialization:

<div class="tilt-card"></div>

<script>
VanillaTilt.init(document.querySelector(".tilt-card"), {
  max: 25,
  speed: 400,
  glare: true,
  "max-glare": 0.5
});
</script>

Multiple Elements:

VanillaTilt.init(document.querySelectorAll(".tilt-card"));

Configuration Options

Complete Options Reference

VanillaTilt.init(element, {
  // Rotation
  reverse: false,               // Reverse tilt direction
  max: 15,                      // Max tilt angle (degrees)
  startX: 0,                    // Starting tilt on X axis (degrees)
  startY: 0,                    // Starting tilt on Y axis (degrees)
  perspective: 1000,            // Transform perspective (px)

  // Scale
  scale: 1.0,                   // Scale on hover (2 = 200%)

  // Speed & Easing
  speed: 300,                   // Transition speed (ms)
  transition: true,             // Enable/disable transition
  easing: "cubic-bezier(.03,.98,.52,.99)",  // CSS easing

  // Axis Control
  axis: null,                   // Restrict axis ("x" or "y")
  reset: true,                  // Reset on mouseout
  "reset-to-start": true,       // Reset to startX/startY values

  // Glare Effect
  glare: false,                 // Enable glare effect
  "max-glare": 1,               // Max glare opacity (0-1)
  "glare-prerender": false,     // Pre-render glare element

  // Mouse/Touch
  "mouse-event-element": null,  // Element for mouse detection
  "full-page-listening": false, // Listen to mouse on entire page
  gyroscope: true,              // Enable gyroscope (mobile)
  gyroscopeMinAngleX: -45,      // Min gyro angle X
  gyroscopeMaxAngleX: 45,       // Max gyro angle X
  gyroscopeMinAngleY: -45,      // Min gyro angle Y
  gyroscopeMaxAngleY: 45        // Max gyro angle Y
});

Configuration Options Details

Rotation Options

max

Maximum tilt angle in degrees.

// Subtle tilt
VanillaTilt.init(element, { max: 10 });

// Dramatic tilt
VanillaTilt.init(element, { max: 35 });

// Extreme tilt
VanillaTilt.init(element, { max: 50 });

Recommended: 15-25 for most use cases


reverse

Reverse the tilt direction.

VanillaTilt.init(element, {
  reverse: true  // Tilts opposite to mouse movement
});

Use case: Parallax layers moving in opposite directions


startX / startY

Initial tilt angle (degrees).

VanillaTilt.init(element, {
  startX: 10,    // Start tilted 10° on X axis
  startY: -5     // Start tilted -5° on Y axis
});

perspective

CSS perspective value (pixels).

// Subtle 3D
VanillaTilt.init(element, { perspective: 2000 });

// Strong 3D
VanillaTilt.init(element, { perspective: 500 });

Lower values = stronger 3D effect


Scale Options

scale

Element scale on hover (1.0 = 100%).

// Subtle zoom
VanillaTilt.init(element, { scale: 1.05 });

// Noticeable zoom
VanillaTilt.init(element, { scale: 1.2 });

// Dramatic zoom
VanillaTilt.init(element, { scale: 1.5 });

Speed & Easing

speed

Transition duration in milliseconds.

// Fast (snappy)
VanillaTilt.init(element, { speed: 200 });

// Smooth (recommended)
VanillaTilt.init(element, { speed: 400 });

// Slow (fluid)
VanillaTilt.init(element, { speed: 800 });

easing

CSS easing function for transitions.

// Linear
VanillaTilt.init(element, {
  easing: "linear"
});

// Ease out (recommended)
VanillaTilt.init(element, {
  easing: "cubic-bezier(.03,.98,.52,.99)"
});

// Bounce
VanillaTilt.init(element, {
  easing: "cubic-bezier(.68,-0.55,.265,1.55)"
});

// Elastic
VanillaTilt.init(element, {
  easing: "cubic-bezier(.6,.04,.98,.335)"
});

transition

Enable/disable CSS transitions.

// Disable for instant response
VanillaTilt.init(element, { transition: false });

Axis Control

axis

Restrict tilt to one axis.

// Horizontal tilt only
VanillaTilt.init(element, { axis: "x" });

// Vertical tilt only
VanillaTilt.init(element, { axis: "y" });

// Both axes (default)
VanillaTilt.init(element, { axis: null });

reset

Reset tilt when mouse leaves element.

// Keep tilt on mouseout
VanillaTilt.init(element, { reset: false });

// Reset on mouseout (default)
VanillaTilt.init(element, { reset: true });

reset-to-start

Reset to startX/startY values instead of 0.

VanillaTilt.init(element, {
  startX: 10,
  startY: -5,
  "reset-to-start": true  // Resets to (10, -5) not (0, 0)
});

Glare Effect

glare

Enable glossy glare overlay.

VanillaTilt.init(element, {
  glare: true,
  "max-glare": 0.5  // 50% max opacity
});

Note: Automatically adds a .js-tilt-glare element inside the tilt element.


max-glare

Maximum glare opacity (0 to 1).

// Subtle glare
VanillaTilt.init(element, {
  glare: true,
  "max-glare": 0.2
});

// Strong glare
VanillaTilt.init(element, {
  glare: true,
  "max-glare": 0.8
});

glare-prerender

Pre-render glare element in HTML (performance optimization).

<div class="tilt-card" data-tilt>
  <div class="js-tilt-glare">
    <div class="js-tilt-glare-inner"></div>
  </div>
  <!-- Your content -->
</div>
VanillaTilt.init(element, {
  glare: true,
  "glare-prerender": true
});

Mouse/Touch Options

mouse-event-element

Use different element for mouse tracking.

VanillaTilt.init(element, {
  "mouse-event-element": document.querySelector(".parent")
});

Use case: Tilt element based on mouse position over larger container.


full-page-listening

Track mouse position across entire page.

VanillaTilt.init(element, {
  "full-page-listening": true
});

Use case: Parallax effects that respond to page-wide mouse movement.


Gyroscope Options

gyroscope

Enable device orientation (mobile).

VanillaTilt.init(element, {
  gyroscope: true,
  gyroscopeMinAngleX: -45,
  gyroscopeMaxAngleX: 45,
  gyroscopeMinAngleY: -45,
  gyroscopeMaxAngleY: 45
});

Note: Requires HTTPS and user permission on iOS.


Methods

destroy()

Remove tilt effect and event listeners.

const element = document.querySelector(".tilt-card");
VanillaTilt.init(element);

// Later...
element.vanillaTilt.destroy();

Important: Always call destroy() when removing elements to prevent memory leaks.


reset()

Reset tilt to default position.

element.vanillaTilt.reset();

getValues()

Get current tilt values.

const values = element.vanillaTilt.getValues();

console.log(values);
// {
//   tiltX: 10.5,
//   tiltY: -5.2,
//   percentageX: 52.5,
//   percentageY: 47.8,
//   angle: 11.7
// }

Return values:

  • tiltX: Current X-axis tilt (degrees)
  • tiltY: Current Y-axis tilt (degrees)
  • percentageX: Mouse X position as percentage (0-100)
  • percentageY: Mouse Y position as percentage (0-100)
  • angle: Total tilt angle (degrees)

setOptions(options)

Update options after initialization.

element.vanillaTilt.setOptions({
  max: 35,
  speed: 600
});

Events

tiltChange

Fired when tilt changes.

element.addEventListener("tiltChange", (e) => {
  console.log("Tilt changed:", e.detail);
  // {
  //   tiltX: 10.5,
  //   tiltY: -5.2,
  //   percentageX: 52.5,
  //   percentageY: 47.8,
  //   angle: 11.7
  // }
});

mouseLeave

Fired when mouse leaves element.

element.addEventListener("mouseLeave", () => {
  console.log("Mouse left tilt element");
});

mouseEnter

Fired when mouse enters element.

element.addEventListener("mouseEnter", () => {
  console.log("Mouse entered tilt element");
});

Common Patterns

Pattern 1: Card Gallery

Tilting card grid with glare.

<div class="card-grid">
  <div class="tilt-card" data-tilt>
    <img src="image1.jpg" alt="Card 1">
    <h3>Card Title</h3>
  </div>
  <div class="tilt-card" data-tilt>
    <img src="image2.jpg" alt="Card 2">
    <h3>Card Title</h3>
  </div>
  <!-- More cards... -->
</div>
.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 2rem;
  padding: 2rem;
}

.tilt-card {
  background: white;
  border-radius: 10px;
  overflow: hidden;
  box-shadow: 0 10px 30px rgba(0,0,0,0.2);
  transform-style: preserve-3d;
}

.tilt-card img {
  width: 100%;
  display: block;
  transform: translateZ(20px);
}

.tilt-card h3 {
  padding: 1rem;
  transform: translateZ(40px);
}
VanillaTilt.init(document.querySelectorAll(".tilt-card"), {
  max: 15,
  speed: 400,
  glare: true,
  "max-glare": 0.3,
  scale: 1.05
});

Pattern 2: Parallax Layers

Multiple layered elements with different tilt depths.

<div class="parallax-container" data-tilt>
  <div class="layer layer-1">Background</div>
  <div class="layer layer-2">Midground</div>
  <div class="layer layer-3">Foreground</div>
</div>
.parallax-container {
  position: relative;
  width: 400px;
  height: 400px;
  transform-style: preserve-3d;
}

.layer {
  position: absolute;
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 10px;
}

.layer-1 {
  background: #667eea;
  transform: translateZ(-50px);
}

.layer-2 {
  background: #764ba2;
  opacity: 0.8;
  transform: translateZ(0px);
}

.layer-3 {
  background: #f093fb;
  opacity: 0.6;
  transform: translateZ(50px);
}
VanillaTilt.init(document.querySelector(".parallax-container"), {
  max: 25,
  speed: 400,
  perspective: 1000
});

Pattern 3: Hover Reveal

Reveal content on tilt hover.

<div class="reveal-card" data-tilt>
  <div class="card-front">
    <h2>Hover Me</h2>
  </div>
  <div class="card-back">
    <p>Hidden content revealed!</p>
  </div>
</div>
.reveal-card {
  width: 300px;
  height: 400px;
  position: relative;
  transform-style: preserve-3d;
}

.card-front,
.card-back {
  position: absolute;
  width: 100%;
  height: 100%;
  backface-visibility: hidden;
  border-radius: 10px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.card-front {
  background: #667eea;
  transform: translateZ(20px);
}

.card-back {
  background: #764ba2;
  transform: translateZ(-20px) rotateY(180deg);
  opacity: 0;
  transition: opacity 0.3s;
}

.reveal-card:hover .card-back {
  opacity: 1;
}
VanillaTilt.init(document.querySelector(".reveal-card"), {
  max: 25,
  speed: 400,
  glare: true
});

Pattern 4: 3D Button

Elevated button with shadow that follows tilt.

<button class="tilt-button" data-tilt>
  <span>Click Me</span>
</button>
.tilt-button {
  padding: 1.5rem 3rem;
  font-size: 1.2rem;
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  color: white;
  border: none;
  border-radius: 50px;
  cursor: pointer;
  transform-style: preserve-3d;
  box-shadow: 0 10px 30px rgba(0,0,0,0.3);
  transition: box-shadow 0.3s;
}

.tilt-button span {
  display: block;
  transform: translateZ(20px);
}

.tilt-button:hover {
  box-shadow: 0 15px 40px rgba(0,0,0,0.4);
}
VanillaTilt.init(document.querySelector(".tilt-button"), {
  max: 20,
  speed: 300,
  scale: 1.1,
  glare: true,
  "max-glare": 0.5
});

Pattern 5: Product Showcase

E-commerce product card with layered depth.

<div class="product-card" data-tilt>
  <div class="product-badge">NEW</div>
  <img src="product.jpg" alt="Product" class="product-image">
  <div class="product-info">
    <h3 class="product-title">Product Name</h3>
    <p class="product-price">$99.99</p>
    <button class="product-button">Add to Cart</button>
  </div>
</div>
.product-card {
  width: 320px;
  background: white;
  border-radius: 15px;
  overflow: hidden;
  transform-style: preserve-3d;
  box-shadow: 0 20px 60px rgba(0,0,0,0.15);
}

.product-badge {
  position: absolute;
  top: 10px;
  right: 10px;
  background: #ff6b6b;
  color: white;
  padding: 0.5rem 1rem;
  border-radius: 20px;
  font-weight: bold;
  transform: translateZ(60px);
  z-index: 10;
}

.product-image {
  width: 100%;
  display: block;
  transform: translateZ(30px);
}

.product-info {
  padding: 1.5rem;
  transform: translateZ(40px);
}

.product-button {
  width: 100%;
  padding: 0.75rem;
  background: #667eea;
  color: white;
  border: none;
  border-radius: 8px;
  cursor: pointer;
  transform: translateZ(50px);
}
VanillaTilt.init(document.querySelector(".product-card"), {
  max: 15,
  speed: 400,
  glare: true,
  "max-glare": 0.2,
  scale: 1.03
});

Advanced Techniques

Dynamic Tilt Values

Update tilt based on external data.

const element = document.querySelector(".tilt-card");
VanillaTilt.init(element);

element.addEventListener("tiltChange", (e) => {
  const { tiltX, tiltY, angle } = e.detail;

  // Update background color based on tilt
  const hue = Math.abs(tiltX * 10);
  element.style.background = `hsl(${hue}, 70%, 50%)`;

  // Update text based on angle
  element.querySelector(".angle-display").textContent =
    `Angle: ${angle.toFixed(1)}°`;
});

Conditional Tilt

Enable tilt based on conditions.

const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);

if (!isMobile) {
  VanillaTilt.init(document.querySelectorAll(".tilt-card"), {
    max: 25,
    speed: 400
  });
} else {
  // Enable gyroscope on mobile
  VanillaTilt.init(document.querySelectorAll(".tilt-card"), {
    max: 15,
    speed: 600,
    gyroscope: true
  });
}

Sync Multiple Elements

Tilt multiple elements together.

const cards = document.querySelectorAll(".sync-card");

// Initialize all cards
VanillaTilt.init(cards, {
  max: 20,
  speed: 400
});

// Sync tilt values
cards[0].addEventListener("tiltChange", (e) => {
  const { tiltX, tiltY } = e.detail;

  // Apply same tilt to other cards
  cards.forEach((card, index) => {
    if (index > 0) {
      card.style.transform =
        `perspective(1000px) rotateY(${tiltX}deg) rotateX(${-tiltY}deg)`;
    }
  });
});

Tilt with Scroll

Combine tilt with scroll position.

const element = document.querySelector(".tilt-card");
VanillaTilt.init(element);

window.addEventListener("scroll", () => {
  const scrollPercent = window.scrollY / (document.body.scrollHeight - window.innerHeight);
  const rotation = scrollPercent * 360;

  element.style.transform += ` rotate(${rotation}deg)`;
});

Custom Glare Colors

Change glare color dynamically.

VanillaTilt.init(element, {
  glare: true,
  "max-glare": 0.5
});

element.addEventListener("tiltChange", (e) => {
  const glareEl = element.querySelector(".js-tilt-glare-inner");
  const { percentageX } = e.detail;

  // Color shifts from blue to purple
  const hue = 200 + (percentageX * 0.8);
  glareEl.style.background =
    `linear-gradient(0deg, transparent, hsl(${hue}, 70%, 50%))`;
});

Framework Integration

React

import React, { useEffect, useRef } from 'react';
import VanillaTilt from 'vanilla-tilt';

function TiltCard({ children, options }) {
  const tiltRef = useRef(null);

  useEffect(() => {
    VanillaTilt.init(tiltRef.current, options);

    return () => {
      tiltRef.current.vanillaTilt.destroy();
    };
  }, [options]);

  return (
    <div ref={tiltRef} className="tilt-card">
      {children}
    </div>
  );
}

// Usage
<TiltCard options={{ max: 25, speed: 400, glare: true }}>
  <h2>Card Content</h2>
</TiltCard>

Vue 3

<template>
  <div ref="tiltRef" class="tilt-card">
    <slot></slot>
  </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import VanillaTilt from 'vanilla-tilt';

const props = defineProps({
  options: {
    type: Object,
    default: () => ({ max: 25, speed: 400 })
  }
});

const tiltRef = ref(null);

onMounted(() => {
  VanillaTilt.init(tiltRef.value, props.options);
});

onUnmounted(() => {
  tiltRef.value.vanillaTilt.destroy();
});
</script>

Angular

import { Component, ElementRef, Input, OnInit, OnDestroy } from '@angular/core';
import VanillaTilt from 'vanilla-tilt';

@Component({
  selector: 'app-tilt-card',
  template: `
    <div class="tilt-card">
      <ng-content></ng-content>
    </div>
  `
})
export class TiltCardComponent implements OnInit, OnDestroy {
  @Input() options: any = { max: 25, speed: 400 };

  constructor(private el: ElementRef) {}

  ngOnInit() {
    VanillaTilt.init(this.el.nativeElement.querySelector('.tilt-card'), this.options);
  }

  ngOnDestroy() {
    this.el.nativeElement.querySelector('.tilt-card').vanillaTilt.destroy();
  }
}

Svelte

<script>
  import { onMount, onDestroy } from 'svelte';
  import VanillaTilt from 'vanilla-tilt';

  export let options = { max: 25, speed: 400 };

  let tiltRef;

  onMount(() => {
    VanillaTilt.init(tiltRef, options);
  });

  onDestroy(() => {
    if (tiltRef && tiltRef.vanillaTilt) {
      tiltRef.vanillaTilt.destroy();
    }
  });
</script>

<div bind:this={tiltRef} class="tilt-card">
  <slot></slot>
</div>

Performance

Optimization Tips

1. Disable on mobile for better performance

const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);

if (!isMobile) {
  VanillaTilt.init(document.querySelectorAll(".tilt-card"));
}

2. Use transform: translateZ for GPU acceleration

.tilt-card {
  transform: translateZ(0);  /* Force GPU layer */
}

3. Limit tilt to visible elements

let observer = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      VanillaTilt.init(entry.target);
    } else {
      if (entry.target.vanillaTilt) {
        entry.target.vanillaTilt.destroy();
      }
    }
  });
});

document.querySelectorAll('.tilt-card').forEach(el => {
  observer.observe(el);
});

4. Reduce max angle for subtle effects

// Less intensive
VanillaTilt.init(element, { max: 10 });

// More intensive
VanillaTilt.init(element, { max: 50 });

5. Disable glare on low-end devices

const hasGoodGPU = navigator.hardwareConcurrency > 4;

VanillaTilt.init(element, {
  max: 25,
  glare: hasGoodGPU,
  "max-glare": hasGoodGPU ? 0.5 : 0
});

Troubleshooting

Tilt Not Working

Check:

  1. Element exists when initializing
  2. Element has dimensions (width/height > 0)
  3. No CSS conflicts with transform
// Wait for DOM
document.addEventListener('DOMContentLoaded', () => {
  VanillaTilt.init(document.querySelector(".tilt-card"));
});

Glare Not Showing

Solution: Ensure element has overflow: hidden or border-radius.

.tilt-card {
  overflow: hidden;  /* Required for glare */
  border-radius: 10px;
}

Memory Leaks in SPAs

Solution: Always destroy on unmount.

// Store reference
const element = document.querySelector(".tilt-card");
VanillaTilt.init(element);

// Before removing element
element.vanillaTilt.destroy();

Resources


License

MIT License - Free for commercial and personal use.

Source: SKILL.md on GitHub

No alerts16d5 checks · Risk SAFE
  • Gen Agent Trust Hub16d

    The skill provides a suite of templates and scripts for creating lightweight 3D web effects. No malicious behaviors were detected.

  • Socket16d

    No alerts

  • Snyk16d

    Risk: LOW · No issues

  • Runlayer7mo

    11 files scanned · No issues

  • ZeroLeaks5mo

    1 finding · Score: 86/100

Signed by skilld at 1da73fe. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 2 months ago.

Dormantupdated 11 months ago

README badge

README badge for freshtechbro/claudedesignskills/lightweight-3d-effects