> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/BintzGavin/helios/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

<Steps>
  <Step title="Install dependencies">
    ```bash theme={null}
    npm install @helios-project/core vue
    ```
  </Step>

  <Step title="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;
    }
    ```
  </Step>

  <Step title="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>
    ```
  </Step>
</Steps>

## Animation approaches

<Tabs>
  <Tab title="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>

  <Tab title="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>
    ```
  </Tab>
</Tabs>

## 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

<AccordionGroup>
  <Accordion title="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>

  <Accordion title="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>

  <Accordion title="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>

  <Accordion title="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>
    ```
  </Accordion>
</AccordionGroup>

## 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

<CardGroup cols={2}>
  <Card title="Animation helpers" icon="wand-magic-sparkles" href="/features/animation-helpers">
    Learn about interpolation, spring physics, and easing functions
  </Card>

  <Card title="Canvas rendering" icon="paintbrush" href="/guides/canvas">
    Create high-performance canvas animations
  </Card>

  <Card title="Sequences" icon="film" href="/guides/sequences">
    Build complex multi-scene animations
  </Card>

  <Card title="Export videos" icon="file-export" href="/guides/export">
    Render your animations to video files
  </Card>
</CardGroup>
