> ## Documentation Index
> Fetch the complete documentation index at: https://docs.heliosrender.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Vue integration

> Use Helios with Vue for reactive video animations

Helios integrates seamlessly with Vue's reactive system through composables that leverage Vue's ref and computed APIs.

## Quick start

**1. Install dependencies**

```bash theme={null}
npm install @helios-project/core vue
```

**2. Create the useVideoFrame composable**

```ts composables/useVideoFrame.ts theme={null}
import { ref, onUnmounted, type Ref } from 'vue';
import type { Helios } from '@helios-project/core';

export function useVideoFrame(helios: Helios): Ref<number> {
    const frame = ref(helios.getState().currentFrame);

    const update = (state: { currentFrame: number }) => {
        frame.value = state.currentFrame;
    };

    const unsubscribe = helios.subscribe(update);

    onUnmounted(() => {
        unsubscribe();
    });

    return frame;
}
```

**3. Create your first animation**

```vue App.vue theme={null}
<script setup lang="ts">
import { Helios } from '@helios-project/core';
import { useVideoFrame } from './composables/useVideoFrame';

// Initialize Helios singleton
const duration = 5;
const fps = 30;
const helios = new Helios({ duration, fps });
helios.bindToDocumentTimeline();

// Expose to window for debugging/player control
if (typeof window !== 'undefined') {
    (window as any).helios = helios;
}

const frame = useVideoFrame(helios);
</script>

<template>
  <div class="container">
    <div
        class="box"
        :style="{
            opacity: Math.min(1, frame / 30),
            transform: `scale(${Math.min(1.5, 0.5 + frame / 150)}) rotate(${frame * 2}deg)`
        }"
    >
        Vue DOM
    </div>
    <div class="info">Frame: {{ frame.toFixed(2) }}</div>
  </div>
</template>

<style scoped>
.container {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    height: 100%;
    color: white;
    font-family: sans-serif;
}
.box {
    width: 200px;
    height: 200px;
    background-color: #42b883;
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 2rem;
    font-weight: bold;
    border-radius: 20px;
    box-shadow: 0 0 20px rgba(66, 184, 131, 0.5);
    border: 4px solid #35495e;
}
.info {
    margin-top: 2rem;
    font-size: 1.5rem;
}
</style>
```

## Animation approaches

> [tab] DOM animations
>
> Animate Vue components using reactive bindings:
>
> ```vue theme={null}
> <script setup>
> import { computed } from 'vue';
> import { useVideoFrame } from './composables/useVideoFrame';
>
> const frame = useVideoFrame(helios);
> const progress = computed(() => frame.value / (duration * fps));
>
> const styles = computed(() => ({
>     opacity: Math.min(1, frame.value / 30),
>     transform: `translateX(${progress.value * 500}px) rotate(${frame.value * 2}deg)`
> }));
> </script>
>
> <template>
>   <div :style="styles">
>     Animated content
>   </div>
> </template>
> ```

> [tab] Canvas animations
>
> Use refs and watchers to draw frame-by-frame animations:
>
> ```vue theme={null}
> <script setup>
> import { ref, watch, onMounted } from 'vue';
> import { Helios } from '@helios-project/core';
> import { useVideoFrame } from './composables/useVideoFrame';
>
> const duration = 5;
> const fps = 30;
> const helios = new Helios({ duration, fps });
> helios.bindToDocumentTimeline();
>
> if (typeof window !== 'undefined') {
>     window.helios = helios;
> }
>
> const canvasRef = ref(null);
> const frame = useVideoFrame(helios);
>
> const draw = () => {
>     const canvas = canvasRef.value;
>     if (!canvas) return;
>
>     const ctx = canvas.getContext('2d');
>     const { width, height } = canvas;
>
>     // Clear
>     ctx.fillStyle = '#111';
>     ctx.fillRect(0, 0, width, height);
>
>     const time = frame.value / fps * 1000;
>     const progress = (time % (duration * 1000)) / (duration * 1000);
>
>     // Draw Vue-y visuals
>     const cx = width / 2;
>     const cy = height / 2;
>     const size = 200;
>
>     ctx.save();
>     ctx.translate(cx, cy);
>
>     // Pulse effect
>     const scale = 1 + Math.sin(progress * Math.PI * 2) * 0.1;
>     ctx.scale(scale, scale);
>
>     // Rotate
>     ctx.rotate(progress * Math.PI * 2);
>
>     // Vue Triangle (Outer)
>     ctx.beginPath();
>     ctx.moveTo(0, size / 2);
>     ctx.lineTo(size / 2, -size / 2);
>     ctx.lineTo(-size / 2, -size / 2);
>     ctx.closePath();
>     ctx.fillStyle = '#42b883'; // Vue Green
>     ctx.fill();
>
>     // Vue Triangle (Inner)
>     ctx.beginPath();
>     ctx.moveTo(0, size / 4);
>     ctx.lineTo(size / 4, -size / 2);
>     ctx.lineTo(-size / 4, -size / 2);
>     ctx.closePath();
>     ctx.fillStyle = '#35495e'; // Vue Dark Blue
>     ctx.fill();
>
>     ctx.restore();
>
>     // Text
>     ctx.fillStyle = 'white';
>     ctx.font = '24px sans-serif';
>     ctx.textAlign = 'center';
>     ctx.fillText(`Frame: ${frame.value}`, cx, cy + size / 1.5 + 50);
> };
>
> // React to frame changes
> watch(frame, draw);
>
> // Handle resize
> onMounted(() => {
>     const canvas = canvasRef.value;
>     if (!canvas) return;
>
>     const resize = () => {
>         canvas.width = window.innerWidth;
>         canvas.height = window.innerHeight;
>         draw();
>     };
>
>     window.addEventListener('resize', resize);
>     resize();
> });
> </script>
>
> <template>
>   <canvas ref="canvasRef" style="width: 100%; height: 100%; display: block;"></canvas>
> </template>
> ```

## Animation helpers

Helios provides utility functions for common animation patterns:

```vue theme={null}
<script setup>
import { computed } from 'vue';
import { interpolate, spring } from '@helios-project/core';
import { useVideoFrame } from './composables/useVideoFrame';

const frame = useVideoFrame(helios);

// Interpolate x position: 0 -> 200 over frames 0-60
const x = computed(() => 
    interpolate(frame.value, [0, 60], [0, 200], { extrapolateRight: 'clamp' })
);

// Spring scale: 0 -> 1 starting at frame 0
const scale = computed(() => 
    spring({ 
        frame: frame.value, 
        fps: 30, 
        from: 0, 
        to: 1, 
        config: { stiffness: 100 } 
    })
);
</script>

<template>
  <div :style="{
    transform: `translateX(${x}px) scale(${scale})`,
    width: '100px',
    height: '100px',
    background: 'hotpink'
  }">
    Animated
  </div>
</template>
```

## Sequencing components

### Sequence component

Create time-based sequences that show/hide content:

```vue components/Sequence.vue theme={null}
<script setup>
import { computed, inject, provide } from 'vue';
import { sequence } from '@helios-project/core';

const props = defineProps({
    from: { type: Number, default: 0 },
    durationInFrames: { type: Number, required: true }
});

const parentFrame = inject('videoFrame', 0);

const sequenceState = computed(() => 
    sequence({ 
        frame: parentFrame.value, 
        from: props.from, 
        durationInFrames: props.durationInFrames 
    })
);

const isActive = computed(() => sequenceState.value.isActive);
const relativeFrame = computed(() => sequenceState.value.relativeFrame);

provide('videoFrame', relativeFrame);
</script>

<template>
  <div v-if="isActive">
    <slot />
  </div>
</template>
```

### Series component

Automatically sequence child components one after another:

```vue components/Series.vue theme={null}
<script setup>
import { useSlots, h, computed } from 'vue';

const slots = useSlots();

const children = computed(() => {
    if (!slots.default) return [];
    
    let currentFrom = 0;
    return slots.default().map((vnode) => {
        const duration = vnode.props?.durationInFrames || 0;
        const newVnode = h(vnode, { from: currentFrom });
        currentFrom += duration;
        return newVnode;
    });
});
</script>

<template>
  <component :is="child" v-for="(child, i) in children" :key="i" />
</template>
```

### Usage example

```vue theme={null}
<script setup>
import { ref, provide, onUnmounted } from 'vue';
import { Helios } from '@helios-project/core';
import Sequence from './components/Sequence.vue';
import Series from './components/Series.vue';

const helios = new Helios({ duration: 5, fps: 30 });
helios.bindToDocumentTimeline();

if (typeof window !== 'undefined') {
    window.helios = helios;
}

const frame = ref(0);

const unsubscribe = helios.subscribe((state) => {
    frame.value = state.currentFrame;
});

onUnmounted(() => {
    unsubscribe();
});

provide('videoFrame', frame);
</script>

<template>
  <div class="container">
    <h1>Vue Animation Helpers</h1>
    <div>Root Frame: {{ frame.toFixed(2) }}</div>

    <Series>
      <!-- Sequence 1: 0-30 frames -->
      <Sequence :durationInFrames="30">
        <div class="box red">Seq 1</div>
      </Sequence>

      <!-- Sequence 2: 30-60 frames -->
      <Sequence :durationInFrames="30">
        <div class="box blue">Seq 2</div>
      </Sequence>
    </Series>
  </div>
</template>
```

## Best practices

> [accordion] Initialize Helios in setup
>
> Create the Helios instance in the `<script setup>` block to ensure it's only created once:
>
> ```vue theme={null}
> <script setup>
> import { Helios } from '@helios-project/core';
>
> // Good: Single instance per component mount
> const helios = new Helios({ duration: 5, fps: 30 });
> helios.bindToDocumentTimeline();
> </script>
> ```

> [accordion] Use computed for derived values
>
> Leverage Vue's computed properties for animation calculations:
>
> ```vue theme={null}
> <script setup>
> import { computed } from 'vue';
>
> const frame = useVideoFrame(helios);
> const progress = computed(() => frame.value / (duration * fps));
> const rotation = computed(() => progress.value * 360);
> </script>
> ```

> [accordion] Clean up subscriptions
>
> Always unsubscribe in `onUnmounted` to prevent memory leaks:
>
> ```vue theme={null}
> <script setup>
> import { onUnmounted } from 'vue';
>
> const unsubscribe = helios.subscribe(update);
>
> onUnmounted(() => {
>     unsubscribe();
> });
> </script>
> ```

> [accordion] Use provide/inject for frame context
>
> Share frame state across component trees using Vue's dependency injection:
>
> ```vue theme={null}
> <!-- Parent -->
> <script setup>
> import { provide } from 'vue';
> provide('videoFrame', frame);
> </script>
>
> <!-- Child -->
> <script setup>
> import { inject } from 'vue';
> const frame = inject('videoFrame');
> </script>
> ```

## TypeScript support

Helios provides full TypeScript support for Vue 3:

```ts theme={null}
import type { Helios } from '@helios-project/core';
import type { Ref } from 'vue';

export function useVideoFrame(helios: Helios): Ref<number> {
    const frame = ref<number>(helios.getState().currentFrame);
    // ...
    return frame;
}
```

## Next steps

> [card] [Animation helpers](https://docs.heliosrender.com/features/animation-helpers.md)
>
> Learn about interpolation, spring physics, and easing functions

> [card] [Canvas rendering](https://docs.heliosrender.com/guides/canvas)
>
> Create high-performance canvas animations

> [card] [Sequences](https://docs.heliosrender.com/guides/sequences)
>
> Build complex multi-scene animations

> [card] [Export videos](https://docs.heliosrender.com/guides/export)
>
> Render your animations to video files


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.