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

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

To screenshot one <div> with Ruby and PhantomJS, let PhantomJS load the page, find the element in the browser DOM, read its bounding rectangle, and pass that rectangle to page.clipRect before calling page.render. Ruby can launch PhantomJS and check its result; the page and screenshot operations themselves are JavaScript, not a Ruby-native PhantomJS API.

This approach is useful for maintaining an existing PhantomJS workflow. PhantomJS is a legacy choice for new projects: its official homepage says development is suspended until further notice. Its QtWebKit rendering engine may behave differently from current browsers, particularly on modern CSS and JavaScript.

How the element screenshot works

PhantomJS does not provide a special “screenshot this div” method. Its documented page.clipRect property sets a rectangular region for page.render, using top, left, width, and height. Without a clip rectangle, rendering is not limited to that element. To make the rectangle follow a particular element when layout changes, query the DOM with page.evaluate and use the element’s bounding rectangle.

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

The work is divided between two runtimes:

  • PhantomJS JavaScript: opens the page, queries the selector, sets the viewport and clip rectangle, and writes the image.
  • Ruby: starts the PhantomJS executable with the script and arguments, then reports whether the process succeeded.

The element rectangle is measured in browser-page coordinates. The viewport affects layout, so choose dimensions that produce the layout you intend to capture. If the page moves, inserts content, or finishes loading images after the initial load event, wait for the relevant page-specific condition before reading the rectangle.

#1 Best Overall

Set up the PhantomJS script

Save this as capture-div.js. It accepts three arguments after the script name: the page URL, a CSS selector, and an output filename. The script checks that the page opened and that the element exists and has nonzero dimensions. It exits with a nonzero status on those failures so Ruby can detect them.

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

if (system.args.length < 4) {
  console.log('Usage: phantomjs capture-div.js URL SELECTOR OUTPUT');
  phantom.exit(2);
}

var url = system.args[1];
var selector = system.args[2];
var output = system.args[3];

// Set the viewport before opening the page because it affects layout.
page.viewportSize = { width: 1365, height: 900 };

page.open(url, function (status) {
  if (status !== 'success') {
    console.log('ERROR: page open failed: ' + status);
    phantom.exit(3);
    return;
  }

  var rect = page.evaluate(function (cssSelector) {
    var element = document.querySelector(cssSelector);
    if (!element) {
      return null;
    }

    var bounds = element.getBoundingClientRect();
    return {
      top: bounds.top + window.pageYOffset,
      left: bounds.left + window.pageXOffset,
      width: bounds.width,
      height: bounds.height
    };
  }, selector);

  if (!rect) {
    console.log('ERROR: selector not found: ' + selector);
    phantom.exit(4);
    return;
  }

  if (rect.width <= 0 || rect.height <= 0) {
    console.log('ERROR: element has zero or negative dimensions');
    phantom.exit(5);
    return;
  }

  page.clipRect = {
    top: rect.top,
    left: rect.left,
    width: rect.width,
    height: rect.height
  };

  var rendered = page.render(output);
  if (!rendered) {
    console.log('ERROR: render failed');
    phantom.exit(6);
    return;
  }

  console.log('Saved ' + output + ' (' + rect.width + ' x ' + rect.height + ')');
  phantom.exit(0);
});

The callback passed to page.evaluate runs in the page context. Its argument and return value cross a sandbox boundary, so pass simple data such as a selector string and return ordinary numbers and objects. Do not return a DOM node or depend on a closure from the PhantomJS process. The selector is evaluated with document.querySelector; use a valid CSS selector, such as #invoice or .product-card.

The example adds the current scroll offsets to getBoundingClientRect() to obtain page-position coordinates. That matters if a page has been scrolled before the rectangle is read. On pages that reposition content after load, add a page-appropriate wait before evaluating the selector; a fixed delay is not a universal readiness guarantee.

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

Run it from Ruby

Install PhantomJS separately and make sure the phantomjs executable is available on your PATH, or replace the executable name below with its full path. Save the following as capture.rb. Ruby’s Open3.capture3 accepts an argument array, avoiding shell interpolation of the URL, selector, and filename.

require 'open3'

phantomjs = ENV.fetch('PHANTOMJS', 'phantomjs')
script = File.expand_path('capture-div.js', __dir__)
url = ARGV.fetch(0) { abort 'Usage: ruby capture.rb URL [SELECTOR] [OUTPUT]' }
selector = ARGV.fetch(1, '#content')
output = ARGV.fetch(2, 'div.png')

stdout, stderr, status = Open3.capture3(
  phantomjs, script, url, selector, output
)

$stdout.write(stdout)
$stderr.write(stderr)

unless status.success?
  abort "PhantomJS failed with exit status #{status.exitstatus}"
end

unless File.file?(output) && File.size(output) > 0
  abort "PhantomJS reported success but no non-empty file was written: #{output}"
end

puts "Screenshot ready: #{File.expand_path(output)}"

Run it from the directory containing both files:

ruby capture.rb 'https://example.com' '#main-content' 'main.png'

Use a URL for a page you are authorized to access. Quote arguments in your shell so characters such as & in a URL are not treated as shell syntax. The Ruby program passes them as separate process arguments rather than building a shell command.

Choose the capture area and output deliberately

Element rectangle versus fixed rectangle

For a stable, known layout, assign literal values to page.clipRect. That is the simplest option, but it will not follow an element if the page layout changes. For a specific element, computing its bounds in the page is more adaptable. The clip rectangle is still a rectangle: if the element has rounded corners, transparent areas, or overlaps other content, the crop follows the bounds rather than the visual shape.

Viewport and page readiness

Set page.viewportSize before opening the URL. A different viewport can change responsive breakpoints, text wrapping, element position, and dimensions. The sample uses 1365 by 900 pixels as an explicit choice, not a universally correct size; change it to match the layout you need.

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

page.open reports whether the page opened, but successful opening does not prove that every asynchronous widget, font, lazy image, or client-side update has finished. If your target is populated after the load callback, add a wait for a known selector or readiness signal and only then query its bounds. A fixed delay may help with a known page, but can be too short on a slow run and waste time on a fast one. If the page uses animations, consider disabling them through page-specific CSS or capturing at a stable point.

Output format and size

page.render chooses the output format from the filename extension. The PhantomJS documentation lists PNG, JPEG, PDF, BMP, PPM, and GIF as formats depending on the Qt build; support is not guaranteed identically across builds. This example writes PNG. For a JPEG output, use a filename ending in .jpg or .jpeg and confirm that the installed build supports the desired format. The element’s measured width and height determine the crop size; the example does not enlarge the capture for retina output.

A very large element can produce a large image and take longer to render or consume more memory. If you only need a visible portion, use a smaller fixed rectangle or a different element/region. Do not assume that clipping fixes outdated rendering behavior: the underlying browser engine remains PhantomJS’s QtWebKit.

Troubleshoot common failures

Symptom Likely cause What to check or change
phantomjs: command not found The executable is not installed or is absent from PATH. Install or locate the executable, then set PHANTOMJS=/full/path/to/phantomjs when running Ruby.
page open failed The URL could not be loaded by the process, or the page load failed. Check the URL, network access, redirects, and whether the page is reachable from the machine running PhantomJS. The script exits rather than saving an unrelated crop.
selector not found The selector is wrong, the element is inserted later, or the page structure differs in this legacy engine. Verify the CSS selector and inspect whether the element exists at the time the callback runs. Wait for the page’s specific readiness condition if it is populated asynchronously.
Image is blank or the wrong area The element may be hidden, zero-sized, positioned differently at the chosen viewport, or still loading. Check the selector, computed layout, viewport, scroll position, and readiness timing. The bounding rectangle is taken when the script evaluates the page.
Image is clipped or unexpected in height The measured rectangle may not match the intended visual region, or the layout moved between measurement and rendering. Use a stable page state and inspect the returned rectangle dimensions. If the desired region is fixed, set an explicit clip rectangle instead.
Ruby reports failure although a file exists PhantomJS can emit a diagnostic or exit nonzero after a partial operation. Read the captured standard output and error output. Treat the exit status as authoritative, then remove or replace any partial file before retrying.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Maintenance and reliability considerations

The official PhantomJS homepage states that development is suspended until further notice. The project describes itself as a scriptable headless browser built on QtWebKit. For an existing script, pin and document the exact executable build used in your environment, keep a representative page in your regression checks, and verify the output after changing the operating system or runtime. These are prudent maintenance steps, not a guarantee of compatibility with modern websites.

Free tools Windows power users keep installed

One-click scans. No signup required.

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

Do not treat an opened page as proof that a current browser would render it the same way. If an essential page depends on newer JavaScript or CSS, first verify that the PhantomJS build can render it correctly; if not, an older engine may be the limiting factor rather than your clip rectangle. Hosted rendering is a separate operational choice: evaluate its access controls, data handling, terms, and maintenance requirements independently.

Or skip the browser setup

If you would rather request a screenshot through an API than install and maintain a local browser, ScreenshotNeo provides a website screenshot API and MCP server. Its one-call GET endpoint returns an image or PDF; for an element-specific capture, use its CSS-selector capture option.

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

See the ScreenshotNeo documentation for the API parameters and selector option. ScreenshotNeo accepts cookie and consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; those steps can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and responses indicate the page verdict and billing status in headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools for AI agents using Claude, Cursor, or another MCP client. The free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots.

Sign up for ScreenshotNeo free to get 1,000 screenshots a month with no card.

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

Frequently Asked Questions

Can I pass a DOM element directly to PhantomJS page.render?

No. Read serializable geometry from the page with page.evaluate, then assign that geometry to page.clipRect.

Does the script take a screenshot of the element’s visible shape?

It crops the element’s rectangular bounds, not a non-rectangular outline or an isolated composited layer.

Does PhantomJS provide an official Ruby screenshot API?

The documented workflow is PhantomJS JavaScript plus its command-line executable; Ruby can orchestrate that process.

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.

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