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.
The Tool Desk
Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →#1 Best Overall
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.
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
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.
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.
Rank #3
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. |
| 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.
Quick wins for a faster PC:
Repair Windows errors before they cause bigger problemsFix Now →Scan for outdated or missing drivers - takes under a minuteDriver Scan →Clear out junk files and repair common Windows errorsFree Scan →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.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Scan for outdated or missing drivers - takes under a minute3Repair Windows errors before they cause bigger problemsThe 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.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.
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.
Recommended Free Tools
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.
Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallIs 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.
Quick Recap
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.

