A finished website needs pictures of itself: for a portfolio entry, for an offer, for a case study. The obvious way is to open the page, press the screenshot key and crop the result. That works exactly once. It stops working when the page animates as you scroll, when there is a second language version, and when the client changes a paragraph three weeks later and every picture is suddenly out of date. So I photograph websites the same way I do every other repetitive job: with a headless browser and a short script.
The pictures go stale before the project does
When three client sites went into my portfolio, the set came to twenty-six shots: the hero of each subpage, crops of individual sections, one mobile view per project. Pressing a key twenty-six times is not the hard part. The hard part is doing it again after a copy change, then again for the other language, and keeping the framing identical every time so the gallery does not jump between images. A script does all of that with one command, and the framing stays identical by construction, because it is a number in the script rather than my hand on a trackpad.
The page has to be scrolled before it is photographed
Marketing pages animate their sections into view: a block stays invisible until it enters the viewport, then it fades and slides in. A browser that opens the page and captures it immediately gets what a visitor would see with the animation still in flight, which is black boxes and half-empty sections. My first batch of case-study shots came out exactly like that, and the sections that looked worst were the ones I was proudest of.
The fix is dull and reliable: before capturing anything the script scrolls the whole page to the bottom in steps of a few hundred pixels, lets the animations finish, and only then goes back and takes the pictures. It is the browser equivalent of walking through a room before you photograph it.
One catch for anything that does not scroll the window itself. If the page scrolls inside its own container, the window scroll position stays at zero, and every trick keyed to that number quietly does nothing. Scrolling the target element into view is what works.
Heavy scenes refuse to sit still
Capturing a single element is the neat approach: exact bounds, no cropping afterwards. It is also the one that breaks on anything heavy. On a page with a live 3D scene, element capture times out with the browser insisting the element is not stable, because the scene keeps repainting and never will be stable. Photographing the whole viewport with a clip rectangle works every time. It took me longer than I would like to admit to accept that the crude method is the correct one here.
ffmpeg does the darkroom work
The browser only produces raw pixels. Everything after that is ffmpeg: scaling every shot to one gallery size, cutting section crops out of a single tall full-page capture instead of shooting each section separately, and padding the phone screenshot onto the same landscape canvas as the rest so the gallery keeps one aspect ratio. Twenty-six JPEGs at 1920 by 1200 come to under four megabytes in total, which the site can carry without a media CDN.
I keep the capture and the processing in two separate scripts. Reframing a crop then costs seconds and does not mean opening a browser again. The same two tools also record video, which is a longer story I told in a product video without a camera.
The same trick for pictures that are not websites
When client logos had to come off my automation service page, I replaced them with screenshots of real code from the automation running on my own server: syntax-highlighted source rendered into a window frame, photographed by the same headless browser at a fixed viewport. Two rules came out of that. Choose the line range so the shot does not end in the middle of a comment. And read every visible line before it becomes a marketing asset, because a code screenshot leaks more than people expect: no client names, no credentials, nothing that is not mine to show.
What a headless browser will not show you
Third-party embeds. A map in an iframe does not render in headless Chromium, so a contact-page shot has an empty box where the map belongs. Fonts and consent banners have the same habit of behaving differently from what you see on your own screen. That is why the last step is manual and stays manual: every frame on one contact sheet, looked at once, before any of it goes near the site. The script removes the repetition, not the judgement.
When it is worth scripting
Take a set of pictures by hand if you take it once. Script it when at least one of these is true: two language versions, more than a handful of frames, or an interface that changes more often than the pictures of it. My rule is the second run. The moment I know I will need the same shots again, the script has already paid for itself.
The website case studies in my portfolio were all shot this way, and so were the interface stills on the AxisMod page. If your own site needs pictures that stay current instead of pictures that age, that is part of what web development means here.



