npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

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.

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

CDN (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 use ssr: false in 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-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;
  }
}

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 blur property 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/web with 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