Polotno
Features

Animations and Videos

Enable animations, preview on canvas, export as GIF, and render MP4 via Cloud API

How to enable animation support?

import { setAnimationsEnabled } from 'polotno/config';

setAnimationsEnabled(true);

When you enable animations, Polotno will add additional UI in the toolbar to change animation properties of the selected object, show a “Videos” side panel with a library of stock videos, and adapt the Pages component with scene preview.

Group animations

A group can own its own enter, exit, and loop animations directly: selecting a group shows the same animation controls as a single element. Group animations are composed on top of each child's own animation, so you can animate a whole group (for example, slide it in) without setting up each child individually, while children keep any animations of their own.

You can preview animations and export an animated scene as a GIF on the client side programmatically:

store.play();
store.stop();
await store.saveAsGIF();

Video Export Options

Polotno supports two methods for exporting animated designs to MP4 video:

Client-Side Video Export

For browser-based video generation without a server, use the @polotno/video-export package. All encoding happens directly in the user's browser:

npm install @polotno/video-export
import { storeToVideo } from '@polotno/video-export';

const videoBlob = await storeToVideo({
  store,
  fps: 30,
  pixelRatio: 2,
  onProgress: (progress) => {
    console.log(`Progress: ${Math.round(progress * 100)}%`);
  },
});

// Download the video
const url = URL.createObjectURL(videoBlob);
const link = document.createElement('a');
link.href = url;
link.download = 'video.mp4';
link.click();

Export speed depends on user hardware. High FPS or complex designs may take longer to process.

See the full Video Export documentation for complete API reference and live demo.

Server-Side Video Export

For production-grade video rendering with consistent performance, use the Cloud Render API. This option provides faster rendering and doesn't depend on client device capabilities.

Audio Support

Add background music or sound effects to your animated designs. Audio tracks sync with the video timeline and export together with animations.

See the Audio documentation for complete details on adding and managing audio tracks.

Generating video JSON with an LLM

Pass these rules to the model together with the design schema.

Trim

startTime and endTime are fractions of the source clip (0 to 1). duration is the clip length in milliseconds. Seconds 3 to 5 of a 12-second clip:

{
  "type": "video",
  "src": "https://example.com/clip.mp4",
  "duration": 12000,
  "startTime": 0.25,
  "endTime": 0.4167
}

startTime = 3000 / duration, endTime = 5000 / duration. Measure duration for a new clip, or read it from the existing JSON for an edit.

Crop

cropX, cropY, cropWidth and cropHeight are fractions of the source size. With the default values, a clip with a different aspect ratio fills the element from its top-left corner. Centred crop:

const frameRatio = element.width / element.height;
const sourceRatio = sourceWidth / sourceHeight;

const cropWidth = sourceRatio > frameRatio ? frameRatio / sourceRatio : 1;
const cropHeight = sourceRatio > frameRatio ? 1 : sourceRatio / frameRatio;

const crop = {
  cropX: (1 - cropWidth) / 2,
  cropY: (1 - cropHeight) / 2,
  cropWidth,
  cropHeight,
};

16:9 clip in a 9:16 frame: cropWidth = (9/16) / (16/9) ≈ 0.3164, cropX ≈ 0.3418, cropY = 0, cropHeight = 1.

Animation names

type: enter, exit, loop.

name: none, fade, move, zoom, rotate, blink, bounce. An unknown name logs a console error and the element does not animate.

Validation

store.loadJSON accepts out-of-range values without an error. validateDesign from @polotno/schema checks structure and types, but not ranges or animation names. Check these before loading:

  • startTime, endTime, cropX, cropY, cropWidth, cropHeight are between 0 and 1
  • startTime < endTime
  • cropX + cropWidth <= 1 and cropY + cropHeight <= 1
  • every animation name is in the list above

Include every top-level key from store.toJSON(). Use audios: [] for a design without audio.

Live demo

On this page