animot-presenter
v0.2.9
Published
Embed animated presentations anywhere. Works with vanilla JS, React, Vue, Angular, Svelte, and any frontend framework. Morphing animations, code highlighting, charts, particles, and more.
Maintainers
Readme
animot-presenter
Embed animated presentations anywhere. A single <animot-presenter> tag that plays Animot animation JSON files with morphing transitions, code syntax highlighting, charts, particles, and more.
Works with any framework: vanilla HTML/JS, React, Vue, Angular, Svelte, or any frontend stack.
Install
npm (recommended for bundled projects)
npm install animot-presenterCDN (for vanilla HTML or quick prototyping)
<link rel="stylesheet" href="https://unpkg.com/animot-presenter/dist/cdn/animot-presenter.css">
<script src="https://unpkg.com/animot-presenter/dist/cdn/animot-presenter.min.js"></script>Or use the ES module version:
<link rel="stylesheet" href="https://unpkg.com/animot-presenter/dist/cdn/animot-presenter.css">
<script type="module" src="https://unpkg.com/animot-presenter/dist/cdn/animot-presenter.esm.js"></script>Quick Start
<animot-presenter src="/my-animation.json" autoplay loop controls></animot-presenter>That's it. The component loads the JSON, renders the animation canvas, and handles everything — morphing, transitions, code highlighting, keyboard navigation.
Usage by Framework
Vanilla HTML / JavaScript
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/animot-presenter/dist/cdn/animot-presenter.min.js"></script>
<style>
/* Size the presenter however you want */
.hero-animation {
width: 100%;
height: 500px;
}
</style>
</head>
<body>
<animot-presenter
class="hero-animation"
src="/animations/hero.json"
autoplay
loop
controls
></animot-presenter>
<script>
const presenter = document.querySelector('animot-presenter');
// Listen for events
presenter.addEventListener('slidechange', (e) => {
console.log(`Slide ${e.detail.index + 1} of ${e.detail.total}`);
});
presenter.addEventListener('complete', () => {
console.log('Animation finished');
});
// Load data programmatically
fetch('/animations/demo.json')
.then(res => res.json())
.then(data => {
presenter.data = data;
});
// Control programmatically
presenter.next();
presenter.prev();
</script>
</body>
</html>Svelte 5
npm install animot-presenter<script>
import { browser } from '$app/environment';
import { AnimotPresenter } from 'animot-presenter';
</script>
<!-- Wrap with {#if browser} in SvelteKit to skip SSR -->
<div style="width: 100%; height: 500px;">
{#if browser}
<AnimotPresenter
src="/animations/hero.json"
autoplay
loop
controls
onslidechange={(index, total) => console.log(`${index + 1}/${total}`)}
/>
{/if}
</div>SvelteKit SSR: The component uses browser APIs (canvas, ResizeObserver) and cannot be server-rendered. Always wrap with
{#if browser}in SvelteKit, or usessr: falsein your+page.ts.
React
npm install animot-presenter// Import once to register the custom element
import 'animot-presenter/element';
import { useEffect, useRef } from 'react';
function HeroAnimation() {
const ref = useRef(null);
useEffect(() => {
const el = ref.current;
if (!el) return;
const handleSlideChange = (e) => {
console.log(`Slide ${e.detail.index + 1} of ${e.detail.total}`);
};
el.addEventListener('slidechange', handleSlideChange);
return () => el.removeEventListener('slidechange', handleSlideChange);
}, []);
return (
<animot-presenter
ref={ref}
src="/animations/hero.json"
autoplay
loop
controls
style={{ width: '100%', height: '500px', display: 'block' }}
/>
);
}
// With programmatic data
function ProgrammaticDemo({ data }) {
const ref = useRef(null);
useEffect(() => {
if (ref.current && data) {
ref.current.data = data;
}
}, [data]);
return (
<animot-presenter
ref={ref}
controls
arrows
style={{ width: '100%', height: '400px', display: 'block' }}
/>
);
}TypeScript: Add this to your react-app-env.d.ts or a global .d.ts file:
declare namespace JSX {
interface IntrinsicElements {
'animot-presenter': React.DetailedHTMLProps<
React.HTMLAttributes<HTMLElement> & {
src?: string;
autoplay?: boolean;
loop?: boolean;
controls?: boolean;
arrows?: boolean;
progress?: boolean;
keyboard?: boolean;
duration?: number;
'start-slide'?: number;
},
HTMLElement
>;
}
}Vue 3
npm install animot-presenter<script setup>
import 'animot-presenter/element';
import { ref, onMounted } from 'vue';
const presenterRef = ref(null);
const animationData = ref(null);
function onSlideChange(e) {
console.log(`Slide ${e.detail.index + 1} of ${e.detail.total}`);
}
// Load data programmatically
onMounted(async () => {
const res = await fetch('/animations/demo.json');
animationData.value = await res.json();
if (presenterRef.value) {
presenterRef.value.data = animationData.value;
}
});
</script>
<template>
<!-- Option A: Load from URL -->
<animot-presenter
src="/animations/hero.json"
autoplay
loop
controls
style="width: 100%; height: 500px; display: block"
@slidechange="onSlideChange"
/>
<!-- Option B: Programmatic data -->
<animot-presenter
ref="presenterRef"
controls
arrows
style="width: 800px; height: 450px; display: block"
/>
</template>Important: Tell Vue to treat animot-presenter as a custom element. In vite.config.ts:
export default defineConfig({
plugins: [
vue({
template: {
compilerOptions: {
isCustomElement: (tag) => tag === 'animot-presenter'
}
}
})
]
});Angular
npm install animot-presenter1. Register the custom element in main.ts:
import 'animot-presenter/element';2. Allow custom elements in your module or standalone component:
// app.module.ts
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
@NgModule({
schemas: [CUSTOM_ELEMENTS_SCHEMA],
// ...
})
export class AppModule {}Or in a standalone component:
@Component({
selector: 'app-hero',
standalone: true,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
template: `
<animot-presenter
src="/animations/hero.json"
autoplay
loop
controls
style="width: 100%; height: 500px; display: block"
(slidechange)="onSlideChange($event)"
(complete)="onComplete()"
></animot-presenter>
`
})
export class HeroComponent {
onSlideChange(event: CustomEvent) {
console.log(`Slide ${event.detail.index + 1} of ${event.detail.total}`);
}
onComplete() {
console.log('Animation finished');
}
}3. Programmatic control:
import { Component, ViewChild, ElementRef, AfterViewInit } from '@angular/core';
@Component({
selector: 'app-demo',
standalone: true,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
template: `
<animot-presenter #presenter controls arrows
style="width: 100%; height: 400px; display: block">
</animot-presenter>
<button (click)="loadAnimation()">Load</button>
`
})
export class DemoComponent implements AfterViewInit {
@ViewChild('presenter') presenterRef!: ElementRef;
async loadAnimation() {
const res = await fetch('/animations/demo.json');
const data = await res.json();
this.presenterRef.nativeElement.data = data;
}
}Props / Attributes
| Attribute | Type | Default | Description |
|----------------|-----------|---------|------------------------------------------|
| src | string | — | URL to an Animot JSON file |
| data | object | — | Inline JSON object (JS property only) |
| autoplay | boolean | false | Auto-advance slides |
| loop | boolean | false | Loop back to first slide after last |
| controls | boolean | true | Show prev/next/play controls |
| arrows | boolean | false | Show left/right carousel arrows |
| progress | boolean | true | Show progress bar at bottom |
| keyboard | boolean | true | Enable arrow key navigation |
| duration | number | — | Override all transition durations (ms) |
| start-slide | number | 0 | Initial slide index |
Note: data can only be set via JavaScript property, not as an HTML attribute.
Events
| Event | Detail | Description |
|----------------|-------------------------------|--------------------------------------|
| slidechange | { index: number, total: number } | Fired when slide changes |
| complete | — | Fired when last slide is reached |
// Vanilla JS
presenter.addEventListener('slidechange', (e) => {
console.log(e.detail.index, e.detail.total);
});
// Svelte
<AnimotPresenter onslidechange={(i, t) => ...} oncomplete={() => ...} />
// Vue
<animot-presenter @slidechange="handler" @complete="handler" />
// Angular
<animot-presenter (slidechange)="handler($event)" (complete)="handler()" />Styling
The presenter fills its parent container. Control the size by styling the parent:
/* Full page */
animot-presenter {
display: block;
width: 100vw;
height: 100vh;
}
/* Hero section */
animot-presenter {
display: block;
width: 100%;
height: 500px;
}
/* Fixed size card */
animot-presenter {
display: block;
width: 400px;
height: 225px;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 4px 24px rgba(0, 0, 0, 0.2);
}Customizing Controls
Override the built-in styles using CSS specificity:
/* Custom control bar background */
animot-presenter .animot-controls {
background: rgba(30, 30, 30, 0.9);
border-radius: 20px;
padding: 6px 12px;
}
/* Custom button style */
animot-presenter .animot-controls button {
background: transparent;
border-radius: 50%;
}
animot-presenter .animot-controls button:hover {
background: rgba(255, 255, 255, 0.15);
}
/* Custom progress bar */
animot-presenter .animot-progress-fill {
background: #3b82f6;
}
/* Custom arrow buttons */
animot-presenter .animot-arrow {
background: rgba(0, 0, 0, 0.6);
width: 48px;
height: 48px;
}
/* Always show controls (no hover needed) */
animot-presenter .animot-controls,
animot-presenter .animot-arrow,
animot-presenter .animot-progress-bar {
opacity: 1 !important;
}
/* Hide controls entirely via CSS */
animot-presenter .animot-controls { display: none; }Tailwind CSS
<animot-presenter
class="w-full h-[500px] rounded-xl overflow-hidden shadow-2xl"
src="/animation.json"
autoplay
loop
></animot-presenter>Features
- Morphing animations — Elements with the same ID across slides smoothly morph position, size, rotation, color, opacity, border radius, and CSS filters (blur, brightness, contrast, saturate, grayscale)
- Code highlighting — Syntax highlighting via Shiki with typewriter, highlight-changes, and instant animation modes
- Shape morphing — Rectangles, circles, triangles, stars, hexagons with smooth transitions
- Charts — Animated bar, line, area, pie, and donut charts
- Counters — Animated number counting with formatting
- Particles — Canvas-based particle backgrounds with configurable shapes and connections
- Confetti — Burst, continuous, fireworks, and snow confetti effects
- Floating animations — Gentle floating motion for elements (vertical, horizontal, or both)
- Transitions — Fade, slide, zoom, flip, and morphing (none) transition types
- Responsive — Automatically scales to fit any container size
- Keyboard navigation — Arrow keys, spacebar, Home/End
- CSS filters — Blur, brightness, contrast, saturate, and grayscale on any element, animated across slides
- Property sequencing — Fine-grained control over which properties animate first (including filters via the
blurproperty sequence)
JSON Schema
Animot JSON files follow this structure:
{
"schemaVersion": 1,
"id": "unique-id",
"name": "My Animation",
"slides": [
{
"id": "slide-1",
"name": "Intro",
"canvas": {
"width": 1920,
"height": 1080,
"background": {
"type": "gradient",
"gradient": { "type": "linear", "angle": 135, "colors": ["#1a1a2e", "#16213e"] }
},
"elements": [
{
"id": "title",
"type": "text",
"content": "Hello World",
"position": { "x": 100, "y": 200 },
"size": { "width": 600, "height": 80 },
"fontSize": 48,
"fontWeight": 700,
"color": "#ffffff",
"rotation": 0,
"visible": true,
"zIndex": 1,
"blur": 0,
"brightness": 100,
"contrast": 100,
"saturate": 100,
"grayscale": 0
}
]
},
"transition": { "type": "fade", "duration": 500, "easing": "ease-in-out" },
"duration": 3000
}
],
"settings": {
"defaultCanvasWidth": 1920,
"defaultCanvasHeight": 1080,
"defaultTransition": { "type": "fade", "duration": 500, "easing": "ease-in-out" },
"defaultSlideDuration": 3000
}
}Create animations visually at animot.io and export as JSON.
Editing the JSON
You can edit the exported JSON file directly. For example, image elements use a src field that accepts any valid image source:
{
"id": "logo",
"type": "image",
"src": "data:image/png;base64,iVBORw0KGgo..."
}You can replace the base64 data URL with a remote or local URL:
// Remote URL
"src": "https://example.com/images/logo.png"
// Relative path (served from your project)
"src": "/images/logo.png"
// Another relative path
"src": "./assets/hero-bg.jpg"This lets you keep your JSON files lightweight by hosting images separately instead of embedding them as base64. The same applies to text elements with backgroundImage and background image fields.
CSS Filters
All element types support CSS filter properties that animate smoothly between slides:
{
"id": "hero-image",
"type": "image",
"blur": 5,
"brightness": 120,
"contrast": 110,
"saturate": 80,
"grayscale": 0
}| Property | Range | Default | Description |
|--------------|---------|---------|----------------------------------|
| blur | 0–20 | 0 | Gaussian blur in pixels |
| brightness | 0–200 | 100 | Brightness percentage (100 = normal) |
| contrast | 0–200 | 100 | Contrast percentage (100 = normal) |
| saturate | 0–200 | 100 | Saturation percentage (100 = normal) |
| grayscale | 0–100 | 0 | Grayscale percentage (0 = none) |
Set different filter values on the same element across slides to create smooth animated transitions (e.g., blur 10 on slide 1 to blur 0 on slide 2 for a reveal effect). Use the blur property sequence in animationConfig.propertySequences to control filter animation timing independently.
Note: Remote URLs must allow cross-origin requests (CORS) if served from a different domain.
Bundle Size
| Build | Raw | Gzipped | |-------|-----|---------| | CDN (IIFE) | ~5.0 MB | ~850 KB | | CDN (ESM) | ~5.2 MB | ~870 KB | | Svelte (tree-shakeable) | ~56 KB | — |
The CDN bundle includes Shiki for code syntax highlighting with 70 web-focused languages (see list below). The npm Svelte package is much smaller since Shiki is loaded lazily at runtime with all 500+ languages available.
CDN supported languages
The CDN bundle includes these languages for code highlighting:
angular-html, angular-ts, astro, bash, blade, c, c++, coffee, cpp, css, glsl, gql, graphql, haml, handlebars, html, http, imba, java, javascript, jinja, jison, json, json5, jsonc, jsonl, js, jsx, julia, less, lit, markdown, marko, mdc, mdx, php, postcss, pug, python, r, regex, sass, scss, sh, shell, sql, stylus, svelte, ts, tsx, typescript, vue, wasm, wgsl, xml, yaml, yml, zsh
npm install gives you all 500+ Shiki languages. The CDN bundle uses the lighter
shiki/bundle/webwith 70 common web/programming languages. If your code blocks use an unsupported language, it falls back to JavaScript highlighting.
Browser Support
Works in all modern browsers that support Custom Elements v1:
- Chrome 67+
- Firefox 63+
- Safari 10.1+
- Edge 79+
License
Business Source License 1.1 (BUSL-1.1)
- Free for non-commercial and personal use
- Commercial use requires a paid license — contact [email protected]
- After the change date (4 years from each release), the code converts to Apache-2.0
