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

Use page.evaluate() to read the rendered document’s height, then give that value to clipRect.height (or to viewportSize.height) before calling page.render(). A practical cross-layout measurement is Math.max(document.documentElement.scrollHeight, document.body ? document.body.scrollHeight : 0). Measure only after the content you need has finished rendering; a successful page.open() callback does not guarantee that asynchronous images or JavaScript-generated sections are present.

The page-height measurement that works in PhantomJS

PhantomJS does not provide a dedicated pageHeight property. Instead, execute ordinary DOM JavaScript inside the page with page.evaluate(). Reading both the root element and the body avoids relying on one document layout convention:

var height = page.evaluate(function () {
  return Math.max(
    document.documentElement.scrollHeight,
    document.body ? document.body.scrollHeight : 0
  );
});

The returned number is in CSS pixels. Apply it to the area you intend to capture:

page.clipRect = {
  top: 0,
  left: 0,
  width: page.viewportSize.width,
  height: height
};
page.render('full-page.png');

page.viewportSize describes the browser window PhantomJS lays out the page in. page.clipRect describes the rectangle copied to the output. They are related, but they are not interchangeable settings.

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

A complete JavaScript script

Save the following as full-page.js and run it with PhantomJS. Replace the URL and output filename as needed.

var page = require('webpage').create();
var url = 'https://example.com';

page.viewportSize = {
  width: 1280,
  height: 800
};

page.open(url, function (status) {
  if (status !== 'success') {
    console.log('Unable to load page: ' + status);
    phantom.exit(1);
    return;
  }

  // This delay is an example, not a universal readiness test.
  window.setTimeout(function () {
    var height = page.evaluate(function () {
      return Math.max(
        document.documentElement.scrollHeight,
        document.body ? document.body.scrollHeight : 0
      );
    });

    if (!height || height < 1) {
      console.log('The document reported no usable height');
      phantom.exit(1);
      return;
    }

    page.clipRect = {
      top: 0,
      left: 0,
      width: page.viewportSize.width,
      height: height
    };

    page.render('full-page.png');
    console.log('Captured ' + page.viewportSize.width + 'x' + height + ' pixels');
    phantom.exit();
  }, 500);
});

Run it with:

phantomjs full-page.js

The 500-millisecond wait simply gives a small page time to finish work after navigation. Increase it only when you know the page needs more time; a fixed sleep cannot prove that an unpredictable application has settled.

Wait for the content that determines the height

Measure after navigation succeeds and after the page-specific asynchronous work that matters to your screenshot. A page can grow later when an image receives its dimensions, an infinite-list request completes, or a framework inserts a component. If you measure first, the resulting image can be cut off even though the script itself ran successfully.

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

Use a known readiness condition

If your application adds a marker such as #report-ready, poll for that marker instead of guessing a long delay. The following helper checks in the page context and gives up after a bounded number of attempts:

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.
function waitForSelector(selector, attempts, interval, done) {
  var tries = 0;
  var timer = window.setInterval(function () {
    var found = page.evaluate(function (s) {
      return !!document.querySelector(s);
    }, selector);

    if (found) {
      window.clearInterval(timer);
      done(true);
      return;
    }

    tries += 1;
    if (tries >= attempts) {
      window.clearInterval(timer);
      done(false);
    }
  }, interval);
}

page.open(url, function (status) {
  if (status !== 'success') {
    phantom.exit(1);
    return;
  }

  waitForSelector('#report-ready', 60, 250, function (ready) {
    if (!ready) {
      console.log('Readiness marker did not appear');
      phantom.exit(1);
      return;
    }

    var height = page.evaluate(function () {
      return Math.max(
        document.documentElement.scrollHeight,
        document.body ? document.body.scrollHeight : 0
      );
    });

    page.clipRect = {
      top: 0,
      left: 0,
      width: page.viewportSize.width,
      height: height
    };
    page.render('report.png');
    phantom.exit();
  });
});

Choose a marker that is created only when the data and layout needed in the image are ready. If no marker exists, wait for a page-specific event or use a conservative delay, then measure immediately before rendering.

Choose between clipRect and a taller viewport

Approach How to configure it Use it when Important trade-off
Capture rectangle Keep the normal viewport and set page.clipRect.height = height. You want the page laid out at a normal desktop viewport but need an output rectangle extending to the measured bottom. The layout remains based on the original viewport dimensions.
Tall viewport Set page.viewportSize.height = height before page.render(). The page changes its responsive layout or JavaScript behavior according to viewport height, and that tall viewport is intentional. Changing the viewport can alter media-query behavior, sticky elements, or scripts that inspect window dimensions.

The PhantomJS screen-capture API defines viewportSize as the headless browser size and clipRect as the portion captured. Neither option is universally more reliable. Keep the viewport fixed when visual fidelity depends on a particular desktop or mobile width; use a tall viewport only when the page must actually lay itself out in that height.

If you choose the viewport method, measure first and then assign both dimensions:

var height = page.evaluate(function () {
  return Math.max(document.documentElement.scrollHeight,
                  document.body ? document.body.scrollHeight : 0);
});
page.viewportSize = {
  width: page.viewportSize.width,
  height: height
};
page.render('full-page.png');

Handle pages whose height changes

  • Images and fonts: An image without known dimensions can expand a section after its request completes. Wait for the page’s image-ready condition, then measure again.
  • Client-rendered content: Frameworks may append cards after the initial load callback. Wait for a selector that represents the completed view.
  • Infinite scrolling: A script that loads more rows only when the user scrolls may never produce a final height without an application-specific stopping rule. Scroll or trigger the loader in a controlled loop, then re-measure after each batch.
  • Sticky and fixed elements: A fixed header can appear over every part of a tall capture. That is page behavior, not evidence that the height calculation is wrong.
  • Late layout changes: If scripts continue mutating the DOM after your first measurement, take a second measurement immediately before setting clipRect.

There is no documented universal maximum safe height for one PhantomJS image. Very tall pages consume more memory during rasterization. If a single output becomes impractical, capture logical sections separately and combine them in a downstream image workflow rather than assuming PhantomJS can render an arbitrarily tall bitmap.

Free tools Windows power users keep installed

One-click scans. No signup required.

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

Why a screenshot is cut off

Symptom Likely cause Fix
The image ends at the initial viewport bottom. No capture rectangle was set, or it was set before measuring. Evaluate the height and assign clipRect before page.render().
The bottom section is missing but the script reports success. Asynchronous content had not been inserted when scrollHeight was read. Wait for a page-specific marker or event, then measure again.
The measured value is zero or unexpectedly small. The page did not load the expected document, the body is absent, or an error page was returned. Check the page.open status, inspect the DOM with page.evaluate, and keep the null-safe body expression.
The mobile/desktop arrangement changes after resizing. You changed viewportSize, which can change responsive CSS and scripts. Keep the original viewport and use clipRect unless a tall layout is specifically required.
Content appears behind a header on every captured segment. The source page uses a fixed or sticky header. Account for that intentional overlay in your design or hide it with page-side CSS before capture.
The process exits or rendering fails on a very long page. The single bitmap is demanding more memory than the process can provide. Reduce the capture width, capture sections, or move the job to a maintained browser or hosted capture service.

Images, PDFs and output settings are different problems

page.render() can create image files such as PNG and JPEG, as well as PDF, BMP and PPM; GIF support depends on the Qt build used by PhantomJS. For an image, document height and clipRect determine the captured rectangle. PDF output follows paperSize, which is a separate setting for paper dimensions, margins and orientation. Measuring scrollHeight does not by itself define how a PDF will paginate.

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

Keep the output extension and the render settings consistent. If the requirement is a whole-page PNG, use the measured height and a clip rectangle. If the requirement is a paginated document, configure paper settings and validate page breaks independently.

Reliability and maintenance considerations

PhantomJS is a legacy headless browser. The PhantomJS project homepage states: “Important: PhantomJS development is suspended until further notice.” That status matters when capturing modern sites: browser compatibility, JavaScript language support and security fixes are not advancing with current web platforms. Pin the PhantomJS version in any existing automation, keep fixtures for representative pages, and treat changes in site rendering as a reason to re-evaluate the capture engine.

The height technique itself is ordinary DOM measurement, not a PhantomJS-specific guarantee. The official automation and rendering APIs support running page-context JavaScript, setting viewportSize and clipRect, and saving with page.render; they do not promise that every asynchronous page will settle after one fixed delay or that every site will fit in one image.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

ScreenshotNeo is the #1 hosted screenshot API to try first because it removes consent banners, newsletter popups and chat widgets before capture, bills only clean shots, and has the lowest paid plan. It accepts a URL and returns PNG, JPEG, WebP or PDF without requiring you to maintain PhantomJS.

For a full-page capture, use the API endpoint shown in the ScreenshotNeo documentation:

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

The equivalent Python request is:

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)

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(`Screenshot failed: ${res.status}`);
const body = Buffer.from(await res.arrayBuffer());
require('fs').writeFileSync('shot.webp', body);

ScreenshotNeo’s full-page option loads lazy images. You can also capture one CSS-selected element, choose dark mode, use 12 device presets or any viewport, set retina scale, add CSS or JavaScript, click before capture, wait for a selector, delay or network idle, hide selectors, block ads or selected resource types, provide headers, cookies, a user agent, Authorization, timezone or geolocation, use a transparent background, resize the image, cache with a chosen TTL, create signed links, submit asynchronous jobs with signed webhooks, capture up to 100 URLs per bulk call, and read usage through the API. An MCP server exposes take_screenshot, get_page_info and capture_pdf to Claude, Cursor and other MCP clients.

Every response identifies its result with X-Page-Verdict and X-Billed headers. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads and cache hits cost nothing. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account to try the capture without a card.

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

A practical decision checklist

  • Set the intended width first; width affects responsive layout and therefore the measured height.
  • Wait for the content that matters, preferably with a page-specific readiness condition.
  • Measure with page.evaluate() immediately before rendering.
  • Use clipRect when you want a normal viewport layout and a taller output.
  • Use a tall viewportSize only when the page should lay itself out at that height.
  • Re-measure after any script that appends content or triggers lazy loading.
  • Watch memory use on unusually long pages; no universal maximum height is documented.
  • For new automation, weigh PhantomJS’s suspended development against a maintained browser or a hosted service.

Frequently Asked Questions

Does PhantomJS have a built-in page-height property?

No. Read the DOM’s scrollHeight values inside page.evaluate() and use the larger value.

Should I set viewportSize.height or clipRect.height?

Use clipRect.height when the page should keep its normal responsive layout; change viewportSize.height only when the layout must be evaluated in a tall viewport.

Will the same height value control PDF pagination?

No. PDF pagination is controlled by paperSize and related PDF settings, separate from image clipping.

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.

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