ScreenshotNeo

BlogHow-to

How to Capture a Website Page with a Sticky Footer for Documentation

Choose a viewport or full-page capture based on what the record needs to show, then verify the footer, lazy-loaded content, and final output.

By the ScreenshotNeo team4 October 202610 min read

For documentation that must show the footer as a visitor sees it, capture a viewport screenshot while the footer is visible. For a record of the entire scrollable document, use a full-page screenshot and inspect the result: a long-page capture does not guarantee that a fixed or sticky footer will appear exactly once in the intended position. If the record should focus on the footer component itself, capture that element. Use a PDF when you need pages, but verify it because print styles can change the layout.

This guide uses Playwright for the do-it-yourself workflow. Its screenshot API supports viewport, element, and full-page captures, and its PDF API has separate print-media behavior. Playwright’s screenshot guide and Page API reference describe those options.

1. Choose what the documentation needs to show

Record goal Capture mode What to verify
Show how the page looks with the footer on screen Viewport screenshot, with the footer in view The footer is legible and the surrounding page context is sufficient
Record the whole scrollable document Full-page screenshot The footer’s position, overlays, page completeness, and image loading
Document just the footer or a related component Element screenshot The element boundary includes all desired content and context
Produce a paginated artifact PDF Print CSS, page breaks, repeated fixed content, and readability

A normal screenshot captures the current viewport. In Playwright, fullPage: true asks for the full scrollable page, as if it fit on a very tall screen. That is a different visual record from a visitor’s viewport. A viewport image is usually the clearest evidence when the requirement is specifically “preserve the footer as visible on screen.”

Inspect the footer in browser developer tools. Check its computed position, the relevant inset such as bottom, and its ancestors’ overflow settings.

  • position: fixed generally positions the element relative to the viewport in visual media and removes it from normal document flow. It may cover content while scrolling.
  • position: sticky behaves like normal flow until it reaches an inset threshold, then sticks relative to its nearest scrolling ancestor and containing block. An ancestor with overflow: hidden, auto, or scroll can change which container governs the sticky behavior. A non-auto inset on the relevant axis is needed for sticking.

See MDN’s CSS position reference for the positioning rules. These distinctions help explain why the footer can look different in a viewport, a tall full-page image, and printed pages. Neither the screenshot API nor the CSS rules guarantee a particular footer result for every site and browser implementation, so inspect the actual saved file.

3. Prepare the page and capture with Playwright

The following Node.js script opens a page, scrolls through it to encourage below-the-fold lazy images to load, returns to the top, scrolls to the footer, and saves both a viewport image and a full-page image. It also saves a focused element image if a footer selector is available. Install Playwright and its Chromium browser before running:

npm install playwright
npx playwright install chromium

Save this as capture.mjs and run node capture.mjs https://example.com. Replace the URL and, if needed, the footer selector.

import { chromium } from 'playwright';

const url = process.argv[2];
if (!url) {
  throw new Error('Usage: node capture.mjs https://example.com');
}

const browser = await chromium.launch({ headless: true });
const page = await browser.newPage({
  viewport: { width: 1440, height: 1000 },
  deviceScaleFactor: 1,
});

try {
  await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 60_000 });
  await page.locator('body').waitFor({ state: 'visible' });

  // Scroll in viewport-sized steps so lazy-loaded content can approach view.
  await page.evaluate(async () => {
    const step = Math.max(300, Math.floor(window.innerHeight * 0.8));
    for (let y = 0; y < document.documentElement.scrollHeight; y += step) {
      window.scrollTo(0, y);
      await new Promise(resolve => setTimeout(resolve, 150));
    }
    window.scrollTo(0, 0);
  });

  // Let layout and images settle after the final scroll.
  await page.waitForTimeout(500);
  await page.screenshot({ path: 'page-viewport.png' });
  await page.screenshot({ path: 'page-full.png', fullPage: true });

  const footer = page.locator('footer').first();
  if (await footer.count()) {
    await footer.screenshot({ path: 'footer-element.png' });
  }
} finally {
  await browser.close();
}

This is a practical starting point, not a claim that a fixed or sticky footer will be rendered identically by every browser or site. Open each output at full size. If the documentation needs the footer visible in its on-screen state, make that viewport capture the primary attachment. If the script’s simple scroll loop is not enough for a site with infinite scrolling or custom scroll containers, scroll the relevant container and wait for its content explicitly.

Capture one mode at a time

The core Playwright calls are:

// Current viewport (first scroll the footer into view if required)
await page.screenshot({ path: 'viewport.png' });

// Entire scrollable document
await page.screenshot({ path: 'full-page.png', fullPage: true });

// A selected element
await page.locator('footer').screenshot({ path: 'footer.png' });

Use a selector that identifies the real footer. A site may use a div rather than a semantic <footer> element. For an element screenshot, the element must be present and visible; if there are multiple matches, choose the intended one explicitly.

4. Set capture options for the record

Playwright screenshot options let you control the image’s scope and output. Choose only the settings relevant to the documentation:

Option Use Consideration
fullPage: true Capture the full scrollable document Inspect footer placement and total image dimensions afterward
clip: { x, y, width, height } Capture a defined rectangle Coordinates are page screenshot coordinates; confirm the crop includes context
scale: 'css' One output pixel per CSS pixel Often yields a smaller artifact
scale: 'device' Use device pixel ratio for a higher-density image Can make a long page file larger; useful when fine detail must remain readable
type: 'png' | 'jpeg' Choose a lossless or compact raster format PNG preserves sharp text and edges; JPEG may be smaller but is lossy
quality Set compression quality for JPEG Applies to JPEG output, not PNG
omitBackground: true Keep transparent pixels where supported Usually unnecessary for a documentation page
animations: 'disabled' Prevent animations from changing the frame during capture Confirm the resulting static state is the one you need
caret: 'hide' Hide a blinking text caret Useful for stable documentation screenshots
mask and maskColor Cover selected dynamic elements Use only when the covered region does not remove evidence needed by the record

For a controlled viewport with the footer actually visible, scroll it into view and capture the viewport. For example:

const footer = page.locator('footer').first();
await footer.scrollIntoViewIfNeeded();
await page.waitForTimeout(200);
await page.screenshot({ path: 'footer-visible-viewport.png', scale: 'css' });

scrollIntoViewIfNeeded() aligns an element as needed for visibility; it does not promise that a sticky footer is at the precise desired viewport position. If you need a specific composition, adjust the scroll position and inspect the resulting image.

5. Handle lazy content and dynamic pages

A page’s load event alone does not establish that every image lower on a long page has loaded. MDN explains that images with loading="lazy" are deferred until they approach the viewport. Scrolling the document before capture can trigger them, but custom lazy-loading code, nested scroll areas, network delays, and infinite lists may need additional handling. See MDN’s image element reference.

For a page with a known lazy image set, you can wait for image loading and decoding after scrolling. This example waits for images currently in the document; it does not make an infinite list finite or guarantee a third-party widget has settled:

await page.evaluate(async () => {
  const images = [...document.images];
  await Promise.all(images.map(async image => {
    if (!image.complete) {
      await new Promise(resolve => {
        image.addEventListener('load', resolve, { once: true });
        image.addEventListener('error', resolve, { once: true });
      });
    }
    if (image.decode) {
      try { await image.decode(); } catch { /* failed images are checked in review */ }
    }
  }));
});

Use a deliberate readiness condition for pages that render asynchronously, such as waiting for a specific content selector or a known application state. Avoid treating networkidle as a universal guarantee: analytics, polling, or long-lived requests can keep a page active, while a quiet network does not prove that all visual content is correct.

6. Use PDF when the record should be paginated

Playwright’s page.pdf() renders using print CSS by default. A site’s @media print rules can hide or restyle elements, and fixed content can repeat on printed pages. If the purpose calls for a screen-style PDF, emulate screen media before exporting, then compare the PDF with the intended record. MDN documents print styles and page settings in its printing guide.

// Print-oriented PDF (Playwright's default media behavior)
await page.pdf({ path: 'page-print.pdf', format: 'A4', printBackground: true });

// Screen-style PDF
await page.emulateMedia({ media: 'screen' });
await page.pdf({ path: 'page-screen.pdf', format: 'A4', printBackground: true });

PDF options include format or explicit width and height, margins, landscape orientation, background printing, page ranges, and scaling. Use page ranges when a large document only needs a subset. A PDF is useful for pagination and printing; it is not automatically a faithful substitute for the viewport state or the long screenshot.

7. Verify the artifact before attaching it

  1. Open the saved file, not only the live browser page.
  2. Confirm the capture mode matches the record: visible viewport, full document, element, or paginated PDF.
  3. Check that the footer is legible and appears in the intended position. Look for duplication, overlap, clipping, or unexpected gaps.
  4. Inspect the whole page for missing lazy images, loading placeholders, partially rendered charts, and content below the fold.
  5. Confirm the crop and scale keep relevant text readable at the size at which the documentation will be viewed.
  6. For PDF, check print-specific changes and page breaks separately from the on-screen page.

If the full-page image does not preserve the required footer state, include a separate viewport screenshot with the footer visible. The two images document different things: the full content and the visible browser state.

Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server for developers. One GET request can return an image or PDF. For the cleanest documentation capture, first use a viewport capture when the footer must appear as it does on screen; choose full-page when you need the whole scrollable document.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

See the ScreenshotNeo API documentation for request parameters, including capture configuration. Cookie banners, newsletter popups, and chat widgets are removed before the shot; each cleanup step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers report the page verdict and billing status. ScreenshotNeo also provides an MCP server so AI agents can take screenshots. The Free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots.

Sign up free for 1,000 screenshots a month, with no card required.

Troubleshooting

Symptom Likely cause Fix
Footer is missing from the screenshot The capture is a viewport image taken before scrolling, or the selector is wrong Scroll the intended footer into view for a viewport capture; confirm the selector matches an element
Footer appears more than once or covers content Fixed positioning interacts with full-page rendering or print pagination Use a viewport image for the visible state; inspect the full-page image or PDF and document the modes separately if needed
Sticky footer does not stick where expected A scrolling ancestor, containing block, or missing inset changes the sticky behavior Inspect computed position, inset values, and ancestor overflow in developer tools
Images are blank or placeholders remain Lazy loading, failed image requests, or app rendering is still in progress Scroll through the relevant container, wait for known visual content, and inspect image load failures
Playwright times out at navigation The page is slow, waits on a long-lived resource, or blocks automation Use a suitable navigation condition such as domcontentloaded, set a considered timeout, and wait for the specific content needed
Footer selector matches nothing The site uses a different tag or class, or the footer is inside a frame Inspect the DOM, select the actual element, and handle the relevant frame explicitly
PDF footer differs from browser image PDF generation uses print media by default Inspect print CSS; emulate screen media if that matches the record’s purpose, then verify pagination
Text is too small or file is unwieldy Capture scale or full-page dimensions are not suited to the use Use CSS scale for one pixel per CSS pixel, crop to relevant context, or choose a viewport/PDF artifact instead

Performance, reliability, and cost

Long pages take more work to render and produce taller files. Device-pixel scale increases output pixel density and can increase file size; CSS scale keeps output aligned to CSS pixels. Element or clipped captures reduce irrelevant area. JPEG may reduce file size at the expense of image quality; PNG retains crisp edges but can be larger. These are practical trade-offs, not fixed size or speed guarantees.

For repeatable evidence, keep the viewport dimensions, device scale, wait conditions, and capture mode consistent. Save the URL and capture time alongside the artifact if your documentation process needs provenance. Dynamic ads, rotating content, personalization, authentication, and third-party resources can change between captures; use appropriate test data and review each output. A successful browser call does not itself prove that the resulting page is complete.

With ScreenshotNeo, caching can be configured with a chosen TTL, and the API exposes usage information. Its billing model charges only clean shots; page verdict and billed status are indicated in response headers. Consult the docs for current request options and response behavior rather than assuming an unsuccessful visual result was billed.

FAQ

Should I attach both viewport and full-page images?

Yes, when reviewers need both the complete document and proof of how the footer appears on screen. Label what each image records.

Yes, if the documentation needs the footer component itself. An element capture does not show the surrounding viewport context, so use a viewport image when that context matters.

Is a PDF the best format for a long page?

Use PDF when pagination, printing, or page references matter. Use an image when the record must show the browser’s visual state, and check print styles before relying on PDF output.

Does a successful full-page call prove all content loaded?

No. Check deferred images and dynamic content in the actual output; a page load event alone may occur before lazy images below the fold are requested.

Technical references: Playwright Screenshots, Playwright Page API, MDN: position, MDN: img, and MDN: Printing.