# Full-page screenshots that are actually complete: lazy loading, sticky headers, 100vh layouts and emoji

> Source: <https://dev.to/tobby_2dda7b04f35d304b323/full-page-screenshots-that-are-actually-complete-lazy-loading-sticky-headers-100vh-layouts-and-4aj>
> Published: 2026-10-18 19:10:53+00:00

*Disclosure: I build and sell one of the three screenshot tools compared below, a hosted screenshot Actor on Apify. This article was written with the help of AI; the numbers come from the public benchmark data linked at the end, and everything here works in a plain Playwright or Puppeteer script.*

`page.screenshot({ fullPage: true })` looks done until you look at the output: blank rectangles where lazy images should be, the sticky header stamped down the page, a cookie banner across the middle, and emoji as tofu boxes. Here is what broke when I captured 30 real sites, and what fixed it.

Images below the fold load only when they come close to the viewport. Scroll the whole page in steps, wait for pending images, then go back to the top before capturing:

``` js
await page.evaluate(async () => {
  const step = window.innerHeight;
  const max = Math.min(document.documentElement.scrollHeight, 30_000);
  for (let y = 0; y < max; y += step) {
    window.scrollTo(0, y);
    await new Promise((r) => setTimeout(r, 150));
  }
  window.scrollTo(0, 0);
});
await page
  .waitForFunction(() => [...document.images].every((i) => i.complete), null, { timeout: 10_000 })
  .catch(() => {}); // a broken image should not fail the whole capture
```

Cap the scrolling (I stop at 30,000 px) or infinite feeds will scroll forever, and *record* that you hit the cap, so nobody mistakes a truncated page for a complete one.

Fixed headers repeat on every slice of a stitched screenshot, and fixed layers get painted in the wrong place. What worked for me:

`position: sticky` → force `position: relative`. At scroll position 0 the element sits in its natural spot anyway.`fixed` bar at the top of the viewport → switch it to `absolute` at its current coordinates, so it appears once, at the top.`z-index`) → hide them.
Chrome can capture beyond the viewport, but on Wikipedia's long *Emoji* article that took about **14 seconds per slice**. So by default I resize the viewport to the page height (in slices of up to 8,000 px) and capture what is on screen.

That breaks on layouts built from `100vh` sections: make the viewport 8,000 px tall and a "full-screen" hero becomes 8,000 px tall too. When the page height changes on resize, I fall back to scrolling a normal-size viewport and stitching the slices. Pages where `body`, not the window, is the scrolling element (getemoji.com is one) need yet another path.

Here is the bottom half of vercel.com, which uses that kind of layout, captured by three tools (left to right: mine, apify/screenshot-url, cheapapi/website-screenshot-api). The middle one repeats the fixed hero over the footer; the right one is blank in the lower half:

Slim Docker images have no colour-emoji font, so 😀 becomes ☐, and Japanese or Chinese text turns into boxes. Two Debian packages fix it:

```
RUN apt-get update \
 && apt-get install -y --no-install-recommends fonts-noto-color-emoji fonts-noto-cjk \
 && fc-cache -f
```

Consent banners can be dismissed: block the consent manager's script, or click its "accept" button before capturing. CAPTCHAs are different. I don't try to bypass them; I detect the challenge page and report an error instead of a picture of it. (Careful: many normal pages load an *invisible* reCAPTCHA badge. Only treat a visible challenge as a block, or you will flag perfectly good pages. I learned that one this week.)

The same 30 pages (Stripe, The Guardian, BBC, Wikipedia, Vercel, getemoji, Japanese Wikipedia, Spiegel, IKEA and others), one run per tool on 18 Oct 2026:

|  | mine | apify/screenshot-url | cheapapi | 
|---|---|---|---|
| Complete and visually correct | 24/30 | 19/30 | 18/30 | 
| Full page captured (28 reachable pages) | 27/28 | 25/28 | 21/28 | 
| Bot-blocked pages (medium.com, unsplash.com) | 2 errors, nothing billed | 1 challenge-page image, 1 no output | 2 challenge-page images | 

Caveats: one run each, with an earlier build of mine; 13 of the 30 rows were judged on captured height vs. true page height only, without visual inspection; the apify run was stopped at 29/30 for budget. Still imperfect in mine: booking.com shows grey skeleton placeholders, and The Verge has an empty ad band.

Per-site verdicts, run IDs, heights and the URL list are in the benchmark folder: [github.com/tobbytools/website-screenshot-examples/tree/main/benchmark](https://github.com/tobbytools/website-screenshot-examples/tree/main/benchmark). The repo also has MIT-licensed calling examples for my tool.

**Which sites break your screenshots?** Drop them in the comments and I'll add them to the next test run.
