Skip to main content
Helios integrates seamlessly with Vue’s reactive system through composables that leverage Vue’s ref and computed APIs.

Quick start

1

Install dependencies

npm install @helios-project/core vue
2

Create the useVideoFrame composable

composables/useVideoFrame.ts
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

App.vue
<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

Animate Vue components using reactive bindings:
<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>

Animation helpers

Helios provides utility functions for common animation patterns:
<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:
components/Sequence.vue
<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:
components/Series.vue
<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

<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

Create the Helios instance in the <script setup> block to ensure it’s only created once:
<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>
Leverage Vue’s computed properties for animation calculations:
<script setup>
import { computed } from 'vue';

const frame = useVideoFrame(helios);
const progress = computed(() => frame.value / (duration * fps));
const rotation = computed(() => progress.value * 360);
</script>
Always unsubscribe in onUnmounted to prevent memory leaks:
<script setup>
import { onUnmounted } from 'vue';

const unsubscribe = helios.subscribe(update);

onUnmounted(() => {
    unsubscribe();
});
</script>
Share frame state across component trees using Vue’s dependency injection:
<!-- 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:
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

Animation helpers

Learn about interpolation, spring physics, and easing functions

Canvas rendering

Create high-performance canvas animations

Sequences

Build complex multi-scene animations

Export videos

Render your animations to video files

Build docs developers (and LLMs) love