ScreenshotNeo

BlogHow-to

How Many Scroll Steps Should a Screenshot Script Use on a Long Page?

There is no universal scroll count. Use full-page capture for one image, or calculate overlapping viewport steps from the page’s live height.

By the ScreenshotNeo team4 October 20268 min read

There is no universal number of scroll steps for a long-page screenshot. If you need one image of the whole page, use your browser automation framework’s full-page screenshot option when it works for the target site. If you need separate viewport images, measure the page and viewport at runtime, choose an overlap, then calculate the positions. Always capture the bottom edge too: the last movement may be shorter than a regular step.

Playwright documents fullPage: true as a way to capture the full scrollable page, and Puppeteer offers a full-page screenshot option as well. See the Playwright screenshots guide and Puppeteer screenshots guide.

1. Choose full-page capture or separate viewport shots

Approach Use it when What to watch
Full-page screenshot You need one image of the complete scrollable document. Very tall output dimensions, page behavior, lazy-loaded content, and downstream image handling.
Manual overlapping viewport captures You need individual segments, full-page capture is unsuitable, or scrolling must reveal content. Viewport size, overlap, final bottom position, repeated or sticky elements, and stitching seams.

Try full-page capture first for a single-image deliverable. It removes the need to guess a scroll count. However, do not assume that taking a full-page screenshot triggers every site’s scroll-triggered loading behavior. Inspect the output on the target page; if important content appears only after scrolling, scroll through the page and wait for it before the final capture.

2. Calculate the number of manual scroll steps

Let:

  • H = scrollable page height in CSS pixels
  • V = viewport height in CSS pixels
  • o = overlap fraction, between 0 and less than 1

Set the scroll increment to S = V × (1 − o). A useful starting estimate is ceil(H ÷ S) captures. This is arithmetic guidance, not a framework-prescribed count or overlap. Make overlap configurable and tune it for your page and stitching workflow.

For example, with a 900-pixel viewport and 20% overlap, the increment is 720 pixels. A 5,000-pixel page needs ceil(5000 ÷ 720) = 7 captures as a starting estimate. In practice, build the positions so the final capture is bottom-aligned. Otherwise the last strip can be missed or the final image can duplicate too much content.

The calculation is for viewport captures covering the document from top to bottom. It assumes the document height is stable during capture. If content expands as you scroll, remeasure the height as you go and recalculate the bottom position.

3. Runnable Playwright example in JavaScript

Install Playwright and its Chromium browser, save this as capture.mjs, then run it with a target URL. It writes numbered viewport PNGs to the current directory. Set OVERLAP as a fraction from 0 up to (but not including) 1.

import { chromium } from 'playwright';

const targetUrl = process.argv[2] ?? 'https://example.com';
const overlap = Number(process.env.OVERLAP ?? 0.2);

if (!Number.isFinite(overlap) || overlap < 0 || overlap >= 1) {
  throw new Error('OVERLAP must be a number from 0 (inclusive) to 1 (exclusive)');
}

const browser = await chromium.launch({ headless: true });
try {
  const page = await browser.newPage({ viewport: { width: 1280, height: 900 } });
  await page.goto(targetUrl, { waitUntil: 'domcontentloaded', timeout: 60000 });

  // Scroll through the document so scroll-triggered content has a chance to load.
  let previousHeight = 0;
  for (let pass = 0; pass < 10; pass++) {
    const height = await page.evaluate(() =>
      Math.max(document.body.scrollHeight, document.documentElement.scrollHeight)
    );
    if (height === previousHeight) break;
    previousHeight = height;
    await page.evaluate(() => window.scrollTo(0, document.documentElement.scrollHeight));
    await page.waitForTimeout(250);
  }

  const { pageHeight, viewportHeight } = await page.evaluate(() => ({
    pageHeight: Math.max(document.body.scrollHeight, document.documentElement.scrollHeight),
    viewportHeight: window.innerHeight,
  }));
  const increment = Math.max(1, Math.floor(viewportHeight * (1 - overlap)));
  const maxTop = Math.max(0, pageHeight - viewportHeight);
  const positions = [...new Set([
    ...Array.from({ length: Math.ceil(pageHeight / increment) }, (_, i) => i * increment),
    maxTop,
  ])].filter((top) => top <= maxTop).sort((a, b) => a - b);

  for (let i = 0; i < positions.length; i++) {
    await page.evaluate((top) => window.scrollTo(0, top), positions[i]);
    await page.waitForTimeout(150); // allow scroll-driven layout/animations to settle
    await page.screenshot({ path: `segment-${String(i + 1).padStart(3, '0')}.png` });
  }
  console.log(`Saved ${positions.length} segments; page height ${pageHeight}px, step ${increment}px.`);
} finally {
  await browser.close();
}

Install and run:

npm install playwright
npx playwright install chromium
node capture.mjs https://example.com

This example deliberately scrolls to the bottom before measuring so common lazy-load patterns have an opportunity to add content. It caps that warm-up at ten passes to avoid endlessly growing feeds. Raise or replace the cap if the site’s behavior requires it. If you need deterministic image dimensions, wait for a page-specific selector or loading signal before capture rather than relying only on a short delay.

4. Puppeteer and one-image alternatives

For one full-page file, the concise Playwright version is:

import { chromium } from 'playwright';

const browser = await chromium.launch({ headless: true });
try {
  const page = await browser.newPage({ viewport: { width: 1280, height: 900 } });
  await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
  await page.screenshot({ path: 'full-page.png', fullPage: true });
} finally {
  await browser.close();
}

Puppeteer’s equivalent uses fullPage: true as a screenshot option:

import puppeteer from 'puppeteer';

const browser = await puppeteer.launch({ headless: true });
try {
  const page = await browser.newPage();
  await page.setViewport({ width: 1280, height: 900 });
  await page.goto('https://example.com', { waitUntil: 'domcontentloaded', timeout: 60000 });
  await page.screenshot({ path: 'full-page.png', fullPage: true });
} finally {
  await browser.close();
}

For manual Puppeteer segments, apply the same calculation: read document.documentElement.scrollHeight and window.innerHeight with page.evaluate, compute Math.ceil(viewportHeight × (1 − overlap)), and capture at each position plus pageHeight − viewportHeight. Puppeteer and Playwright both let you configure viewport dimensions; the exact height in use is therefore part of the calculation. See the Playwright Page API.

5. Set overlap and capture options deliberately

  • Overlap: Choose it based on content and stitching. More overlap makes adjacent segments easier to align but increases duplicate pixels and capture count. There is no authoritative universal percentage.
  • Viewport height: Use the actual browser viewport height, not a guessed monitor size. A smaller viewport means more segments.
  • Bottom alignment: Include max(0, pageHeight − viewportHeight) as the final scroll position. Deduplicate positions when the page is shorter than one viewport or the regular increment lands at the bottom already.
  • Output size: A full-page screenshot can be extremely tall. Prefer segments if the consuming system has image dimension, memory, upload, or review constraints.
  • Page growth: Infinite scroll and delayed content change the height during capture. Define a stopping condition such as a known item count, end marker, stable height for several passes, or a maximum scroll distance.
  • Sticky UI: Fixed headers and floating controls can appear in every segment. Hide them for capture or account for them during stitching if the task allows it.
  • Animation: Moving content can create inconsistent segments. Wait for a stable state or disable animations when appropriate for your capture task.

6. Python and cURL when a hosted capture API fits

For DIY browser automation in Python, the same rule applies: read the current document and viewport dimensions, calculate the increment, scroll through for lazy content, and capture the bottom-aligned position. The browser API determines how to save and combine each segment. If you only need a single full-page image, use a browser library’s full-page option where supported.

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server. A single request returns an image or PDF; for this task, request full-page capture rather than manually choosing a scroll count. See the ScreenshotNeo API documentation.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com --data-urlencode full_page=true -o shot.webp
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com", "full_page": "true"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({
  access_key: 'YOUR_API_KEY',
  url: 'https://stripe.com',
  full_page: 'true',
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer())));

ScreenshotNeo removes cookie and consent banners, newsletter popups, and chat widgets before capture. Bot checks, blank pages, failed loads, and cache hits are not billed. Its MCP server lets AI agents use screenshot tools. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000. The same options can also be used for custom viewport captures if you need a particular layout. Create a free account and get 1,000 screenshots a month with no card.

7. Troubleshooting

Symptom Likely cause Fix
The bottom of the page is missing. The script used only regular increments and stopped before the maximum scroll position. Add max(0, pageHeight − viewportHeight) explicitly and capture it.
There are gaps between stitched segments. The increment is greater than the visible area or page geometry changed. Reduce the increment by increasing overlap; remeasure page height during dynamic pages.
Content is duplicated too much. Overlap is larger than needed or positions are not deduplicated. Reduce overlap and deduplicate positions. Keep enough overlap to align reliably.
Images or cards are blank below the fold. Lazy loading or scroll-triggered rendering has not completed. Scroll through first, wait for the relevant selector or image load state, then measure and capture.
The output is enormous or screenshot capture fails. A full-page bitmap exceeds practical image or memory limits for the workflow. Capture viewport segments or target a smaller viewport/output size.
Segment seams do not line up. Sticky elements, animation, changing content, or layout shifts altered the page between captures. Wait for stable layout, suppress animation where suitable, handle fixed elements consistently, and avoid changing page state between shots.
The script keeps scrolling forever. An infinite feed keeps adding content whenever the bottom is reached. Use an explicit item count or end marker, impose a maximum number of passes, or stop when height is stable for several checks.
The measured height seems wrong. The page uses a nested scroll container instead of document scrolling. Measure and scroll the container element that actually owns the content, then capture its region or page state appropriately.

8. Performance, reliability, and cost

Manual segmentation takes roughly one screenshot per calculated position, plus time spent scrolling and waiting for content. Increasing overlap raises the number of captures; increasing viewport height usually lowers it. Full-page mode avoids application-level step calculation, but very tall output can still cost more memory and processing in your own pipeline. Those are operational tradeoffs, not universal timing benchmarks.

For reliable automation, base positions on runtime measurements, use a predictable viewport, wait for a page-specific ready condition, and log the measured page height, viewport height, overlap, increment, and positions. Recheck height on pages whose content expands during scrolling, and put a bound on infinite-load loops. Neither the documentation cited here nor the formula implies one universally reliable overlap or wait duration.

With a self-hosted browser, account for browser runtime and compute in your own environment. With ScreenshotNeo, only clean screenshots are billed; response headers identify the page verdict and billing status. Its plans range from 1,000 monthly free screenshots to paid tiers, and all features are on every plan. For current options and plan details, consult the documentation and ScreenshotNeo.

FAQ

Should I use 5, 10, or 20 scrolls?

None is a reliable default. The page height and viewport determine the number; measure both for each run.

Is 20% overlap the right amount?

It is only an example starting value. Choose overlap based on the page and how you will align or inspect segments.

Does full-page capture load every lazy image?

Do not assume so. Verify the target page’s output, and scroll through and wait for content that appears only on scroll.

Can I screenshot only one section?

Yes. If the requirement is a single element rather than the whole document, capture that element directly instead of scrolling through the page.