October DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsClean PCRecommendedOne scan can reveal what keeps slowing WindowsLook for cleanup and repair opportunities.Run ScanOctober DealsAmazon USDeal season is back - check today's better picksAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
CSS sticky

Why a Sticky Navbar Appears in the Middle of a Puppeteer Full-Page Screenshot (and How to Fix It)

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Short answer: Puppeteer’s fullPage: true captures the document’s full extent; it does not convert position: sticky or position: fixed elements into ordinary document-flow content. During capture, the navbar still follows its CSS scrolling ancestor and containing block, so a single tall image can show it at a position that looks like the middle of the page. Inspect the navbar’s computed position and ancestor overflow first, then choose between a normal viewport capture, a capture-only CSS override, or a version-specific screenshot option experiment.

What fullPage actually does

Puppeteer’s ScreenshotOptions reference defines fullPage as taking a screenshot of the full page. That describes the capture extent, not the layout behavior of every element in the image. CSS is still applied while Chromium lays out the page.

A sticky element behaves like a relatively positioned element until its inset threshold (often top: 0) is reached. It then sticks within the relevant containing block and scrolling ancestor. A fixed element is attached to the viewport. When Puppeteer produces one tall image from a page whose layout was designed for scrolling, the navbar can therefore appear at an unexpected vertical location, or appear more than once in stitched output on affected browser versions.

This is not automatically a Puppeteer defect. The result depends on the navbar’s CSS, ancestor overflow, the page’s scroll container, and the Puppeteer/Chromium versions in use.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Diagnose the navbar before changing the screenshot

1. Read the computed position and inset

Use page.evaluate to inspect the actual element after all styles have loaded. Check both position and inset values such as top, bottom, and z-index.

const info = await page.evaluate(() => {
  const el = document.querySelector('nav');
  if (!el) return { error: 'nav not found' };
  const style = getComputedStyle(el);
  const rect = el.getBoundingClientRect();
  return {
    position: style.position,
    top: style.top,
    bottom: style.bottom,
    zIndex: style.zIndex,
    rectTop: rect.top,
    rectHeight: rect.height,
    className: el.className
  };
});
console.log(info);

Run this after navigation and any application hydration. A value of sticky or fixed confirms that the navbar is intentionally outside normal-flow behavior once its threshold is reached.

2. Walk up the ancestor chain

Sticky positioning is governed by the nearest scrolling ancestor, not necessarily by the element that visibly moves when you drag the page. An ancestor with overflow: hidden, scroll, auto, or overlay can establish that scrolling mechanism. Inspect every ancestor and identify the element whose scrollHeight exceeds its clientHeight.

const ancestors = await page.evaluate(() => {
  const el = document.querySelector('nav');
  const rows = [];
  for (let node = el; node; node = node.parentElement) {
    const s = getComputedStyle(node);
    rows.push({
      tag: node.tagName,
      id: node.id,
      className: node.className,
      overflow: s.overflow,
      overflowX: s.overflowX,
      overflowY: s.overflowY,
      scrollHeight: node.scrollHeight,
      clientHeight: node.clientHeight
    });
  }
  return rows;
});
console.table(ancestors);

Pay particular attention to an application shell such as .app, main, or a modal layout that owns scrolling while the document itself remains at a different scroll position.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

3. Check the page at a normal viewport

Before testing full-page capture, take an ordinary viewport screenshot. If the navbar looks correct there, the discrepancy is caused by the interaction between full-document capture and the page’s scrolling/layout model rather than by the navbar’s basic styling.

await page.screenshot({ path: 'viewport.png' });

Choose the output you actually need

When you need the current browser view

If the requirement is “what a user currently sees,” do not use a full-page image. Set the intended viewport and capture it normally:

await page.setViewport({ width: 1440, height: 900, deviceScaleFactor: 1 });
await page.goto('https://example.com', { waitUntil: 'networkidle2' });
await page.screenshot({ path: 'viewport.png' });

This preserves sticky behavior exactly as it appears to a visitor.

When you need one static long image

For documentation, visual regression fixtures, or a page preview, temporarily neutralize the navbar’s sticky/fixed behavior only while capturing. Setting position: static returns it to normal flow; clearing the relevant inset prevents a leftover offset. The override can change layout, so inspect the output and remove the style after the screenshot.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
await page.addStyleTag({ content: `
  nav,
  .site-nav,
  [data-sticky-nav] {
    position: static !important;
    top: auto !important;
    right: auto !important;
    bottom: auto !important;
    left: auto !important;
  }
` });
await page.screenshot({ path: 'full-static.png', fullPage: true });

Replace the selectors with the actual navbar selector. If the navbar is fixed and removed from flow, making it static may move content upward or downward; add a capture-only spacer if the design requires the original reserved height.

Rank #2
Sale
HTML and CSS: Design and Build Websites
  • HTML CSS Design and Build Web Sites
  • Comes with secure packaging
  • It can be a gift option

When an overflow container, not the document, scrolls

If the page uses a fixed-height shell with overflow: auto, fullPage may not represent that shell’s complete scrollable content. Capture the scrolling element itself or temporarily remove the container’s height/overflow for a dedicated static render. Do this only for the capture state, because changing the container can alter responsive breakpoints and lazy-loaded content.

A complete Puppeteer script

The following script records the diagnosis, waits for the page to settle, applies an optional capture-only override, and writes both a viewport image and a static full-page image. Install Puppeteer with npm install puppeteer, then run it with Node.js.

const puppeteer = require('puppeteer');

(async () => {
  const url = process.argv[2] || 'https://example.com';
  const browser = await puppeteer.launch({ headless: true });
  const page = await browser.newPage();
  await page.setViewport({ width: 1440, height: 900, deviceScaleFactor: 1 });

  await page.goto(url, { waitUntil: 'networkidle2', timeout: 90000 });
  await page.screenshot({ path: 'viewport.png' });

  const diagnosis = await page.evaluate(() => {
    const nav = document.querySelector('nav, .site-nav, [data-sticky-nav]');
    if (!nav) return { error: 'No navbar selector matched' };
    const s = getComputedStyle(nav);
    return { position: s.position, top: s.top, overflow: s.overflow };
  });
  console.log('Navbar:', diagnosis);

  await page.addStyleTag({ content: `
    nav, .site-nav, [data-sticky-nav] {
      position: static !important;
      top: auto !important;
      right: auto !important;
      bottom: auto !important;
      left: auto !important;
    }
  ` });

  await page.screenshot({ path: 'full-static.png', fullPage: true });
  await browser.close();
})();

If you need the page’s original sticky behavior in the long image, remove the addStyleTag block and test the result against your Puppeteer and Chromium versions. Do not assume that a visually surprising placement means the script failed.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

What captureBeyondViewport can and cannot tell you

captureBeyondViewport is a separate screenshot option. In the current Puppeteer reference, its default is false when there is no clip and true otherwise. It controls capture scope; the reference does not describe it as a universal sticky-position fix.

A 2021 issue participant reported that setting it to false solved a particular case. Treat that as a targeted experiment, not a rule for every current Puppeteer/Chromium combination:

await page.screenshot({
  path: 'experiment.png',
  fullPage: true,
  captureBeyondViewport: false
});

Compare this output with the default, record the exact Puppeteer and Chromium versions, and keep the setting only if it produces the required image without clipping content.

Why viewport resizing often makes things worse

A common workaround is to measure document.body.scrollHeight, resize the viewport to that height, and capture. This can change layouts that use vh, media queries, or viewport-bound fixed elements. A maintainer discussion about historical full-page captures notes that resizing can place fixed or sticky elements incorrectly. Prefer a normal viewport capture or a capture-only CSS override unless you have verified the resized layout at your target pages.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Troubleshooting common failures

The navbar appears in the middle only with fullPage: true

Confirm that its computed position is sticky or fixed. Capture a normal viewport for a faithful browser view, or apply the static override for a long document image.

The override does nothing

Your selector may not match the rendered element, or a shadow DOM/component stylesheet may own the navbar. Log document.querySelector, target the actual host element, and inspect computed styles after the page has hydrated. An ancestor may also be applying a transform or clipping the element.

The navbar disappears after setting position: static

Fixed elements are removed from normal flow, so the original design may have relied on padding or an explicit spacer. Add a temporary block with the navbar’s height, or apply equivalent top padding to the content in the capture stylesheet.

Rank #4
Sale
Web Design with HTML, CSS, JavaScript and jQuery Set
  • Brand: Wiley
  • Set of 2 Volumes
  • A handy two-book set that uniquely combines related technologies Highly visual format and accessible language makes these books highly effective learning tools Perfect for beginning web designers and front-end developers

The page is clipped or only the shell is captured

Find the real scrolling element. If an ancestor has a constrained height and overflow: auto, document-level fullPage does not automatically mean that nested scroll region is fully rendered. Capture or expand that container deliberately.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Images or content are missing

Wait for the relevant selector, lazy-load images before capture, or wait for network idle plus an application-specific readiness signal. A screenshot option cannot repair content that has not been rendered.

Behavior changed after an upgrade

Record Puppeteer and Chromium versions and reduce the case to a small page. Historical discussions include changes associated with Puppeteer 2.0.0 and upstream Chromium, so older reports should not be treated as current universal bugs.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Performance and reliability considerations

  • Use a realistic viewport: it keeps responsive breakpoints and vh-based calculations meaningful.
  • Wait for layout stability: capture after fonts, images, and client-side rendering have completed; otherwise the navbar’s threshold can move during capture.
  • Limit page-side mutations: inject the static stylesheet only for the capture and use a fresh page for the unmodified version.
  • Keep diagnostics in CI: log the URL, selector, computed position, scroll container, Puppeteer version, and Chromium revision when a screenshot differs.
  • Test representative layouts: desktop, mobile, nested scrolling shells, and pages with consent dialogs can follow different positioning rules.

Or skip the browser setup

If you only need a clean website image or PDF, ScreenshotNeo provides a single HTTP request instead of maintaining a Puppeteer browser. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be disabled. Bot checks/CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing result. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients.

For API parameters and all options, see the ScreenshotNeo documentation. A basic call is:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

The same request in Python:

import requests
r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)

And in Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
require('fs').writeFileSync('shot.webp', Buffer.from(await res.arrayBuffer()));

ScreenshotNeo supports full-page captures, CSS-selector element captures, custom CSS and JavaScript, waits for selectors/delays/network idle, device presets or custom viewports, dark mode, retina scale, request blocking, cookies and headers, caching, signed links, PDFs, asynchronous jobs, bulk capture of up to 100 URLs per call, and usage reporting. Every feature is included on every plan. The Free plan includes 1,000 shots each month without a card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account to try it.

FAQ

Is this always a Puppeteer bug?

No. CSS sticky/fixed rules and ancestor scrolling determine the layout; historical browser issues exist, but current behavior must be checked with your versions and page structure.

Should I always disable sticky positioning?

No. Disable it only when the deliverable is a static long image. Keep it for a viewport screenshot that should match what users see.

Does fullPage include nested scrollable panels?

Not automatically. A nested element with its own overflow and height may need a separate capture strategy.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Frequently Asked Questions

Can I solve this by scrolling the page to the bottom first?

Usually not. Scrolling changes the sticky threshold but does not turn a sticky or fixed navbar into normal-flow content. Choose a viewport capture or apply a capture-only style override.

What should I report when filing a reproducible issue?

Include a minimal URL or HTML case, the navbar and ancestor CSS, Puppeteer version, Chromium revision, viewport dimensions, screenshot options, and whether the document or a nested container scrolls.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Leave a Reply

Your email address will not be published. Required fields are marked *

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Read next

Recommended PC Tool
Recommended PC Tool
Outdated Drivers Are Slowing You DownFree scan - exact matches
Windows Errors? Fix Them Before They SpreadFree repair scan

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.