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

Call page.open once, keep the same webpage object, change the page state with page.evaluate, wait for that change to finish, and call page.render with a new filename for every image. This avoids a second navigation while preserving the page instance between captures.

The single-load capture pattern

PhantomJS separates navigation from rendering. page.open loads a URL and reports success or fail in its callback. After a successful load, the same page can be manipulated and rendered repeatedly. A typical sequence is:

  1. Create one object with require('webpage').create().
  2. Set the viewport before opening the page.
  3. Call page.open once and stop if the callback status is not success.
  4. For each desired state, run page-context JavaScript with page.evaluate.
  5. Wait for the state-specific update to settle.
  6. Call page.render using a filename that has not been used before.
  7. Repeat until all states are captured, then call phantom.exit().

The state can be a DOM attribute, a selected tab, an expanded panel, a chart filter, or any other change that can be triggered in the page context. No second page.open call is needed unless you intentionally navigate to another document.

Complete PhantomJS example

Save this as multi-capture.js, replace the URL and state logic, and run it with the PhantomJS command-line executable.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
var page = require('webpage').create();
var target = 'https://example.com/';
var states = ['first', 'second', 'third'];
var step = 0;

page.viewportSize = {
  width: 1024,
  height: 768
};

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

  captureNext();
});

function captureNext() {
  if (step >= states.length) {
    phantom.exit();
    return;
  }

  var state = states[step];

  page.evaluate(function (value) {
    // Replace this with a page-specific action or DOM change.
    document.body.setAttribute('data-capture-state', value);
  }, state);

  // Replace this delay with a page-specific readiness check when needed.
  window.setTimeout(function () {
    page.render('capture-' + (step + 1) + '.png');
    step += 1;
    captureNext();
  }, 100);
}

The 1024×768 viewport is only an example. Set dimensions that match the layout you need to test. Each render receives a different filename, so an earlier image is not overwritten.

Replacing the illustrative state mutation

The sample changes an attribute only to make the workflow visible. Real pages usually need an interaction. For example, a tab switch could be performed inside evaluate:

page.evaluate(function () {
  var tab = document.querySelector('[data-tab="reports"]');
  if (tab) {
    tab.click();
  }
});

You can also set a form value, add a class, expand an accordion, or invoke a page-defined function. Keep the argument and return value JSON-serializable. PhantomJS documentation states: “As of PhantomJS 1.6, JSON-serializable arguments can be passed to the function.” Do not pass a DOM node, function, or other value that cannot be represented as JSON; pass a selector or plain data instead.

Waiting for each state to be ready

A fixed 100-millisecond delay is useful for demonstrating control flow, not for guaranteeing that an application has finished updating. A chart, request-driven table, or animation may need more time. Wait for an observable condition belonging to that state.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
function waitForReport(done, attempts) {
  attempts = attempts || 0;

  var ready = page.evaluate(function () {
    return !!document.querySelector('.report-rendered');
  });

  if (ready) {
    done();
    return;
  }

  if (attempts >= 40) {
    console.log('Timed out waiting for the report state');
    done();
    return;
  }

  window.setTimeout(function () {
    waitForReport(done, attempts + 1);
  }, 250);
}

To use it, invoke waitForReport after your state-changing evaluate call and put page.render inside its callback. Replace .report-rendered with a selector, flag, or other condition that the target page actually sets. If the page exposes no reliable signal, choose a conservative delay and document that it is an approximation; there is no universal PhantomJS wait value for every site.

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

Viewport size versus clip rectangle

Setting What it controls Typical use
page.viewportSize The browser area in which the page lays out and paints. Reproduce a desktop, tablet, or mobile-like layout.
page.clipRect The rectangular portion included in the rendered output. Capture one panel or crop a known region without changing layout.

These settings work together: the viewport affects responsive breakpoints, while the clip rectangle selects the pixels saved. The documented 1024×768 values are examples, not required dimensions. Set clipRect before the render that needs cropping, then change or clear it before the next capture if the regions differ.

Handling several states safely

Use deterministic names

Include a sequence number and, when useful, a state label such as capture-02-reports.png. Never reuse the same path unless overwriting is intentional. If a run can be restarted, write to a run-specific directory or add a timestamp generated by the outer script.

Keep navigation out of the loop

Putting page.open inside the state loop reloads the document and defeats the purpose of this technique. Perform only in-page actions between renders. If an action genuinely navigates, wait for that navigation to complete before rendering; it is still the same page object, but it is no longer the original document state.

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.

End the process on every terminal path

Call phantom.exit(1) for an initial load failure and phantom.exit() after the final image. A missing exit call can leave an automation job running after the files have been written.

Troubleshooting

The callback reports fail

Do not render after a failed open. Check the URL, DNS or network access available to the PhantomJS process, and print the status before exiting. A successful callback means the load completed according to PhantomJS; it does not prove that every application request or widget finished.

Every image looks identical

Confirm that the selector exists and that the action actually changes the DOM. Log the state value, return a simple boolean from evaluate, and inspect the page for a state marker. If a framework updates asynchronously, replace the short delay with a selector or flag that appears only after the update.

The next capture starts too soon

Do not guess with an ever-smaller delay. Poll a page-specific readiness condition, increase the maximum wait attempts, or have the application set a completion marker after its request and rendering work finish.

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

A file is missing or has been overwritten

Check the working directory from which PhantomJS was launched and print the exact output path. Ensure the filename includes the incrementing step and that the process has permission to write there.

An evaluate argument causes an error

Pass strings, numbers, booleans, arrays, or plain objects that can be serialized as JSON. Pass a CSS selector instead of a live element and locate the element inside the evaluated function.

The crop is wrong

Inspect the viewport and clip coordinates. The clip rectangle is measured in page pixels; it does not resize the layout. First verify the full viewport capture, then add a clip rectangle with coordinates inside that viewport.

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 process waits forever

Bound every custom wait with a maximum number of attempts. On timeout, record the state and either render a diagnostic image or exit with a failure code, depending on whether incomplete captures are acceptable for your job.

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

Performance and reliability considerations

One navigation removes repeated network and page-startup work, so a sequence of renders is generally more efficient than opening the URL for every image. Rendering still consumes CPU and memory, especially for large viewports or many captures. Process states in a predictable order, release the process when finished, and keep output files separate so a later failure does not destroy earlier evidence.

PhantomJS documentation is legacy documentation. Current maintenance status and compatibility with present-day websites are not established here, so test the exact pages, JavaScript features, authentication flow, and rendering requirements you depend on before adopting this workflow for production. A page that loads successfully can still rely on browser capabilities that an older engine does not implement.

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 provides a website screenshot API and MCP server when you prefer an HTTP call over maintaining a PhantomJS process. A request captures a URL as PNG, JPEG, WebP, or PDF. For a single capture, the minimal cURL call is:

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

See the ScreenshotNeo API documentation for authentication, output formats, and options. Equivalent Python and Node.js requests are:

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

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://example.com/"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({
  access_key: 'YOUR_API_KEY',
  url: 'https://example.com/'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const data = Buffer.from(await res.arrayBuffer());
require('fs').writeFileSync('shot.webp', data);

For several visual states, make separate requests with state-specific custom JavaScript or click instructions, or submit asynchronous jobs when your integration supports them. ScreenshotNeo is useful when you need browser setup handled remotely and want a response that identifies the result with X-Page-Verdict and X-Billed headers.

Options relevant to repeated or controlled captures

  • Full-page screenshots can load lazy images; element capture accepts a CSS selector.
  • Choose dark mode, one of 12 device presets, any viewport, and a retina scale.
  • Generate PDFs with paper size, margins, landscape mode, and page ranges.
  • Run custom CSS or JavaScript, click an element before capture, hide selectors, and wait for a selector, delay, or network idle.
  • Block ads, trackers, selected requests, or resource types to make captures more deterministic.
  • Supply custom headers, cookies, user agents, Authorization values, timezone, and geolocation.
  • Use transparent backgrounds, image resizing, and a cache with a TTL you choose.
  • Create signed links for public image tags, asynchronous jobs with signed webhooks, and bulk captures of up to 100 URLs per call.
  • Check usage through the usage API, retrieve the OpenAPI specification, and use parameter names accepted by other screenshot APIs to ease migration.

Before the capture, ScreenshotNeo accepts cookie or consent banners as a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response reports what happened. An MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.

Plans and billing

Plan Included screenshots per month Price
Free 1,000 $0, no card
Starter 3,000 $5
Growth 15,000 $15
Pro 60,000 $39
Scale 250,000 $99
Business 1,000,000 $249

Every feature is available on every plan. Yearly billing gives two months free. The free tier includes 1,000 screenshots each month without a card, and paid plans start at $5 for 3,000 screenshots. Create a free ScreenshotNeo account to try it.

Frequently Asked Questions

Can page.evaluate return a DOM element for the next step?

Return a selector, boolean, string, number, or plain object instead. DOM elements and functions are not JSON-serializable values for the evaluate boundary.

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.

Does one PhantomJS render call create multiple image files?

No. Each call writes one output, so a loop must invoke page.render once per state and provide a distinct filename.

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.