
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
animot-presenter
Advanced tools
Embed animated presentations anywhere. Works with vanilla JS, React, Vue, Angular, Svelte, and any frontend framework. Morphing animations, code highlighting, charts, particles, and more.
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.
npm install animot-presenter
<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>
<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.
<!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>
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.
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
>;
}
}
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'
}
}
})
]
});
npm install animot-presenter
1. 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;
}
}
| 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.
| 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()" />
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);
}
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; }
<animot-presenter
class="w-full h-[500px] rounded-xl overflow-hidden shadow-2xl"
src="/animation.json"
autoplay
loop
></animot-presenter>
blur property sequence)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.
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.
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.
| 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.
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.
Works in all modern browsers that support Custom Elements v1:
Business Source License 1.1 (BUSL-1.1)
FAQs
Embed animated presentations anywhere. Works with vanilla JS, React, Vue, Angular, Svelte, and any frontend framework. Morphing animations, code highlighting, charts, particles, and more.
We found that animot-presenter demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.