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

To capture a JavaScript-rendered chart with PhantomJS, create a webpage object, set its viewport (and an optional clipping rectangle), open the page, wait for the visualization’s own ready signal—or use a cautious delay as a fallback—then call page.render() and exit. A successful page.open() callback only means that the document load reported success; it does not prove that asynchronous data, fonts, or chart animations have finished.

PhantomJS development is suspended. The project website says, “Important: PhantomJS development is suspended until further notice,” and its GitHub repository is archived read-only (archive date: May 30, 2023). Treat the procedure below as a legacy maintenance option, not a new automation recommendation. Its WebKit engine can render CSS, SVG, images, and Canvas, but no legacy runtime can be assumed to support every current chart library or website.

What you need before starting

  • A PhantomJS installation that runs on your operating system.
  • The URL of the page containing the visualization.
  • A destination directory where PhantomJS can write the image or PDF.
  • Knowledge of how the page signals chart readiness, if it exposes such a signal.

Run the script from a shell with phantomjs capture.js. PhantomJS executes the script outside the browser page; code inside page.evaluate() executes inside the page context, so values must be returned explicitly and browser-only objects cannot be used directly in the outer script.

A complete PhantomJS capture script

The following example captures a full rendered page after checking for a page-specific readiness flag. Adapt the URL, output filename, and readiness selector or JavaScript property to the site you control.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
var page = require('webpage').create();
var system = require('system');

var url = system.args[1] || 'https://example.com/dashboard';
var output = system.args[2] || 'visualization.png';

page.viewportSize = { width: 1440, height: 1000 };
// Optional: capture only a rectangle in page coordinates.
// page.clipRect = { top: 120, left: 80, width: 1280, height: 720 };

var start = Date.now();
var timeoutMs = 30000;
var pollMs = 100;
var fallbackDelayMs = 1500;
var fallbackStarted = false;

function finish(status) {
  if (status === 'success') {
    page.render(output);
    console.log('Saved ' + output);
  } else {
    console.error('Page did not load: ' + status);
  }
  phantom.exit(status === 'success' ? 0 : 1);
}

function chartIsReady() {
  return page.evaluate(function () {
    // Replace this with the application’s real readiness condition.
    return !!document.querySelector('[data-chart-ready="true"]');
  });
}

function waitForChart() {
  var elapsed = Date.now() - start;
  if (chartIsReady()) {
    finish('success');
    return;
  }
  if (elapsed >= timeoutMs) {
    // The page loaded, but the chart never exposed readiness.
    // Capture only if your use case accepts a best-effort result.
    console.warn('Readiness condition timed out; rendering current page.');
    finish('success');
    return;
  }
  if (!fallbackStarted && elapsed >= fallbackDelayMs) {
    fallbackStarted = true;
  }
  setTimeout(waitForChart, pollMs);
}

page.open(url, function (status) {
  if (status !== 'success') {
    finish(status);
    return;
  }
  waitForChart();
});

Save this as capture.js, then run:

phantomjs capture.js https://example.com/dashboard chart.png

The script deliberately checks a condition rather than assuming a fixed sleep is sufficient. If the page has no usable signal, replace chartIsReady() with a conservative delay, described below, and decide whether a best-effort image is acceptable when the timeout expires.

Step-by-step workflow

1. Create the WebPage object

require('webpage').create() returns the object that controls navigation, viewport, clipping, evaluation, and rendering.

2. Set dimensions before loading

Set page.viewportSize before page.open() when responsive breakpoints affect the chart. A wider viewport can change labels, legends, and data density. Use page.clipRect when you need only a region, such as a chart card rather than the entire page. The rectangle uses page coordinates with top, left, width, and height.

page.viewportSize = { width: 1280, height: 900 };
page.clipRect = { top: 200, left: 40, width: 1200, height: 600 };

3. Open the URL and inspect status

page.open(url, callback) invokes the callback with a status such as success or fail. Handle non-success immediately; rendering a failed navigation usually produces an unusable file.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
page.open('https://example.com/chart', function (status) {
  if (status !== 'success') {
    console.error('Open failed: ' + status);
    phantom.exit(1);
    return;
  }
  // Do not render yet: asynchronous chart work may still be running.
});

4. Wait for visualization readiness

The most reliable trigger is a state your application exposes after data and drawing complete. Common patterns include a data-chart-ready="true" attribute, a global Boolean, or a DOM element that appears only after the chart is populated. Query it with page.evaluate():

Rank #2
Sale
var ready = page.evaluate(function () {
  return window.dashboard && window.dashboard.chartReady === true;
});

This is page-context code. It can inspect the DOM and page globals, but it cannot call functions or access variables defined in the outer PhantomJS script unless you pass values through the return result.

5. Use a fixed delay only as a fallback

PhantomJS’s Quick Start demonstrates delaying capture with setTimeout. A delay is easy to add, but it is a heuristic: a slow response may still be drawing when the timer expires, while a fast page makes you wait unnecessarily. If you must use one, choose a delay that matches your page’s normal worst case and retain a timeout so a broken request does not hang forever.

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

6. Render and exit

page.render('chart.png') saves the current rendered page. The documented formats include PDF, PNG, JPEG, BMP, and PPM; GIF availability depends on the Qt build. PhantomJS normally infers format from the filename extension. JPEG and PNG quality settings are available through the documented rendering API. Always call phantom.exit() after the file is written so batch jobs terminate cleanly.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
page.render('chart.pdf');
phantom.exit();

Capturing one chart instead of the whole page

PhantomJS’s clipping rectangle is coordinate-based, not a CSS-selector crop. To determine coordinates, inspect the element in the page context and return its bounding rectangle, then assign that rectangle to page.clipRect before rendering.

var rect = page.evaluate(function () {
  var node = document.querySelector('#revenue-chart');
  if (!node) { return null; }
  var r = node.getBoundingClientRect();
  return { top: r.top, left: r.left, width: r.width, height: r.height };
});

if (!rect) {
  console.error('Chart element not found');
  phantom.exit(1);
} else {
  page.clipRect = rect;
  page.render('revenue-chart.png');
  phantom.exit();
}

Account for scrolling and responsive layout when calculating coordinates. If the chart changes size after data arrives, calculate the rectangle only after the readiness condition is true.

Choosing an output format

Format Use it when Considerations
PNG You need crisp text, lines, transparency, or lossless output. Usually larger than JPEG for photographic content.
JPEG The visualization is photographic or file size matters more than lossless edges. Compression can soften text and fine lines; quality can be configured.
PDF You need a document-style export or printing workflow. Page dimensions and pagination require deliberate viewport and layout choices.
BMP/PPM A downstream tool specifically requires an uncompressed or simple raster format. Files are commonly much larger than PNG or JPEG.
GIF Your PhantomJS Qt build provides GIF support. Support depends on the build, so verify it in the target environment.

Use the filename extension to select the format, and verify the produced file before distributing it.

Handling common dynamic-page problems

The callback says fail

Check the URL, DNS and network access available to the PhantomJS process. A page requiring authentication, a blocked certificate, or an unreachable host can fail before chart code runs. Log the status and exit non-zero rather than saving a misleading blank image.

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

The image is blank or shows a loading spinner

Loading completion and chart readiness are different events. Inspect the page for a real readiness flag, wait for the chart’s final DOM node, or increase the fallback delay. If the application fetches data after load, ensure the request is allowed in the environment and that your timeout is long enough for the slowest expected response.

Only part of the chart is visible

Increase page.viewportSize, remove or revise page.clipRect, and check responsive breakpoints. A clip rectangle smaller than the chart will intentionally crop it.

Labels or fonts differ from a normal browser

PhantomJS uses an older WebKit stack. Font availability, CSS support, and modern JavaScript features can differ from current browsers. Treat visual discrepancies as compatibility limitations, not as evidence that the chart data changed.

The script never exits

Every asynchronous path needs either phantom.exit() or a timeout branch. Add a maximum wait duration and terminate with an error when the page never reaches readiness.

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

The output format is rejected

Use a documented extension such as .png, .jpg, .pdf, .bmp, or .ppm. If GIF is required, confirm that the installed Qt build includes GIF support.

Reliability and maintenance practices

  • Expose an explicit readiness flag in pages you own instead of guessing from elapsed time.
  • Record the URL, viewport, clip rectangle, readiness timeout, and PhantomJS version alongside each artifact.
  • Keep separate failure handling for navigation failure, readiness timeout, and rendering errors.
  • Run a representative set of pages after any OS, Qt, font, or PhantomJS package change.
  • Compare captures at the same viewport; responsive charts can legitimately produce different layouts.
  • Do not present a successful file as proof that all asynchronous requests completed unless your readiness condition establishes that fact.

Because development is suspended and the repository is archived, plan a migration if the workflow must support modern sites, current browser APIs, or long-term security maintenance.

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 single-call screenshot API when maintaining a PhantomJS runtime is not worthwhile. 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 or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.

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

See the ScreenshotNeo API documentation for all options, including full-page lazy-image loading, CSS-selector element capture, device and viewport settings, retina scale, PDF paper and page-range controls, custom CSS or JavaScript, pre-capture clicks, selector hiding, selector or network-idle waits, request and resource blocking, headers, cookies, user agents, Authorization, timezone, geolocation, transparent backgrounds, resizing, cache TTLs, signed image links, asynchronous webhooks, bulk capture of up to 100 URLs per call, usage reporting, and the OpenAPI specification.

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.

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)

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 fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));

Every feature is included on every plan. 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 API.

FAQ

Can PhantomJS capture SVG and Canvas charts?

Its documented screen-capture scope includes CSS, SVG, images, and Canvas. Compatibility with a particular modern chart library is not guaranteed, so validate the exact page and runtime you depend on.

Does page.open() wait for AJAX data?

No. Its callback reports navigation status. You must separately wait for the visualization’s readiness condition or use a deliberately bounded fallback delay.

Can I save a PDF instead of an image?

Yes. Pass a filename ending in .pdf to page.render(), then adjust viewport and page layout for the document you need.

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

Is PhantomJS suitable for a new project?

It is a legacy option because development is suspended and the repository is archived. Use it only when you accept its older WebKit behavior and have verified your pages.

Frequently Asked Questions

Can PhantomJS capture SVG and Canvas charts?

Its documented screen-capture scope includes CSS, SVG, images, and Canvas. Compatibility with a particular modern chart library is not guaranteed, so validate the exact page and runtime you depend on.

Does page.open() wait for AJAX data?

No. Its callback reports navigation status. You must separately wait for the visualization’s readiness condition or use a deliberately bounded fallback delay.

Can I save a PDF instead of an image?

Yes. Pass a filename ending in .pdf to page.render(), then adjust viewport and page layout for the document you need.

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

Is PhantomJS suitable for a new project?

It is a legacy option because development is suspended and the repository is archived. Use it only when you accept its older WebKit behavior and have verified your pages.

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.