Menu
Sign up

HTML to GIF: turn a CSS animation into a GIF or MP4

Convert an HTML and CSS animation into an animated GIF or MP4 with one API call: frame-exact recording, the limits, and how to do it yourself instead.

Published November 6, 2026 · 5 min read

To convert HTML to a GIF, you need a browser to play the animation and something to turn its frames into a file. Shareglint does both: save the HTML and CSS as a template, request it with "format": "gif" or "format": "mp4", and you get back a looping GIF or an H.264 MP4 of up to 5 seconds. The same template rendered as PNG gives the still, so one design covers the animated post and the static og:image.

This page is about the conversion itself: how the recording works, the request, the limits, and what a do-it-yourself version with Puppeteer and ffmpeg involves. For where animated images actually play — link previews mostly show the first frame — see animated OG images.

Recorded frame by frame, not filmed

A screen recorder films a page in real time, so a busy machine drops frames and every run comes out a little different. The renderer does not let time run at all. For each frame it pauses every CSS animation and transition on the page and sets it to that frame's exact moment through the Web Animations API (document.getAnimations()), then captures the frame. A 3-second clip at 15 fps is 45 frames at 0, 66.7, 133.3… milliseconds, every time.

Two things follow. The same template with the same fields always gives the same file, which is why a repeat request is served from cache for free. And anything CSS can animate works: transform, opacity, colours, clip-path, filter, background-position, keyframes with delays and easing, transitions.

The request

Save the template once:

curl -X POST "https://shareglint.com/v1/templates" \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Launch banner",
    "slug": "launch-banner",
    "width": 1200, "height": 630,
    "fields": ["headline"],
    "html": "<div class=\"wrap\"><h1>{{ headline }}</h1><div class=\"bar\"></div></div>",
    "css": ".wrap{height:100%;display:grid;place-content:center;background:#0b1020;color:#fff;font:700 72px Roboto}.bar{height:12px;background:#ff7a18;transform-origin:left}.motion h1{animation:rise var(--clip) ease-out both}.motion .bar{animation:grow var(--clip) ease-in-out both}@keyframes rise{from{opacity:0;transform:translateY(40px)}}@keyframes grow{from{transform:scaleX(0)}}"
  }'

Then render it as a clip:

curl -X POST "https://shareglint.com/v1/images?wait=true" \
  -H "Authorization: Bearer sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "template": "launch-banner",
    "format": "gif",
    "duration": 3,
    "fps": 15,
    "modifications": { "headline": "Version 2 is live" }
  }'

The response is an ordinary render with an animation object (duration, fps, frames) and the file's url. Swap gif for mp4 for the video, add "sizes": ["og", "square", "story"] for the same animation in other shapes (the template lays itself out again for each — see one template, every size), or use a signed URL ending in .gif to render from an <img> tag without a server.

The two hooks in the CSS above matter. During a GIF or MP4 render the <html> element has the class motion and the CSS variable --clip with the clip length. A still render has no motion class, so the PNG shows the finished design, and timing every animation as a fraction of var(--clip) means a 2-second and a 5-second render both play the whole sequence.

Built-in animated templates — countdown, stat counter, typing headline, product reveal and more, all with slugs starting with animated- — work the same way without writing any CSS.

Limits

Length 0.5 to 5 s, default 3
Frame rate 10 to 30 fps; default 15 for GIF, 30 for MP4
Size long edge capped at 1080 px, both edges even (og 1200×630 gives 1080×566)
GIF loops forever, one 256-colour palette computed for the whole clip
MP4 H.264, yuv420p, faststart, no audio
Scripts removed from your templates on save — CSS animation only
Batches still formats only; one request per clip
Cost 5 renders of quota per clip and size

The length limit is deliberate: this is for loops that sit in a feed, an email or a chat message, not for screen recordings or product videos. A 3-second GIF at 15 fps is roughly 0.1 to 3 MB depending on how much of the frame moves; the MP4 of the same clip is a fraction of that.

GIF or MP4

  • Email: GIF. It is the only animation that plays broadly in email clients; the HTML <video> element is stripped by Gmail (Can I email). Outlook for Windows does not always play a GIF (Can I email), so make the first frame readable.
  • Social posts, Discord, ads: MP4. Smaller, full colour, plays in every feed.
  • Your own pages: MP4 in <video autoplay muted loop playsinline> — lighter than a GIF of the same clip.
  • og:image: a still PNG or JPEG; most link previews show only a GIF's first frame anyway.

Doing it yourself with Puppeteer and ffmpeg

The same result is reachable with open-source tools, and for a one-off it may be all you need:

  1. Load the page in headless Chrome with Puppeteer or Playwright at the size you want.
  2. For each frame, run document.getAnimations() in the page, pause() each animation and set its currentTime to the frame's time; for JavaScript animation, drive your own clock instead.
  3. Take a screenshot per frame (page.screenshot() or the DevTools protocol's Page.captureScreenshot, which is faster for dozens of frames).
  4. Encode with ffmpeg. For GIF, a two-pass palette — palettegen then paletteuse — avoids the banding of the default palette; for MP4, -c:v libx264 -pix_fmt yuv420p -movflags +faststart gives a file every phone plays (yuv420p also needs even width and height).

What a service saves is the operating part: a Chrome pool that survives traffic, fonts and emoji installed, a frame loop that never waits on real time, caching of identical requests, a queue that keeps long encodes away from your web server, and per-request limits so one large clip does not starve the rest. If you already run headless Chrome for screenshots, adding the frame loop and ffmpeg is a day of work; if you do not, it is a new service to run.

A third route is a client-side library that paints the DOM into a canvas and encodes a GIF in the browser. It never needs a server, but it re-implements CSS rendering itself, so fonts, shadows, filters and newer CSS often come out differently from what the browser shows.

Cost

An animated render uses 5 renders of your monthly quota per clip and size — capturing and encoding dozens of frames is that much more work than one screenshot. The same template with the same fields, duration and fps again is free, and a failed render returns all 5. The Free plan's 100 renders a month include GIF and MP4, with the same watermark as stills; Starter is $9 a month for 2,000 renders (pricing).

What it does not do

  • No screen recording of a live website, and no scrolling or clicking through a page — it plays the CSS animations of one template.
  • No JavaScript, canvas or WebGL animation in your own templates.
  • No audio, and nothing longer than 5 seconds.
  • No GIF in bulk batches; render clips one request at a time.

FAQ

Can I convert HTML to a GIF?

Yes. Save your HTML and CSS as a template and render it with format gif (or mp4). The renderer steps every CSS animation and transition frame by frame in headless Chrome and encodes the frames, so the GIF shows exactly what the browser would play.

How long can the GIF be?

From 0.5 to 5 seconds, at 10 to 30 frames per second (15 by default for GIF, 30 for MP4). The long edge is capped at 1080 pixels. It is meant for short loops — cards, counters, banners — not screen recordings.

Does JavaScript animation work?

Not in your own templates: scripts are removed when a template is saved, so animate with CSS keyframes and transitions. Canvas, WebGL and GSAP-style animations need a browser you run yourself.

Should I render GIF or MP4?

MP4 wherever video is accepted — social posts, Discord, your own pages — because it is many times smaller and keeps full colour. GIF where only images are accepted, above all in email.