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.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
#1 Best Overall
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

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.

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

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:

(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:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
var 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
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

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.

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

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.open returned success before calling evaluate or injectJs.
  • 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.injectJs and 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.

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.

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

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.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

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.

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

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.

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

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.

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.