What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Inject CSS after page.open succeeds and before you render or measure the page. The reliable pattern is to call page.evaluate, create a <style> element in the page context, append your stylesheet as a text node, and then call page.render. Pass the stylesheet as a string: PhantomJS serializes values crossing the evaluate boundary, so DOM nodes, functions, and closures cannot be passed as arguments or returned.
The basic injection pattern
This complete PhantomJS script loads a page, inserts CSS, renders the result, and exits with a useful status message:
var page = require('webpage').create();
var css = 'body { background: #f5f5f5; } .notice { color: #b00; }';
page.open('https://example.test/', function (status) {
if (status !== 'success') {
console.log('Unable to access network');
phantom.exit();
return;
}
page.evaluate(function (cssText) {
var style = document.createElement('style');
style.setAttribute('type', 'text/css');
style.appendChild(document.createTextNode(cssText));
(document.head || document.documentElement).appendChild(style);
}, css);
page.render('styled.png');
phantom.exit();
});
The callback supplied to page.evaluate executes inside the web page, where normal DOM APIs and CSS selectors are available. The second argument, css, crosses from the PhantomJS script into that page context as a JSON-safe string. The callback creates a style node in the document, appends the CSS text, and places it in the head when one exists. The fallback to document.documentElement also works for markup that has no conventional head element.
Why the timing matters
Wait for a successful navigation
Inject only after page.open reports success. Before that point the document may not exist, or it may still be the previous page. If navigation fails, report the failure and stop instead of rendering an old or empty document.
#1 Best Overall
- HTML CSS Design and Build Web Sites
- Comes with secure packaging
- It can be a gift option
Inject before rendering or measuring
Call page.render, read computed styles, or measure layout only after the style element has been appended. Otherwise the screenshot and measurements reflect the unmodified stylesheet.
Account for pages that rebuild their DOM
Some applications replace large parts of the document after load. If the application removes the style element or replaces the head, inject after the final DOM update, or schedule a second injection when the application has finished its own work. A style element that remains in the document will continue to apply as the page changes.
Passing CSS through page.evaluate safely
Keep the stylesheet in a plain string variable. PhantomJS’s evaluate bridge accepts simple JSON-serializable values; DOM elements, JavaScript functions, and closures do not cross that boundary. This works:
var css = '.card { border: 1px solid #ccc; }';
page.evaluate(function (cssText) {
var style = document.createElement('style');
style.appendChild(document.createTextNode(cssText));
document.documentElement.appendChild(style);
}, css);
This does not work reliably:
var styleNode = document.createElement('style');
page.evaluate(function (node) {
document.head.appendChild(node);
}, styleNode);
The node belongs to the page context and cannot be transferred as an argument. Create it inside the evaluated callback instead. The same rule applies to functions and objects that contain non-serializable members.
Use an external injector when the CSS is reusable
For a stylesheet used by several scripts, put the DOM operation in a separate file. The file runs in the page context when loaded with injectJs:
Rank #2
(function () {
var cssText = 'body { font-family: sans-serif; }';
var style = document.createElement('style');
style.type = 'text/css';
style.appendChild(document.createTextNode(cssText));
(document.head || document.documentElement).appendChild(style);
}());
Save that code as inject-css.js, then load it after navigation:
var page = require('webpage').create();
page.open('https://example.test/', function (status) {
if (status !== 'success') {
console.log('Unable to access network');
phantom.exit();
return;
}
if (!page.injectJs('inject-css.js')) {
console.log('CSS injector could not be loaded');
phantom.exit();
return;
}
page.render('styled.png');
phantom.exit();
});
The official API returns true when the file is loaded and false otherwise, so check the return value before rendering. This approach keeps the injector logic in one place, while the inline evaluate pattern is convenient when each page needs different CSS.
Choose the right approach
| Approach | Use it when | Important behavior |
|---|---|---|
page.evaluate with a CSS string |
You need page-specific rules or a small one-off change | Creates the style element in the loaded page; pass only JSON-safe values |
page.injectJs |
The same injector should be reused across scripts | Loads an external JavaScript file and returns a success boolean |
page.setContent |
You control the complete HTML document | Loads supplied markup without an HTTP request and sets the current location to the base URL you provide |
Appending a remote <link> |
The stylesheet must remain an external resource | Adds another network dependency; rendering can depend on that request completing |
When you own the HTML: setContent
If PhantomJS is rendering HTML generated by your own program, put the style directly in the document and use setContent:
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Fix the driver behind crashes, sound loss and screen glitches3Clear out junk files and repair common Windows errorsvar page = require('webpage').create();
var html = '<!doctype html><html><head><style>body{margin:0}</style></head>' +
'<body><h1>Report</h1></body></html>';
page.setContent(html, 'https://example.test/report/');
page.render('report.png');
phantom.exit();
setContent replaces the page with the supplied markup, sets the location to the URL argument, and does not make an HTTP request. The base URL matters when the markup refers to relative resources such as images, scripts, or stylesheets. Setting page.content also replaces and reloads the main-frame content, but setContent makes the intended base URL explicit.
Making injected rules take effect
Specificity and cascade
An injected rule still participates in the normal cascade. A selector with lower specificity can lose to an existing rule, and a later stylesheet can override it. Increase specificity deliberately or use !important only where the override is intentional. Inspect the target element and test the final computed style when a change appears to do nothing.
Rank #3
- 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
Media conditions
Rules inside media queries apply only when PhantomJS’s current viewport and media settings match. A desktop rule will not necessarily affect a page rendered with a narrow viewport, and print-specific rules do not automatically apply to a screen render.
Inline styles and stateful classes
Inline declarations and state classes can take precedence over a broad selector. Target the actual class or state that exists at capture time, and inject after any script that adds or removes those classes.
Remote stylesheets
You can create a <link rel="stylesheet"> element instead of an inline style, but that introduces another network request. Inline CSS avoids waiting for an additional resource and is preferable when deterministic rendering matters.
Debugging checklist
- Confirm that
page.openreturnedsuccessbefore callingevaluateorinjectJs. - Verify that the CSS string is not empty and contains valid braces, selectors, and declarations.
- Use
(document.head || document.documentElement)so documents without a normal head still receive the style. - Render only after injection; if you read layout values, read them after injection as well.
- Check selector specificity, inline declarations, later stylesheets, and media conditions.
- Look for application code that replaces the document head or removes the injected node.
- For an external injector, check the Boolean result from
page.injectJsand verify the file path. - If the page depends on a remote stylesheet, image, font, or script, allow that resource to finish before rendering; an inline rule alone cannot repair a failed network dependency.
Common failures and fixes
The screenshot looks unchanged
Most often, the render occurred before injection, the selector lost the cascade, or the page rewrote its DOM afterward. Move the render below the injection call, test the selector in the page context, and inject after the final DOM update.
page.evaluate throws or receives an empty value
Pass a string or another simple JSON value, not a DOM node, function, or closure. Define the style element inside the evaluated callback and pass the CSS text as its argument.
Rank #4
The style file is not applied
A false return from injectJs means the file could not be loaded. Correct the path, permissions, or filename, then check the return value again. Keep the injector self-contained; it must use APIs available in the page context.
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEFix the driver behind crashes, sound loss and screen glitchesFind Drivers →Only part of the page changes
The rule may be limited by a media query, a more specific selector, or an inline declaration. Inspect the element’s final computed style and adjust the selector or viewport intentionally.
The page is blank or navigation fails
Do not treat a failed page.open as a successful capture. Log the failure, skip injection, and exit. A CSS injector cannot fix an unreachable page or a document that never loaded.
Performance and reliability considerations
Inline injection adds one DOM operation and no network request, making it the most predictable option for a single capture. An external injector is easier to maintain but adds file loading and path resolution. A remote stylesheet adds network variability and should be used only when keeping CSS external is a requirement.
Keep the stylesheet focused on the elements you need to change. Very broad rules can trigger more style recalculation, especially on large documents. If a page performs late asynchronous rendering, wait for the application’s final state before injecting and rendering; otherwise you may capture an intermediate layout even though the CSS itself is correct.
Best Value
PhantomJS is legacy software
PhantomJS remains useful for maintaining existing capture scripts, but its project README states: "Important: PhantomJS development is suspended until further notice." Treat it as a legacy runtime: pin the version used by your build, test pages that rely on modern browser behavior, and plan a migration when current web APIs or security requirements exceed what PhantomJS can provide.
Or skip the browser setup
If your goal is a clean screenshot rather than maintaining a PhantomJS runtime, ScreenshotNeo provides a website screenshot API. One GET request returns PNG, JPEG, WebP, or PDF output, and its custom CSS option can apply presentation changes without writing browser orchestration code.
One-call capture with cURL
See the full parameter reference in the ScreenshotNeo documentation. This request saves a WebP screenshot:
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
Python
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
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}`);
ScreenshotNeo accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed as clean shots; response headers identify the page verdict and billing result. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.
Recommended Free Tools
The Free plan includes 1,000 shots each month with no card. Paid plans start at $5 for 3,000 shots; every feature is available on every plan. Create a free ScreenshotNeo account to try it without adding a card.
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.

