# Rlottie WASM Vue Player

A Vue 3 component for the rlottie-wasm animation player. This package allows you to play Lottie animations in your Vue applications using the rlottie WebAssembly module.

## Improved Usage Experience

With this package, you can enjoy a seamless and efficient experience when working with Lottie animations in your Vue applications. The key benefits include:

- **Zero configuration required** - All WASM and JS assets are automatically included
- **Simple import** - Just `import { RlottiePlayer } from 'rlottie-vue-player'` and you're ready to go
- Play, pause, stop, and seek Lottie animations
- Control playback speed
- Customize layer colors, opacity, position, scale, and rotation
- Responsive design
- Event handling for animation lifecycle

## Installation

```bash
npm install rlottie-wasm-vue-player
```

## Setup

### 1. Install the package

Install the package using npm or yarn:

```bash
npm install rlottie-wasm-vue-player
# or
yarn add rlottie-wasm-vue-player
```

That's it! No need to manually copy any files. All required WASM and JS assets are automatically included in the package.

### 2. Import and use the component

No additional script loading is required. All necessary WASM and JS files are automatically loaded when you import the component.

## Usage

### Register as a Vue plugin (global component)

```js
// main.js
import { createApp } from 'vue';
import App from './App.vue';
import RlottieVuePlayer from 'rlottie-wasm-vue-player';

const app = createApp(App);

// Register the plugin with optional configuration
app.use(RlottieVuePlayer, {
  // Optional: custom path to WASM assets if you need to override the default
  // wasmPath: '/custom-path'
});

app.mount('#app');
```

### Use as a local component

```js
// YourComponent.vue
import { RlottiePlayer } from 'rlottie-wasm-vue-player';

export default {
  components: {
    RlottiePlayer
  }
};
```

### Basic Example

```vue
<template>
  <RlottiePlayer :src="BirthdayCake" :autoplay="true" :loop="true" :speed="1" />
</template>

<script setup>
import { RlottiePlayer } from 'rlottie-wasm-vue-player';
import BirthdayCake from 'somewhere/BirthdayCakeColorful.json';
</script>

```

### Comprehensive Example of Usage Pattern

```vue
<template>
  <div class="lottie-demo">
    <h1>Lottie Animation Player Demo</h1>

    <!-- Animation Player -->
    <section class="player-section">
      <div class="player-display">
        <RlottiePlayer
          ref="player"
          :src="animationData"
          :width="displaySize.width"
          :height="displaySize.height"
          :autoplay="playbackSettings.autoplay"
          :loop="playbackSettings.loop"
          :speed="playbackSettings.speed"
          :layers="layerCustomizations"
          @load="onAnimationLoaded"
          @play="isPlaying = true"
          @pause="isPlaying = false"
          @stop="isPlaying = false"
          @error="onAnimationError"
          @complete="onAnimationComplete"
        />
      </div>

      <!-- Basic Playback Controls -->
      <div class="control-panel">
        <h2>Playback Controls</h2>

        <div class="control-buttons">
          <button class="control-btn" @click="playAnimation" :disabled="isPlaying">Play</button>
          <button class="control-btn" @click="pauseAnimation" :disabled="!isPlaying">Pause</button>
          <button class="control-btn" @click="stopAnimation">Stop</button>
        </div>

        <div class="progress-control">
          <label>
            <span>Progress: {{ progressValue }}%</span>
            <input type="range" min="0" max="100" v-model.number="progressValue" @input="seekToPosition" />
          </label>
        </div>

        <div class="speed-control">
          <label>
            <span>Speed: {{ playbackSettings.speed }}x</span>
            <input type="range" min="0.1" max="5" step="0.1" v-model.number="playbackSettings.speed" />
          </label>
        </div>

        <div class="playback-options">
          <label>
            <input type="checkbox" v-model="playbackSettings.autoplay" />
            Autoplay
          </label>
          <label>
            <input type="checkbox" v-model="playbackSettings.loop" />
            Loop
          </label>
        </div>
      </div>
    </section>

    <!-- Animation Source Controls -->
    <section class="source-section">
      <h2>Animation Source</h2>
      <div class="source-controls">
        <button class="source-btn" @click="loadSampleAnimation">Load Sample Animation</button>
      </div>

      <div class="paste-json">
        <h4>Or paste animation JSON:</h4>
        <textarea
          v-model="jsonInput"
          placeholder="Paste Lottie JSON here..."
          rows="5"
        ></textarea>
        <button @click="loadFromJsonInput">Load JSON</button>
      </div>
    </section>

    <!-- Layer Customization -->
    <section class="customization-section" v-if="targetLayerPath">
      <h2>Layer Customization</h2>

      <div class="layer-path-input">
        <label>
          Layer Path:
          <input
            type="text"
            v-model="targetLayerPath"
            placeholder="e.g. Shape Layer 1"
          />
        </label>
      </div>

      <div class="customization-panels">
        <!-- Color Controls -->
        <div class="customization-panel">
          <h3>Color</h3>
          <div class="color-controls">
            <label>
              R: <input type="range" min="0" max="255" v-model.number="colorSettings.r" @input="updateLayerColor" />
              <span>{{ colorSettings.r }}</span>
            </label>
            <label>
              G: <input type="range" min="0" max="255" v-model.number="colorSettings.g" @input="updateLayerColor" />
              <span>{{ colorSettings.g }}</span>
            </label>
            <label>
              B: <input type="range" min="0" max="255" v-model.number="colorSettings.b" @input="updateLayerColor" />
              <span>{{ colorSettings.b }}</span>
            </label>
          </div>
        </div>

        <!-- Opacity Controls -->
        <div class="customization-panel">
          <h3>Opacity</h3>
          <label>
            <input
              type="range"
              v-model.number="opacityValue"
              min="0"
              max="1"
              step="0.1"
              @input="updateLayerOpacity"
            />
            <span>{{ opacityValue }}</span>
          </label>
        </div>

        <!-- Stroke Width Controls -->
        <div class="customization-panel">
          <h3>Stroke Width</h3>
          <label>
            <input
              type="range"
              v-model.number="strokeWidthValue"
              min="1"
              max="20"
              @input="updateStrokeWidth"
            />
            <span>{{ strokeWidthValue }}</span>
          </label>
        </div>

        <!-- Position Controls -->
        <div class="customization-panel">
          <h3>Position</h3>
          <div class="position-controls">
            <label>
              X: <input type="number" v-model.number="positionValues.x" @change="updatePosition" />
            </label>
            <label>
              Y: <input type="number" v-model.number="positionValues.y" @change="updatePosition" />
            </label>
          </div>
        </div>

        <!-- Scale Controls -->
        <div class="customization-panel">
          <h3>Scale</h3>
          <div class="scale-controls">
            <label>
              Width: <input type="number" v-model.number="scaleValues.width" step="0.1" @change="updateScale" />
            </label>
            <label>
              Height: <input type="number" v-model.number="scaleValues.height" step="0.1" @change="updateScale" />
            </label>
          </div>
        </div>

        <!-- Rotation Controls -->
        <div class="customization-panel">
          <h3>Rotation</h3>
          <label>
            <input
              type="range"
              v-model.number="rotationValue"
              min="0"
              max="360"
              @input="updateRotation"
            />
            <span>{{ rotationValue }}°</span>
          </label>
        </div>
      </div>
    </section>

    <!-- Animation Properties -->
    <section class="properties-section" v-if="animationProperties">
      <h2>Animation Properties</h2>
      <pre>{{ JSON.stringify(animationProperties, null, 2) }}</pre>
    </section>
  </div>
</template>

<script setup>
import { ref, reactive, onMounted } from 'vue';
import { RlottiePlayer } from 'rlottie-wasm-vue-player';
import BirthdayCakeLottieJSON from 'src/lotties/BirthdayCakeColorful.json';

// ------------------------
// Player Configuration
// ------------------------

// Reference to the player component instance
const player = ref(null);

// Animation data source
const animationData = ref('');
const jsonInput = ref(BirthdayCakeLottieJSON);

// Display dimensions
const displaySize = reactive({
  width: 300,
  height: 300
});

// Playback settings
const playbackSettings = reactive({
  autoplay: true,
  loop: true,
  speed: 1
});

// Playback state
const isPlaying = ref(false);
const progressValue = ref(0);

// Animation metadata
const animationProperties = ref(null);

// ------------------------
// Layer Customization
// ------------------------

// Target layer to customize
const targetLayerPath = ref(''); // e.g., 'Shape Layer 1'

// Color settings
const colorSettings = reactive({
  r: 255,
  g: 0,
  b: 0
});

// Other customization values
const opacityValue = ref(1);
const strokeWidthValue = ref(5);
const positionValues = reactive({ x: 0, y: 0 });
const scaleValues = reactive({ width: 1, height: 1 });
const rotationValue = ref(0);

// Collection of all layer customizations
const layerCustomizations = ref({});


// ------------------------
// Playback Control Methods
// ------------------------

// Start animation playback
const playAnimation = () => {
  if (player.value) {
    player.value.play();
  }
};

// Pause animation playback
const pauseAnimation = () => {
  if (player.value) {
    player.value.pause();
  }
};

// Stop animation and reset to first frame
const stopAnimation = () => {
  if (player.value) {
    player.value.stop();
  }
};

// Seek to a specific position in the animation
const seekToPosition = () => {
  if (player.value) {
    player.value.seek(progressValue.value);
  }
};

// ------------------------
// Animation Loading
// ------------------------

// Load a sample animation from a URL
const loadSampleAnimation = () => {
  animationData.value = '/sample.json';
  targetLayerPath.value = 'Shape Layer 1'; // Default layer path for the sample
};

// Load animation from JSON input (string or object)
const loadFromJsonInput = () => {
  if (typeof jsonInput.value === 'string') {
    try {
      // Parse string input into object
      animationData.value = JSON.parse(jsonInput.value);
    } catch (e) {
      alert('Invalid JSON string: ' + e.message);
      animationData.value = null;
    }
  } else if (typeof jsonInput.value === 'object' && jsonInput.value !== null) {
    // Use direct object reference
    animationData.value = jsonInput.value;
  } else {
    alert('JSON input is empty or invalid.');
    animationData.value = null;
  }
};

// ------------------------
// Event Handlers
// ------------------------

// Called when animation is successfully loaded
const onAnimationLoaded = (data) => {
  console.log('Animation loaded successfully:', data);
  updateAnimationProperties();
};

// Called when animation fails to load
const onAnimationError = (error) => {
  console.error('Animation error:', error);
  alert('Failed to load animation: ' + (error.message || error));
};

// Called when animation playback completes (only if loop is false)
const onAnimationComplete = () => {
  console.log('Animation playback complete');
  isPlaying.value = false;
};

// ------------------------
// Animation Properties
// ------------------------

// Fetch and update animation properties from the player
const updateAnimationProperties = () => {
  if (player.value) {
    animationProperties.value = player.value.getProperties();
  }
};

// ------------------------
// Layer Customization Methods
// ------------------------

// Update layer color
const updateLayerColor = () => {
  updateLayer({
    color: { r: colorSettings.r, g: colorSettings.g, b: colorSettings.b }
  });
};

// Update layer opacity
const updateLayerOpacity = () => {
  updateLayer({
    opacity: opacityValue.value
  });
};

// Update stroke width
const updateStrokeWidth = () => {
  updateLayer({
    strokeWidth: strokeWidthValue.value
  });
};

// Update layer position
const updatePosition = () => {
  updateLayer({
    position: { x: positionValues.x, y: positionValues.y }
  });
};

// Update layer scale
const updateScale = () => {
  updateLayer({
    scale: { width: scaleValues.width, height: scaleValues.height }
  });
};

// Update layer rotation
const updateRotation = () => {
  updateLayer({
    rotation: rotationValue.value
  });
};

// Helper method to update layer customizations
const updateLayer = (properties) => {
  if (!targetLayerPath.value) return;

  const path = targetLayerPath.value;
  const currentLayerProps = layerCustomizations.value[path] || {};

  layerCustomizations.value = {
    ...layerCustomizations.value,
    [path]: {
      ...currentLayerProps,
      ...properties
    }
  };
};

// ------------------------
// Lifecycle Hooks
// ------------------------

onMounted(() => {
  // Load the imported BirthdayCakeLottieJSON by default
  if (jsonInput.value) {
    animationData.value = jsonInput.value;
  } else {
    console.warn('Default Lottie JSON is not available.');
  }
});
</script>
```

## Component Props

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `src` | String, Object | - | Animation source - stringified JSON or URL to JSON file |
| `width` | Number, String | 300 | Width of the player |
| `height` | Number, String | 300 | Height of the player |
| `autoplay` | Boolean | false | Whether to autoplay the animation |
| `loop` | Boolean | true | Whether to loop the animation |
| `speed` | Number | 1 | Playback speed (1 is normal speed) |
| `background` | String | 'transparent' | Background color |
| `layers` | Object | {} | Layer customizations (see below) |
| `canvasId` | String | null | Optional custom canvas ID |
| `assetPath` | String | '' | Optional: Custom path to asset files (not required with default usage) |

## Layer Customization

The `layers` prop accepts an object with the following format:

```js
{
  "keypath": {
    color: { r: 255, g: 0, b: 0 },
    opacity: 0.5,
    strokeWidth: 2,
    position: { x: 10, y: 20 },
    scale: { width: 1.2, height: 0.8 },
    rotation: 45
  }
}
```

Where `keypath` is the path to the layer in the Lottie animation (e.g., "Shape Layer 1").

## Component Methods

| Method | Description |
|--------|-------------|
| `play()` | Starts or resumes animation playback |
| `pause()` | Pauses animation playback |
| `stop()` | Stops animation and resets to the first frame |
| `seek(percentage)` | Seeks to a specific frame based on percentage (0-100) |
| `getProperties()` | Returns properties of the current animation |

## Global API

The package also exposes a global API on `window.RlottieVuePlayer` with the following methods:

| Method | Description |
|--------|-------------|
| `loadAnimation(jsonData)` | Loads Lottie animation data (stringified JSON) |
| `play()` | Starts or resumes animation playback |
| `pause()` | Pauses animation playback |
| `stop()` | Stops animation and resets to the first frame |
| `seek(percentage)` | Seeks to a specific frame based on percentage (0-100) |
| `getAnimationProperties()` | Returns properties of the current animation |
| `setPlaySpeed(speed)` | Sets the playback speed |
| `resize(width, height)` | Informs the player of the canvas dimensions |
| `setLayerColor(keypath, r, g, b)` | Sets the fill and stroke color of a layer |
| `setLayerOpacity(keypath, opacity)` | Sets the fill and stroke opacity of a layer (0-1) |
| `setStrokeWidth(keypath, width)` | Sets the stroke width of a layer |
| `setPosition(keypath, x, y)` | Sets the position of a layer |
| `setScale(keypath, width, height)` | Sets the scale of a layer |
| `setRotation(keypath, degree)` | Sets the rotation of a layer |

## License

MIT
