DEV Community

Cover image for How I built a browser extension that records a full web page while auto-scrolling
Keny Alba
Keny Alba

Posted on

How I built a browser extension that records a full web page while auto-scrolling

Why a screenshot was not enough

I needed recordings of websites that moved the way a person would scroll them: a pause near a headline, a steady pass through a long article, a stop at the footer.

A full-page screenshot showed the layout. It did not show motion, hover, lazy-loaded blocks filling in, or the page-load sequence. It was a poster of the site, not a demo of using it.

A manual screen recording showed those things, and also my hand on the wheel. The speed was uneven. I skipped sections. The cursor wandered. A sticky header covered the title I meant to show.

That gap is why I built ScrollFlow as a Chrome extension. It drives the scroll itself and records on the user’s machine. It does not record every website correctly. The rest of this post is about why that is true.

A webpage is moving target

A webpage is not a PDF. While you travel down it, the document keeps changing.

Lazy-loaded images and extra sections arrive after you thought you had reached the bottom. A cookie banner or promo bar collapses, and the “bottom” you measured a second ago is no longer there. Sticky and fixed chrome sit in every viewport. If you stitch screenshots, that chrome stamps itself onto every slice.

Scroll-snap, smooth-scroll libraries, and custom overflow containers often ignore a simple “scroll to Y” command, or they fight it with their own animation loop. Canvas and WebGL scenes may have no real scrollbar at all, so the browser cannot tell you when the story is over.

Capture films what the tab is actually showing, not what your scroll function believes. If the encoder cannot keep up, frames disappear while the screen still looks smooth. If the tab is in the background, the recording can go black.

The hard problem is not calling scroll. It is keeping a changing document, a browser capture API, and an encoder honest about what landed in the file.

The approach: record live, render when needed

I did not send pages to a remote rendering backend. Recording and encoding run on the user’s device. The finished file downloads locally.

There are two ways to produce motion, and they answer different questions.

Live capture films the actual tab while auto-scroll runs. Animations, video, hover, and WebGL that are on screen appear in the file because they were really there. Timing is whatever the browser delivered that day. That is the path I use when the page is alive.

Cinema mode captures the page once as a still full-page map, then flies a virtual camera over that image. Timing is controlled. Motion can look deliberate because the “page” is a bitmap. Hover menus, playing video, and WebGL are not in that bitmap, so they cannot appear. I use this when I need a clean move down a mostly static layout.

A stitched full-page PNG is still useful when a still is the deliverable. It is a scan of viewports, with sticky chrome handled as a special case, not a screenshot of the whole document object.

Exports stay on the machine: the live recording, a local conversion when a different container is needed, or a composited file from the map. If a conversion fails, the capture is not thrown away.

Where browser automation gets difficult

Four cases ate most of the work.

Lazy-loaded content lies about height. Auto-scroll can hit a fake bottom, notice that the document grew, and continue. It can also unlock if a banner collapsed and the real bottom moved. Infinite feeds and images that arrive late still surprise it. There is no complete answer, only a better guess than stopping on the first pause.

Sticky and fixed UI reprints itself. For a full-page image I keep the header on the first slice and hide it on the rest, then look again after each step. Many navs are ordinary until you scroll, then they become fixed. Overlay cleanup hides chrome without deleting nodes, and it still guesses wrong on some widgets.

Scroll-snap and custom scroll engines fight linear motion. Pixel-by-pixel scrolling on a snapped marketing site looks like the page is arguing with you. Ordinary auto-scroll turns snap off. A stepped, impulse-style move leaves snap on, because those sites use it as the section cut. Smooth-scroll libraries remember an old position and restore it after you jump to the top, so the start of a capture has to hold the page there until they settle. Overflow boxes that actually own the scroll need extra handling. Some pages still win.

Canvas, WebGL, and dynamically rendered scenes often have no usable scrollbar. If the document barely scrolls, I keep moving at a constant speed and I do not auto-stop. The user has to end the recording. That is the honest outcome for a scene that never exposed a height. Cinema mode cannot invent a running canvas from a still map.

What I learned

I learned four things I wish I had believed on day one.

  • A screenshot and a recording are different products. Freezing animation and hiding sticky chrome is correct for a poster and wrong for a scroll-driven demo.
  • The page, the compositor, and the encoder are three clocks. Forcing them into one number made the video look worse than accepting what the live tab actually produced.
  • Local encoding is a feature and a limit. Work stays on the device. Heavy conversions can fail, so a lesser local file is better than a crashed export.
  • Browser APIs dominate the design. A background script cannot record media. A side panel does not grant tab access the way a toolbar popup does. Hidden tabs go black. Those constraints decided the architecture more than any UI idea.

What still breaks

Cross-origin iframes are a separate document. If the real page lives in a frame, auto-scroll may move the wrong thing or nothing useful.

If you switch away from the tab, the capture can go black. There is a warning. There is no recovery.

Horizontal pages are out of scope. The driver moves on the vertical axis.

Complex WebGL and custom-scroll scenes with no real scrollbar do not auto-finish. You stop them by hand. Cinema mode will not reconstruct hover, video, or a running canvas from a frozen map.

I do not claim this works on every website. The difficult pages are why the work exists.

Feedback welcome

If you have fought sticky headers in stitched screenshots, custom scroll fighting a programmatic move, or a recording that does not match what you saw on screen, I want those cases.

I turned this experiment into ScrollFlow, a Chrome extension for creating local auto-scroll captures of websites. It is free to try, and I’d genuinely value feedback from developers who work with difficult pages.

Top comments (0)