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:
- Load the page in headless Chrome with Puppeteer or Playwright at the size you want.
- For each frame, run
document.getAnimations()in the page,pause()each animation and set itscurrentTimeto the frame's time; for JavaScript animation, drive your own clock instead. - Take a screenshot per frame (
page.screenshot()or the DevTools protocol'sPage.captureScreenshot, which is faster for dozens of frames). - Encode with ffmpeg. For GIF, a two-pass palette —
palettegenthenpaletteuse— avoids the banding of the default palette; for MP4,-c:v libx264 -pix_fmt yuv420p -movflags +faststartgives 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.