ScreenshotNeo

BlogHow-to

How to Fix PhantomJS Image Widths Not Matching Expectations

Fix PhantomJS screenshot width problems by separating viewportSize, clipRect, paperSize, and page background settings.

By the ScreenshotNeo team1 October 20268 min read

When a PhantomJS image has the wrong width, check two settings separately: page.viewportSize controls the headless browser viewport, while page.clipRect controls the rectangle copied into the screenshot. Set both explicitly, render the file, and inspect its actual pixel dimensions. For PDF output, configure page.paperSize separately.

1. Understand the three dimensions

Setting Controls Use it when
page.viewportSize The browser’s layout viewport You need predictable responsive layout and media-query behavior.
page.clipRect The page region saved by render() The output image must have an exact crop and pixel size.
page.paperSize PDF page dimensions You are rendering a PDF rather than a bitmap.

PhantomJS documentation describes viewportSize as the actual size of the headless browser and clipRect as the portion of the page being captured. See the screen-capture guide and the render API.

2. Minimal fixed-width PhantomJS screenshot

This complete script creates a 1,200 × 800 PNG. The viewport and crop are deliberately the same size, so layout and output dimensions are easy to reason about.

var page = require('webpage').create();
var system = require('system');

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

page.viewportSize = {
  width: 1200,
  height: 800
};

page.clipRect = {
  left: 0,
  top: 0,
  width: 1200,
  height: 800
};

page.open(url, function (status) {
  if (status !== 'success') {
    console.error('Could not load ' + url + ' (status: ' + status + ')');
    phantom.exit(1);
    return;
  }

  page.render(output);
  console.log('Wrote ' + output);
  phantom.exit(0);
});

Run it with phantomjs capture.js https://example.com shot.png. The file’s pixel dimensions should be 1200 × 800. The official PhantomJS example also sets page.viewportSize before calling render(); treat those documented dimensions as configuration examples, not a performance guarantee.

3. Diagnose a width mismatch step by step

  1. Confirm the output type. render() writes an image when the filename has an image extension. A PDF uses different sizing rules.
  2. Print the values you set. Verify that width and height are numbers, not strings, and that no later code overwrites them.
  3. Compare viewport and crop. A 1440-pixel viewport with a 1024-pixel clipRect.width intentionally produces a 1024-pixel-wide image.
  4. Inspect the saved file. Use an image tool such as identify shot.png (ImageMagick) or your language’s image library. Checking the file separates an output-size problem from a page-layout problem.
  5. Check page background separately. Transparency changes how the image appears; it does not set its width.
var page = require('webpage').create();
page.viewportSize = { width: 1440, height: 900 };
page.clipRect = { left: 0, top: 0, width: 1024, height: 768 };

page.open('https://example.com', function (status) {
  if (status === 'success') {
    page.render('cropped.png');
  }
  phantom.exit(status === 'success' ? 0 : 1);
});

This intentionally saves a 1024 × 768 crop while the page lays out inside a 1440 × 900 viewport.

4. Choose the right viewportSize

Set the viewport to the layout you want the page to use. Responsive pages may select different CSS breakpoints at different viewport widths, so changing this value can change wrapping, navigation, and element positions even when the crop is unchanged.

page.viewportSize = {
  width: 1920,
  height: 1080
};

The PhantomJS render example uses 1920 × 1080, while the screen-capture guide shows 1024 × 768 as another example. Those values demonstrate configuration; select dimensions that match your required layout.

5. Choose the right clipRect

Use left, top, width, and height to define the captured rectangle. Set the rectangle explicitly whenever consumers require exact image dimensions.

page.clipRect = {
  left: 80,
  top: 120,
  width: 1000,
  height: 600
};

The resulting bitmap is 1000 × 600, starting 80 pixels from the left and 120 pixels from the top of the page’s rendered coordinate space. If you want the full viewport, make the rectangle match the viewport. If you want a smaller region, keep the viewport large enough for the desired layout and crop with clipRect.

Element-sized captures

PhantomJS does not make clipRect automatically follow an element. Read the element’s bounding rectangle and assign those coordinates yourself.

var box = page.evaluate(function () {
  var el = document.querySelector('#invoice');
  if (!el) return null;
  var r = el.getBoundingClientRect();
  return { left: r.left, top: r.top, width: r.width, height: r.height };
});

if (!box) {
  console.error('Missing #invoice');
  phantom.exit(1);
} else {
  page.clipRect = box;
  page.render('invoice.png');
  phantom.exit(0);
}

Account for any scrolling or coordinate conversion required by your page before using a measured rectangle. Re-check the final file dimensions after rendering.

6. PDF output uses paperSize

Changing viewportSize or clipRect does not define a PDF page. Set page.paperSize for PDF output. PhantomJS documents units including mm, cm, in, and px; a missing unit means pixels. See the paperSize documentation.

var page = require('webpage').create();
page.viewportSize = { width: 1200, height: 900 };
page.paperSize = {
  format: 'A4',
  orientation: 'portrait',
  margin: '1cm'
};

page.open('https://example.com', function (status) {
  if (status === 'success') page.render('page.pdf');
  phantom.exit(status === 'success' ? 0 : 1);
});

For a custom paper rectangle, use explicit dimensions and units supported by your PhantomJS build, for example { width: '210mm', height: '297mm', margin: '0' }. Validate the generated PDF with a PDF inspection tool because PDF page size is not a PNG pixel width.

7. Background transparency is a separate issue

PhantomJS’s FAQ explains that the page determines the background. If the page has no background, the rendered image can remain transparent. Set a background in the document when you need an opaque image; this does not change the image width.

page.evaluate(function () {
  document.body.bgColor = '#ffffff';
});
page.render('white-background.png');

Run this after the page has loaded and before render(). A transparent image that appears narrower against a viewer background still has the dimensions reported by the file itself.

8. Timing, scrolling, and dynamic pages

Capture only after the content that determines the measured size exists. For pages that update after open returns, schedule the measurement and render after the page’s own initialization has completed.

page.open('https://example.com', function (status) {
  if (status !== 'success') {
    phantom.exit(1);
    return;
  }

  window.setTimeout(function () {
    page.clipRect = { left: 0, top: 0, width: 1200, height: 800 };
    page.render('after-load.png');
    phantom.exit(0);
  }, 1000);
});

If a long page is required, choose a crop height that covers the intended region and ensure the page has been laid out before capturing. A viewport setting alone does not make the saved image a full-page capture.

9. Common errors and fixes

Symptom Likely cause Fix
Image is narrower than the viewport clipRect.width is smaller. Match clipRect.width to the desired output width, or remove the crop if the viewport-sized region is intended.
Image is wider than expected A later assignment changed clipRect, or the file being inspected is from an earlier run. Log the final values immediately before render() and write to a new output path.
Page layout changes at the same output width The viewport width differs from the crop width. Set viewportSize to the layout width you need; use clipRect only for the final crop.
Element is cut off The crop starts at the wrong coordinates or is too small. Measure the element with getBoundingClientRect(), then set left, top, width, and height from that measurement.
Blank or partially loaded capture The page had not finished loading or returned a failed status. Check status, wait for page initialization, and render only after required content exists.
Transparent-looking output No page background was set. Set document.body.bgColor or a CSS background before rendering; this is independent of width.
PDF page has the wrong size Only bitmap settings were changed. Configure page.paperSize with the required format, dimensions, orientation, and margins.

10. A repeatable verification checklist

  • Write down the required output width and height in pixels.
  • Set page.viewportSize for the desired responsive layout.
  • Set page.clipRect for the exact captured rectangle.
  • Wait until content affecting the crop has loaded.
  • Log the final settings immediately before page.render().
  • Inspect the saved file’s actual dimensions.
  • For PDFs, verify page.paperSize independently.
  • If appearance is confusing, check transparency separately from dimensions.

11. Performance, reliability, and cost considerations

A smaller crop reduces the number of pixels written, but it does not replace choosing the correct viewport: the page still has to lay out at the viewport dimensions you set. Keep the viewport and crop stable across runs when you need repeatable output, and wait for dynamic content before measuring. Check the load status and fail the process when the page cannot be opened so downstream systems do not treat an incomplete file as valid.

PhantomJS settings control rendering; they do not provide a hosted capture service, retries, usage accounting, or billing. If maintaining a browser runtime is the part causing operational work, a hosted endpoint can handle the capture request for you.

12. Or skip the browser setup

ScreenshotNeo provides a website screenshot API and MCP server. A single GET request returns a PNG, JPEG, WebP, or PDF. Before capture, it accepts cookie and consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be turned off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers. See the ScreenshotNeo API documentation for all options.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.com -o shot.webp
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 fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));

ScreenshotNeo also supports full-page captures with lazy images loaded, CSS-selector element captures, dark mode, device presets or custom viewports, retina scale, custom CSS and JavaScript, click and wait controls, blocked resources, headers, cookies, user agents, authorization, timezone and geolocation, transparent backgrounds, resizing, caching with a chosen TTL, signed links, asynchronous jobs with signed webhooks, bulk capture for up to 100 URLs per call, a usage API, and an OpenAPI specification. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.

Plans include 1,000 screenshots a month free with no card; paid plans start at $5 for 3,000 screenshots. Every feature is on every plan. Create a free ScreenshotNeo account.

13. FAQ

Does viewportSize.width guarantee the image width?

No. It sets the browser viewport. The saved bitmap width is determined by the captured region, so set clipRect.width when an exact output width matters.

Should viewport and clip rectangle always match?

They should match when you want a viewport-sized screenshot. Keep them different when you intentionally want a crop from a larger layout viewport.

Why is my PDF still the wrong size after fixing the PNG?

PDF dimensions come from paperSize. Configure and verify that property separately.

Can transparency cause a width mismatch?

No. Transparency affects the background and appearance. Inspect the file’s pixel dimensions to diagnose width.

What is the fastest hosted alternative?

Use ScreenshotNeo’s one-call API when you want hosted capture, consent and popup cleanup, verdict-based billing, and MCP tools for AI agents.