![Animation](./demo.png)

# DepthText.js

![](https://img.shields.io/badge/Typescript-Ready-blue?logo=typescript&color=3178c6) [![npm version](https://img.shields.io/npm/v/depthtext.svg?logo=npm&logoColor=cb3837&color=cb3837&style=flat)](https://www.npmjs.com/package/depthtext) [![npm downloads](https://img.shields.io/npm/dm/depthtext.svg?style=flat-square)](https://www.npmjs.com/package/depthtext) [![bundle size](https://img.shields.io/bundlephobia/minzip/depthtext?style=flat-square)](https://bundlephobia.com/package/depthtext) [![jsdelivr hits](https://img.shields.io/jsdelivr/npm/hm/depthtext?logo=jsdelivr&style=flat-square)](https://www.jsdelivr.com/package/npm/depthtext) [![GitHub license](https://img.shields.io/github/license/MobiWise-dev/DepthText?style=flat-square)](https://github.com/MobiWise-dev/DepthText/blob/main/LICENSE) [![GitHub stars](https://img.shields.io/github/stars/MobiWise-dev/DepthText?style=flat-square)](https://github.com/MobiWise-dev/DepthText/stargazers)

DepthText is a lightweight, dependency-free JavaScript library that creates smooth multi-layer 3D text with depth, parallax, and interactive rotation.  
Designed for high performance, accessibility, and modern UX motion guidelines.

It is the spiritual successor of ztext.js, but rewritten from scratch with a cleaner API, better performance, and a modern ES module architecture.

---

## ✨ Features

- 🌀 **True 3D layered depth** using CSS `transform-style: preserve-3d`
- 🖱️ **Interactive rotation** (auto, pointer, scroll…)
- 🎚️ **Configurable number of layers**
- 🎨 Optional **fade effect** across layers
- ⚡ **GPU-optimized** and jank-free
- 🦾 **Accessibility-friendly** (`aria-hidden`, reduced-motion support)
- 🔥 **No dependencies**, only 4–6 KB minified
- 📦 Works with bundlers, ES modules, and browsers
- 🖼️ **Supports images, SVGs, and emojis** within text content
- 🎭 **Custom CSS classes** for advanced styling control
- 🎨 **Per-layer styling** with `layerClassMap` and auto-generated classes
- 🔄 **Dynamic updates** with `.update()` method

---

## 🛠 Installation

### NPM

```bash
npm install depthtext
```

### CDN

#### jsDelivr (Recommended)

```html
<!-- ESM -->
<script type="module">
  import { DepthTextify } from "https://cdn.jsdelivr.net/npm/depthtext@latest/dist/depthtext.mjs";
  DepthTextify();
</script>

<!-- Global/IIFE (classic script tag) -->
<script src="https://cdn.jsdelivr.net/npm/depthtext@latest/dist/depthtext.global.js"></script>
<script>
  DepthText.DepthTextify();
</script>
```

#### unpkg

```html
<!-- ESM -->
<script type="module">
  import { DepthTextify } from "https://unpkg.com/depthtext@latest/dist/depthtext.mjs";
  DepthTextify();
</script>

<!-- Global/IIFE -->
<script src="https://unpkg.com/depthtext@latest/dist/depthtext.global.js"></script>
<script>
  DepthText.DepthTextify();
</script>
```

### Import (ES module)

```js
import { DepthTextify } from "depthtext";
```

---

## 🚀 Quick Start

### HTML

```html
<h1 class="depthtext" data-depth="2rem" data-depth-event="pointer">DepthText Rocks</h1>
```

### JavaScript

```js
import { DepthTextify } from "depthtext";

DepthTextify(); // Enhances all elements with [data-depth]
```

---

## 🧩 Options

DepthText can be configured using either:

- **HTML data attributes**
- or **JavaScript options**

---

### HTML Attributes

| Attribute                    | Type    | Default   | Description                                                    |
| ---------------------------- | ------- | --------- | -------------------------------------------------------------- |
| `data-depth`                 | string  | `1rem`    | Z-axis distance between layers (e.g., "2rem", "20px")          |
| `data-depth-layers`          | number  | `10`      | Number of 3D layers (1-40, recommended ≤25 for performance)    |
| `data-depth-direction`       | string  | `both`    | Layer direction: `both`, `forwards`, `backwards`               |
| `data-depth-event`           | string  | `none`    | Interaction: `none`, `pointer`, `scroll`, `scrollX`, `scrollY` |
| `data-depth-event-rotation`  | string  | `30deg`   | Max rotation angle on interaction (e.g., "45deg", "0.5rad")    |
| `data-depth-event-direction` | string  | `default` | Rotation direction: `default`, `reverse`                       |
| `data-depth-fade`            | boolean | `false`   | Fade layers as depth increases                                 |
| `data-depth-perspective`     | string  | `500px`   | CSS perspective value                                          |
| `data-depth-engaged`         | boolean | `true`    | Enable/disable the effect                                      |
| `data-depth-add-class`       | string  | `""`      | Custom CSS class(es) to add to ALL layers                      |

**⚠️ Important:** Use `data-depth-engaged` to enable/disable, NOT `data-depth` alone.

---

### JavaScript API

```js
import { DepthTextInstance } from "depthtext";

const instance = new DepthTextInstance(element, {
  depth: "1rem", // Z-distance between layers
  layers: 10, // Number of layers
  direction: "both", // "both" | "forwards" | "backwards"
  event: "pointer", // "none" | "pointer" | "scroll" | "scrollX" | "scrollY"
  eventRotation: "30deg", // Max tilt angle
  eventDirection: "default", // "default" | "reverse"
  fade: false, // Enable opacity fade
  perspective: "500px", // CSS perspective
  engaged: true, // Enable/disable effect
  addClass: "my-class", // Custom class for ALL layers
  layerClassMap: ["front", "middle", "back"], // Custom class per layer (NEW in v1.2.0)
});

// Dynamically update options (NEW in v1.2.0)
instance.update({
  depth: "2rem",
  event: "scroll",
});

// Cleanup
instance.destroy();
```

---

## 🎨 Advanced Styling

### Auto-Generated Layer Classes (NEW in v1.2.0)

Each layer automatically receives a unique class:

```html
<!-- 3 layers example -->
<span class="depthtext-layer depthtext-layer-0">...</span>
<span class="depthtext-layer depthtext-layer-1">...</span>
<span class="depthtext-layer depthtext-layer-2">...</span>
```

Target specific layers with CSS:

```css
.depthtext-layer-0 {
  /* Front layer */
}
.depthtext-layer-1 {
  /* Middle layer */
}
.depthtext-layer-2 {
  /* Back layer */
}
```

### Per-Layer Custom Classes with `layerClassMap` (NEW in v1.2.0)

Map custom classes to individual layers by index:

```js
new DepthTextInstance(element, {
  layers: 5,
  layerClassMap: ["front", "mid-front", "center", "mid-back", "back"],
});
```

Result:

```html
<span class="depthtext-layer depthtext-layer-0 front">...</span>
<span class="depthtext-layer depthtext-layer-1 mid-front">...</span>
<span class="depthtext-layer depthtext-layer-2 center">...</span>
<span class="depthtext-layer depthtext-layer-3 mid-back">...</span>
<span class="depthtext-layer depthtext-layer-4 back">...</span>
```

**Rainbow gradient example:**

```js
new DepthTextInstance(element, {
  layers: 7,
  layerClassMap: ["red", "orange", "yellow", "green", "blue", "indigo", "violet"],
});
```

```css
.red {
  color: #ff0000;
}
.orange {
  color: #ff7f00;
}
.yellow {
  color: #ffff00;
}
.green {
  color: #00ff00;
}
.blue {
  color: #0000ff;
}
.indigo {
  color: #4b0082;
}
.violet {
  color: #9400d3;
}
```

**⚠️ Partial mapping:** If you provide fewer classes than layers, unmapped layers will only receive default classes:

```js
new DepthTextInstance(element, {
  layers: 10,
  layerClassMap: ["special-0", "special-1", "special-2"],
  // Layers 3-9 will only have: depthtext-layer depthtext-layer-N
});
```

### Apply Classes to ALL Layers with `addClass`

```html
<h1 data-depth="1rem" data-depth-add-class="gradient-text">Stylized Text</h1>
```

```css
.gradient-text {
  background: linear-gradient(45deg, #ff6b6b, #4ecdc4);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
}
```

Multiple classes:

```js
new DepthTextInstance(element, {
  addClass: "gradient-text shadow-effect animated",
});
```

### 🎯 Important: Styling Individual Layer Colors

Because layers are clones sharing the same text formatting context, color inheritance can be tricky.

**❌ This WON'T work reliably:**

```css
.depthtext-layer-0 {
  color: blue;
}
.depthtext-layer-1 {
  color: red;
}
```

**✅ Use the universal selector instead:**

```css
.depthtext-layer-0 * {
  color: blue !important;
}
.depthtext-layer-1 * {
  color: red !important;
}
```

**Or add a global reset:**

```css
.depthtext-layer > * {
  color: inherit !important;
}
```

This ensures each layer's content truly inherits its parent layer's color.

### Advanced Styling Examples

```css
/* Neon glow effect */
.neon-glow {
  color: #00ffff;
  text-shadow: 0 0 10px #00ffff, 0 0 20px #00ffff, 0 0 30px #00ffff;
}

/* Metallic gradient */
.metallic {
  background: linear-gradient(90deg, #c0c0c0, #e8e8e8, #c0c0c0);
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
}

/* Rainbow animation */
@keyframes rainbow {
  0%,
  100% {
    color: #ff0000;
  }
  16% {
    color: #ff7f00;
  }
  33% {
    color: #ffff00;
  }
  50% {
    color: #00ff00;
  }
  66% {
    color: #0000ff;
  }
  83% {
    color: #8b00ff;
  }
}

.rainbow-text {
  animation: rainbow 3s linear infinite;
}

/* Per-layer depth-based opacity */
.depthtext-layer-0 {
  opacity: 1;
}
.depthtext-layer-1 {
  opacity: 0.9;
}
.depthtext-layer-2 {
  opacity: 0.8;
}
.depthtext-layer-3 {
  opacity: 0.7;
}
```

---

## 🔄 Dynamic Updates (NEW in v1.2.0)

Change options after initialization without recreating the instance:

```js
const instance = new DepthTextInstance(element, {
  depth: "1rem",
  event: "pointer",
});

// Later, change behavior
instance.update({
  depth: "2rem",
  event: "scroll",
  layers: 15,
});
```

Perfect for:

- Responsive designs (change depth on mobile)
- Interactive controls (user preferences)
- State-based animations

---

## 🌐 Automatic Initialization

Auto-enhance all elements with `[data-depth]` attribute:

```html
<h1 data-depth="2rem" data-depth-event="pointer">Auto-enhanced</h1>
<h2 data-depth="1rem" data-depth-layers="5">Another one</h2>
```

```js
import { DepthTextify } from "depthtext";

// Initialize all [data-depth] elements
DepthTextify();

// Or with custom selector
DepthTextify(".custom-selector");

// Or with default options
DepthTextify({
  depth: "2rem",
  event: "pointer",
});
```

---

## 🧼 Accessibility

DepthText is designed to remain invisible to assistive technologies:

- Wrappers are automatically marked with `aria-hidden="true"`
- Motion reacts to `prefers-reduced-motion: reduce`
- Original text remains intact and readable in source
- Screen readers only see the original content

---

## 📦 Browser Usage (no bundler)

```html
<script src="dist/depthtext.global.js"></script>
<script>
  // DepthText is available globally
  DepthText.DepthTextify();
</script>
```

---

## ⚛️ Framework Integration

DepthText is framework-agnostic. You can use it with React, Vue, Angular, Svelte, etc., by instantiating `DepthTextInstance` on a mounted DOM element.

### React / Next.js

```jsx
import { useEffect, useRef } from "react";
import { DepthTextInstance } from "depthtext";

export default function DepthComponent() {
  const textRef = useRef(null);

  useEffect(() => {
    if (!textRef.current) return;

    const dt = new DepthTextInstance(textRef.current, {
      layers: 10,
      depth: "1rem",
      event: "pointer",
      layerClassMap: ["front", "mid", "back"], // Per-layer styling
    });

    // Cleanup on unmount
    return () => dt.destroy();
  }, []);

  return <h1 ref={textRef}>DepthText in React</h1>;
}
```

### Vue 3

```html
<script setup>
  import { onMounted, onUnmounted, ref } from "vue";
  import { DepthTextInstance } from "depthtext";

  const textRef = ref(null);
  let dt = null;

  onMounted(() => {
    if (textRef.value) {
      dt = new DepthTextInstance(textRef.value, {
        layers: 10,
        event: "pointer",
        addClass: "custom-style",
        layerClassMap: ["layer-a", "layer-b", "layer-c"],
      });
    }
  });

  onUnmounted(() => {
    if (dt) dt.destroy();
  });
</script>

<template>
  <h1 ref="textRef">DepthText in Vue</h1>
</template>
```

### Angular

```ts
import { Component, ElementRef, ViewChild, AfterViewInit, OnDestroy } from "@angular/core";
import { DepthTextInstance } from "depthtext";

@Component({
  selector: "app-depth-text",
  template: "<h1 #depthText>DepthText in Angular</h1>",
})
export class DepthTextComponent implements AfterViewInit, OnDestroy {
  @ViewChild("depthText") textRef!: ElementRef;
  private dt?: DepthTextInstance;

  ngAfterViewInit() {
    this.dt = new DepthTextInstance(this.textRef.nativeElement, {
      layers: 10,
      event: "pointer",
      layerClassMap: ["front", "middle", "back"],
    });
  }

  ngOnDestroy() {
    this.dt?.destroy();
  }
}
```

---

## 🎭 Supported Content

DepthText works with various content types:

### ✅ Fully Supported

- **Text content** (including unicode, special characters)
- **Emojis** (😊, 🚀, ❤️, etc.)
- **Inline SVG** (`<svg>...</svg>`)
- **Images** (`<img src="...">`)
- **Mixed content** (text + images + SVGs together)

### Example with mixed content:

```html
<h1 class="depthtext" data-depth="1rem" data-depth-event="pointer" data-depth-add-class="colorful">
  Hello World! 🚀
  <svg width="30" height="30"><circle cx="15" cy="15" r="10" fill="blue" /></svg>
  <img src="logo.png" width="40" alt="Logo" />
</h1>
```

### ⚠️ Notes

- Images should be loaded before initialization for best results
- Very large images may impact performance
- External SVGs (`<img src="icon.svg">`) work like regular images

---

## 🛠 Known Issues & Notes

- DepthText uses CSS transforms; parent elements must not flatten 3D contexts
- Avoid nested DepthText unless you understand `transform-style: preserve-3d`
- **Images**: For complex layouts, consider using `background-image` on wrapper elements
- **Performance**: Very high layer counts (>25) with large images may impact performance
- **Color styling**: Use `.depthtext-layer-N * { color: ... }` for reliable per-layer coloring

---

## 🧑‍💻 Contributing

Pull requests are welcome!  
If you add a major feature, please include documentation updates.

---

## 📄 License

MIT License – free to use in commercial and open-source projects.

---

## 💬 Author

Created by [**MobiWise**](https://mobiwise.dev).  
A modern reimagining of the classic [ztext.js](https://github.com/bennettfeely/ztext).
