A scrolling screenshot shows a whole web page the way a visitor meets it: the top first, then the page sliding up. As a video it fits where a tall full-page picture does not — a post, a portfolio, a client report, a product page's gallery. There are two ways to make one, and they produce different videos. This page shows both, with the file sizes and times we measured.
How we measured. On 2026-11-08 we made scroll videos of python.org's home page (2,726 px tall at 1280 px wide, about 3.4 screens) through our API from our development stack, at the default desktop viewport and with the iphone-15 preset, and recorded the same page with Playwright 1.64 on the same machine. The page loads in a few seconds, so render times include it.
Two ways to make a scroll video
- Record a real browser. A script scrolls the page while the browser records its screen. You get everything the page does: animations, lazy images popping in, a sticky header following you down, and also the page's own loading and any jank.
- Slide over a full-page screenshot. Capture the page once from top to bottom, then cut a viewport-sized window out of that tall picture for each frame, moving it down. The motion is perfectly smooth and the result is the same every time; nothing on the page moves except the scroll itself.
Our API does the second. Use the first when the page's motion is the point.
Recording it yourself with Playwright
Playwright records video without extra software (it ships its own ffmpeg). Its video docs say the video "size defaults to the viewport size scaled down to fit 800x800", so set the size, and that videos are saved "upon browser context closure". The script, run on 2026-11-08:
import { chromium } from 'playwright';
const browser = await chromium.launch();
const context = await browser.newContext({
viewport: { width: 1280, height: 800 },
recordVideo: { dir: 'videos', size: { width: 1280, height: 800 } },
});
const page = await context.newPage();
await page.goto('https://www.python.org/', { waitUntil: 'load' });
await page.waitForTimeout(600); // hold at the top
// Scroll to the bottom in about 4 seconds, easing in and out.
await page.evaluate(async () => {
const distance = document.documentElement.scrollHeight - innerHeight;
const start = performance.now();
await new Promise((done) => {
const step = (now) => {
const t = Math.min(1, (now - start) / 4000);
scrollTo(0, distance * (t < 0.5 ? 4 * t ** 3 : 1 - (-2 * t + 2) ** 3 / 2));
t < 1 ? requestAnimationFrame(step) : done();
};
requestAnimationFrame(step);
});
});
await page.waitForTimeout(600); // hold at the bottom
const video = page.video();
await context.close(); // the file is written here
console.log(await video.path());
await browser.close();
It took 6.8 s in total and wrote a 1280×800 VP9 WebM of 6.1 s, 1.9 MB. The recording starts with the context, so the first half second shows the page loading over a white screen; trim it with ffmpeg -ss 0.6 -i in.webm out.mp4, which also gives you an MP4 for places that do not play WebM. A page with lazy images may show them appearing as you scroll past; scroll once through the page before you start recording if you do not want that.
Puppeteer can do the same with page.screencast({ path: 'scroll.webm' }), but "You must have ffmpeg installed on your system" (Puppeteer docs).
One API call instead
curl -X POST "https://shareglint.com/v1/screenshots?wait=true" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://www.python.org/", "scroll_video": true, "format": "mp4", "block_cookie_banners": true}'
What happens on our side: the page is captured once in full, with the lazy-load pass on (so images further down are in the picture) and down to 8 screen heights at most. ffmpeg then cuts the frames with its crop filter, moving the window down the picture: the video holds still for 0.6 s at the top and at the bottom and eases in and out between them. MP4 runs at 25 fps, GIF at 12. The long edge of the video is at most 1080 px. duration sets the length, 1 to 10 seconds.
What came out:
| Request | Video | Size | Render time |
|---|---|---|---|
| Desktop, MP4, 5 s | 1080×674, 125 frames | 760 KB | 12.8 s |
| Desktop, MP4, 10 s | 1080×674, 250 frames | 1.53 MB | 17.0 s |
| Desktop, GIF, 5 s | 1080×674, 60 frames | 5.4 MB | 11.5 s |
iphone-15, MP4, 5 s |
498×1080, 125 frames | 739 KB | 12.6 s |
iphone-15, GIF, 5 s |
498×1080, 60 frames | 2.0 MB | 11.3 s |
| Full-page PNG, for comparison | 1280×2726, still | 505 KB | 9.8 s |
| Full-page JPEG, for comparison | 1280×2726, still | 372 KB | 9.7 s |
Asking for the same video again returned the stored file at once: the same URL with the same options within 24 hours is served from cache and costs nothing.
MP4, GIF or a tall picture?
- MP4 is the default and the right choice wherever video plays: social posts, web pages (
<video autoplay muted loop playsinline>), slides. Doubling the length doubled the file (760 KB → 1.53 MB). - GIF is seven times larger for the desktop scroll at half the frame rate. Use it only where a video will not play inline — most email clients, some chat apps, older CMSs — and prefer the phone-sized one.
- A full-page screenshot is smaller still and shows every pixel at once; better for documentation, audits and anything someone will zoom into. See full-page screenshots in Chrome.
For a video framed in a browser window or a phone, render the still in our browser-frame or device-mockup template instead; animated renders of templates are covered in animated OG images.
What our scroll video does not show
Because it slides over one picture, everything on the page that moves by itself is frozen at the moment of capture: carousels, auto-playing videos, animated counters, parallax and scroll-triggered effects. A sticky header or a chat button appears once, where it was at the top, instead of following the scroll. Pages longer than 8 screens are cut there. If any of that matters, record a real browser as above.
The other options work as for any screenshot: device for phone and tablet presets, dark_mode, block_cookie_banners (what it removed on 12 sites), hide_selectors for anything else in the way. A scroll video costs 5 renders of the quota; a viewport screenshot costs 1 and a full page 2 (pricing, screenshot API).