Skip to content

Static sites & CMS

Building with a static-site generator or a CMS? Use a pre-rendered SVG: the build tool or CMS inlines the file and the animation is on screen before any JavaScript runs. Even the flavours with a <script> just work when inlined. Where a framework also runs client code, the JSON format with a player is available too.

FrameworkInline a pre-rendered SVGJSON alternative
Astroimport svg from './animation.svg?raw'; then <Fragment set:html={svg} />use the React or Vue component with the client:load directive, which tells Astro to run it in the browser (an “island”, in Astro’s own terms)
Gatsbygatsby-plugin-react-svg (CSS flavour) or dangerouslySetInnerHTML with the raw fileReact component
Jekyll{% include_relative assets/animation.svg %}use the player library @pixodesk/svg-animator-web: it ships a single .js file (the “UMD” build) that you copy to your site and load with a plain <script> tag — how to get the file, example below
Hugo{{ readFile "static/animation.svg" | safeHTML }}use the player library @pixodesk/svg-animator-web: it ships a single .js file (the “UMD” build) that you copy to your site and load with a plain <script> tag — how to get the file, example below
11ty (Eleventy){% include "animation.svg" %}use the player library @pixodesk/svg-animator-web: it ships a single .js file (the “UMD” build) that you copy to your site and load with a plain <script> tag — how to get the file, example below
Docusaurus / MDXimport the CSS flavour as a component (SVGR is built in) — example belowReact component

The rows above use two different ways of putting the file into the page, and they are not equal. A raw include — Astro’s ?raw import, Jekyll’s include, Hugo’s readFile — copies the file’s content into the HTML exactly as it is, <script> tags included; that is why the JS-triggers and JS-animation flavours work in these generators. A component import (SVGR, vite-svg-loader) instead converts the SVG into a component, and removes any <script> along the way — so it works for the CSS flavour only.

Docusaurus imports .svg files as React components through SVGR, so a CSS-flavour export drops into any .mdx page like an icon, right among the page’s ordinary Markdown content:

import Ball from './animation.svg';
# Welcome
<Ball style={{ width: 300, height: 300 }} />

(# Welcome is just the page’s own Markdown heading — in .mdx, Markdown text and imported components sit side by side.)

An On load export plays on its own. For a hover, click or scroll trigger, wrap it in the React package’s PixodeskSvgCssAnimator, which toggles the play classes for you:

import Ball from './animation.svg';
import { PixodeskSvgCssAnimator } from '@pixodesk/svg-animator-react';
<PixodeskSvgCssAnimator start="mouseOver" mouseOut="pause" style={{ width: 300, height: 300 }}>
<Ball />
</PixodeskSvgCssAnimator>

One setting matters. Docusaurus runs SVGO, an SVG optimiser, on every imported SVG — and one of SVGO’s optimisations (inlineStyles) rewrites the file’s <style> rules as plain attributes on the elements. That rewrite destroys the animation’s on/off switch: the file’s CSS rules only apply while the play classes are on the root <svg> (how that works), and once the rules are inlined, the classes no longer control anything — the animation cannot be started or stopped. Turn SVGO off for the SVGR plugin in docusaurus.config.js:

export default {
presets: [
['classic', {
svgr: { svgrConfig: { svgo: false } },
}],
],
};

Pasting the SVG markup straight into an .mdx file does not work: MDX reads the { } inside <style> as expressions and drops <script>. For the scripted flavours, import the file raw and set it as HTML — Docusaurus renders the page to static HTML at build time, so the file’s own <script> runs when the page loads:

import svg from '!!raw-loader!./animation.svg';
<div dangerouslySetInnerHTML={{ __html: svg }} />

The !!raw-loader! prefix in the import needs the raw-loader package — add it with npm install raw-loader. This is not our invention: it is the approach Docusaurus itself recommends for bringing a file’s raw text into a page.

Application frameworks that render on the client — Next.js, Nuxt, SvelteKit, Angular — are a different case: there the JSON format with a player is the natural fit. See React → Next.js and Vue → Nuxt.

Example: static/vanilla-umd — pnpm example:docs, then open #static/vanilla-umd.

A page with no framework and no build step: the UMD build copied to your site, one element per animation, one call:

<div data-px-animation-src="/bouncing-ball.json"></div>
<script src="/js/pixodesk-svg-animator.umd.min.js"></script>
<script>PixodeskAnimator.loadTagAnimators();</script>

The pixodesk-svg-animator.umd.min.js file in the first <script> tag is the player itself — one .js file you copy out of the npm package and put on your own site; Installing the players (overview) shows exactly how. Everything you can do with the player from here — controlling playback, reacting to events — is described in Web player.

Paste the SVG’s markup into the platform’s HTML / code block. One thing to know first: most of these platforms clean up any HTML you paste before publishing it — they strip out tags they consider risky, and <script> is usually the first to go. When that happens to a pre-rendered SVG of a flavour that carries a script (JS triggers, JS animation), the file still shows, but its animation is broken or cannot start. The SVG + CSS animation flavour has no <script> at all, so nothing can be stripped from it — it is the safe choice on any platform that filters pasted HTML.

PlatformMethod
WordPressa Custom HTML block, or in a theme: <?php echo file_get_contents(get_template_directory() . '/assets/animation.svg'); ?>
Shopifysave the SVG file as a theme snippet (e.g. snippets/animation.liquid), then put {% render 'animation' %} in the template where it should appear — or simply paste the SVG markup into a section’s custom Liquid code
WebflowEmbed component → paste the SVG markup
SquarespaceCode block → paste the SVG markup
WixEmbed HTML element → paste the SVG markup (runs in an iframe)
Framer / Notion / othersan embed / code block that accepts raw HTML

Before you publish — several things to check

Section titled “Before you publish — several things to check”
  • Content Security Policy (CSP). Inlined scripts (JS-triggers / JS-animation flavours) count as inline scripts; if your site’s CSP forbids them, use the CSS flavour, or JSON with the player loaded from your own origin.
  • ⚠️ Don’t inline the same file twice on one page. Different files are fine; a duplicate of the same file repeats its ids and breaks masks, gradients and bindings. For the same animation in two places, export a separate file for each (every export gets its own ids) or use JSON, where the player gives every copy fresh ids (read more).
  • Sizing. Keep the viewBox, remove fixed width/height if you want the SVG to scale with its container, and size the container with CSS.
  • Animations further down the page. An animation the visitor has to scroll to should wait until it is on screen, or it plays while nobody sees it and is over by the time the visitor gets there. Keep the default trigger — On load with Off Screen: pause — and export as SVG + CSS animation + JS triggers (or JSON). The pure SVG + CSS flavour cannot see scrolling, so it starts as soon as the page loads.