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.

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 capture a CSS animation in PhantomJS, open the page, wait for the animation to reach the desired point, and call page.render(). A timer is adequate for an approximate moment. For repeatable output, use page.evaluate() to place the element in a known state before rendering. PhantomJS uses a legacy WebKit engine and its project says development is suspended, so verify animation behavior on the exact build you run.

Choose between timing and state control

page.render() records the page as it exists when the method executes. The callback from page.open() tells you that loading has completed; it does not identify a CSS-animation frame. You therefore need to decide whether elapsed time or an explicitly controlled page state is more important.

Use a delay for an approximate frame

After a successful load, schedule the render with setTimeout(). The delay is measured from the callback, so it is only an elapsed-time approximation. Network resources, fonts, application code and animation start times can make the visible result vary.

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

Set the animation state for repeatable captures

Run code in the page with page.evaluate() before rendering. That function executes in the page context, where it can find the target element and change its styles or other state. PhantomJS requires arguments and return values crossing this boundary to be simple JSON-serializable values; DOM nodes and closures do not cross it. Use the exact CSS property and behavior supported by your PhantomJS/QtWebKit build rather than assuming modern browser compatibility.

Minimal PhantomJS script with a timed capture

Save this as capture.js, then run it with the PhantomJS executable. The one-second delay below is an example to tune for your page, not a universal animation setting.

var page = require('webpage').create();
page.viewportSize = { width: 1024, height: 768 };

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

  // Tune this delay against the target animation.
  setTimeout(function () {
    page.render('capture.png');
    phantom.exit();
  }, 1000);
});

The viewport is set before navigation so layout and responsive breakpoints are established consistently. Rendering supports image formats documented by PhantomJS, including PNG, JPEG and GIF, as well as PDF output; the filename extension selects the normal output format. The quick-start workflow also requires phantom.exit() after work is complete, otherwise the process can remain alive.

Control an animation before rendering

The following pattern waits for the page to load, then uses page-context JavaScript to change a selected element. It first pauses the element and sets an animation delay. Whether a particular CSS animation property behaves as expected depends on the WebKit build, so inspect the output on your runtime.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
var page = require('webpage').create();
page.viewportSize = { width: 1024, height: 768 };

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

  var changed = page.evaluate(function () {
    var target = document.querySelector('.animated-panel');
    if (!target) {
      return false;
    }

    // Verify these properties in the PhantomJS build you deploy.
    target.style.webkitAnimationPlayState = 'paused';
    target.style.animationPlayState = 'paused';
    target.style.webkitAnimationDelay = '-2s';
    target.style.animationDelay = '-2s';
    return true;
  });

  if (!changed) {
    console.log('Animation target was not found');
    phantom.exit(1);
    return;
  }

  page.render('animation-state.png');
  phantom.exit();
});

A negative delay is one possible way to request a point in an animation, but it is not a PhantomJS guarantee. An alternative is to replace the animated element’s classes or inline styles with a static style representing the desired end state. That approach can be more reliable when you control the page source. If the page uses JavaScript rather than CSS to advance its state, expose a page function or data attribute that selects a deterministic state, then call it from evaluate().

Rank #2
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

Wait for assets, not only the animation clock

A successful page.open() callback is a useful load milestone, but a screenshot can still be wrong if web fonts, images, API data or a late script has not settled. Add a page-specific readiness check where possible. For example, wait until a marker element exists and then add a short delay for the animation:

function waitForMarker(page, selector, timeout, done) {
  var started = Date.now();
  (function poll() {
    var present = page.evaluate(function (s) {
      return !!document.querySelector(s);
    }, selector);
    if (present) {
      done(true);
      return;
    }
    if (Date.now() - started >= timeout) {
      done(false);
      return;
    }
    setTimeout(poll, 100);
  }());
}

var page = require('webpage').create();
page.viewportSize = { width: 1024, height: 768 };
page.open('https://example.com/', function (status) {
  if (status !== 'success') {
    phantom.exit(1);
    return;
  }
  waitForMarker(page, '.animation-ready', 10000, function (ready) {
    if (!ready) {
      console.log('Readiness marker did not appear');
      phantom.exit(1);
      return;
    }
    setTimeout(function () {
      page.render('ready-animation.png');
      phantom.exit();
    }, 500);
  });
});

This polling loop is deliberately page-specific. Pick a marker that means the content and assets required for your capture are present. It does not prove that every resource is loaded, nor does it make a legacy engine deterministic.

Capture only the animated region

Set page.clipRect when the output should contain a section instead of the entire viewport. The rectangle uses page coordinates and includes top, left, width and height.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
page.clipRect = {
  top: 120,
  left: 80,
  width: 640,
  height: 360
};
page.render('panel.png');

Set the viewport before opening the URL, because responsive layout can move or resize the target. If the element’s position changes during the animation, a fixed clip rectangle may no longer surround it; use a stable container or capture the full viewport instead.

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

A repeatable capture checklist

  1. Confirm the URL and PhantomJS build you will use in production.
  2. Create the webpage object and set viewportSize before calling page.open().
  3. Check that the open callback status is success; exit with an error otherwise.
  4. Wait for a page-specific readiness marker or a deliberately chosen elapsed interval.
  5. Use page.evaluate() to select the animation state when an approximate timer is not sufficient.
  6. Set clipRect only if a fixed capture region is appropriate.
  7. Call page.render(), check that the output was created, and call phantom.exit() afterward.
  8. Run the script repeatedly on the same build and compare images. Treat differences as a signal to inspect timing, assets and runtime support rather than assuming a frame-selection bug.

Troubleshooting PhantomJS animation screenshots

The screenshot is always the first frame

The render probably runs immediately after page.open()4 or before the animation starts. Move rendering into a timer or readiness callback. If the animation starts only after an interaction, trigger that interaction in page-context code before waiting.

The frame changes between runs

A delay selects elapsed time, not a frame number. Resource timing and animation start time can vary. Pause the target and apply a controlled state through evaluate(), or replace the animation with a static test style. Confirm that the properties you use are supported by the exact PhantomJS WebKit build.

The target selector is missing

Return a boolean or other simple value from evaluate() and fail the script when it is false. A selector may differ after responsive layout, be inside an unavailable frame, or be created by a script that has not run yet. Wait for a page-specific marker and inspect the rendered page at the chosen viewport.

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

Fonts or images are missing

Loading completion and visual readiness are different. Wait for the relevant element or application state, and add a modest page-specific delay. Check network-dependent content and font URLs in the page itself. Do not treat a larger fixed delay as a guarantee.

The process never terminates

Call phantom.exit() on every success and failure path, including timeout and missing-selector branches. Exit only after the render call has completed.

The animation property has no effect

PhantomJS is a legacy WebKit browser. Its official project page states that development is suspended until further notice: phantomjs.org. The documentation does not promise support for every CSS animation feature or vendor prefix. Test the exact build, simplify the page state, or move the capture to a maintained browser automation runtime when the required behavior cannot be made reliable.

Performance, reliability and format choices

A smaller viewport or clip rectangle reduces the amount of output to process, but it does not remove the cost of loading the page. Keep scripts short, avoid unnecessary polling frequency, and use a readiness condition rather than an arbitrary multi-second sleep. For batch jobs, record the URL, viewport, delay, selector result and exit status so a changed page can be diagnosed.

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

Choose PNG when you need lossless pixels or transparency, JPEG for photographic content and smaller files, and GIF only where its limitations are acceptable. PhantomJS’s render documentation describes output and quality options; consult it for the format-specific parameters: render API. The screen-capture guide covers viewport and clipping examples: Screen Capture with PhantomJS.

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

Relevant PhantomJS API references

  • Quick Start demonstrates opening a page, rendering it and exiting.
  • page.evaluate() documents page-context execution and serialization limits.
  • Page Automation describes clipRect and callbacks such as onLoadFinished and onRepaintRequested.

Or skip the browser setup

ScreenshotNeo provides a hosted screenshot API and MCP server, so you can request a capture without installing PhantomJS or maintaining a legacy browser. Its cleanup steps accept cookie and consent banners before capture and remove more than 60 known consent platforms, newsletter popups and chat widgets; each step can be disabled. Only clean shots are billed: bot checks or CAPTCHAs, blank pages, timeouts, failed loads and cache hits cost nothing, and response headers identify the page verdict and billing result.

One GET request returns PNG, JPEG, WebP or PDF. The API supports full-page captures with lazy images loaded, CSS-selector element captures, dark mode, device presets or custom viewports, retina scale, PDF paper and page-range controls, custom CSS and JavaScript, clicks, selector waits, delays, network-idle waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, chosen cache TTLs, signed image links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, a usage API and an OpenAPI specification.

For an animation, use a delay or page script that places the page in the desired state, then request the image. The parameter names used by many other screenshot APIs also work, which can simplify migration. Full option details are in the ScreenshotNeo documentation.

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.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
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)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

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. An MCP server exposes take_screenshot, get_page_info and capture_pdf to Claude, Cursor and other MCP clients, allowing AI agents to request captures directly. Create a free ScreenshotNeo account to get started.

FAQ

Can PhantomJS capture a CSS animation as a video?

The documented workflow captures a still image with page.render(). Capturing a sequence would require running the script repeatedly and managing filenames and timing yourself.

Does onRepaintRequested select the animation frame?

It is a documented page callback, but the available documentation does not define it as a deterministic CSS-animation frame controller. Use explicit page state or a measured delay and verify the result on your build.

What delay should I use?

There is no universal value. Choose a delay from the animation and page’s actual readiness requirements, then validate repeated runs.

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

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.