DEV Community

Cover image for Rendering Full-Page GIF Scroll Animations with Headless Chromium
Crawler Bros
Crawler Bros

Posted on Fully Autonomous

Rendering Full-Page GIF Scroll Animations with Headless Chromium

Capturing Dynamic UI Flows Without Manual Screen Recording

Static screenshots miss critical dynamic context when documenting long landing pages, reviewing front-end visual regressions across deployments, or building automated visual site tours. Recording screen captures manually for every pull request or site audit introduces variable scroll speeds, inconsistent mouse movements, and non-standard browser viewports.

Automating this process requires a rendering engine that opens a browser, standardizes the viewport, handles asynchronous elements like sticky navigation bars and cookie compliance popups, and captures frame-by-frame steps as the page scrolls down. The GIF Scroll Animation Actor automates this pipeline by running headless Chromium, capturing screenshots at deterministic pixel intervals, and assembling the frames into an optimized GIF file using Python's Pillow library.

How the Frame-by-Frame Capture Pipeline Operates

The capture process follows a sequential workflow designed to deliver repeatable frame pacing and consistent visual output:

  1. Browser Initialization: Headless Chromium launches at the configured viewportWidth and viewportHeight (defaulting to 1280x720 pixels).
  2. DOM and Consent Handling: The actor navigates to the target url and waits for networkidle. If configured, waitToLoadPageMs pauses execution for extra milliseconds to let lazy-loaded images, canvas animations, or web fonts render. If cookieWindowSelector is defined (such as button#accept-all), the browser clicks the element to clear overlay banners before frame capture starts.
  3. Incremental Scroll and Capture: The browser scrolls down by scrollStepPx increments (defaulting to 250 pixels). After each step, a frame is captured. This continues until the browser reaches the bottom of the page (scrollY + viewportHeight >= scrollHeight) or hits the defined maxFrames hard cap.
  4. Processing and Encoding: Each frame screenshot is downscaled by downscaleFactor to reduce final file size. Pillow quantizes the color palette and encodes the image array into a single animated GIF binary using frameDelayMs to define the delay between frames.
  5. Storage and Output: The raw GIF binary is stored under the key output.gif in the run's default key-value store. A companion metadata record is pushed to the run's default dataset.

JSON Input Configuration

You pass input parameters to control rendering resolution, scroll fidelity, and runtime caps. The example configuration below targets a landing page with quarter-resolution downscaling and explicit frame limits:

{
  "url": "https://apify.com",
  "viewportWidth": 1280,
  "viewportHeight": 720,
  "scrollStepPx": 250,
  "frameDelayMs": 200,
  "maxFrames": 40,
  "downscaleFactor": 2,
  "cookieWindowSelector": "button#onetrust-accept-btn-handler",
  "waitToLoadPageMs": 1000
}
Enter fullscreen mode Exit fullscreen mode

Dataset Output Record

When the execution finishes, the actor outputs a single dataset record containing spatial properties, durations, and direct storage keys:

{
  "url": "https://apify.com",
  "gifUrl": "https://api.apify.com/v2/key-value-stores/a1b2c3d4e5/records/output.gif",
  "frameCount": 28,
  "width": 640,
  "height": 360,
  "aspectRatio": 1.778,
  "fileSizeBytes": 482113,
  "frameDelayMs": 200,
  "durationMs": 5600,
  "scrapedAt": "2026-04-26T14:23:11+00:00"
}
Enter fullscreen mode Exit fullscreen mode

How to Run the Actor via the Apify API

You can trigger this actor programmatically from Python, Node.js, or cURL. Follow these steps to generate a GIF scroll animation via the HTTP API:

  1. Construct the API Request: Send a POST request to the actor's run endpoint using your Apify API token.
  2. Pass execution parameters: Supply the required target url along with your desired viewport dimensions and frame configuration in the request payload.
  3. Poll or fetch the dataset item: Once the run status changes to SUCCEEDED, read the output dataset item to extract the gifUrl.
import requests

APIFY_TOKEN = "your_apify_token_here"
ACTOR_ID = "crawlerbros~gif-scroll-animation"

run_input = {
    "url": "https://apify.com",
    "viewportWidth": 1280,
    "viewportHeight": 720,
    "scrollStepPx": 200,
    "frameDelayMs": 150,
    "maxFrames": 50,
    "downscaleFactor": 2
}

url = f"https://api.apify.com/v2/acts/{ACTOR_ID}/runs?token={APIFY_TOKEN}"
response = requests.post(url, json=run_input)
run_data = response.json()["data"]

print(f"Run started with ID: {run_data['id']}")
Enter fullscreen mode Exit fullscreen mode

To retrieve the raw binary without parsing dataset JSON, construct the Direct Key-Value Store URL using the store ID returned by the API:

https://api.apify.com/v2/key-value-stores/<KVS_ID>/records/output.gif
Enter fullscreen mode Exit fullscreen mode

Managing File Size, Frame Rates, and Execution Costs

Animated GIFs do not use inter-frame compression like modern video codecs (e.g., H.264 or VP9). Capturing dozens of full-resolution frames can result in heavy output files exceeding 10 MB.

Three main settings control the balance between smoothness and final output size:

  • scrollStepPx: A smaller value (e.g., 50) produces smoother scrolling motion but generates significantly more total frames.
  • downscaleFactor: An integer factor used by Pillow to resize frames prior to compilation. A factor of 1 preserves native full resolution, whereas a factor of 2 cuts frame width and height in half (reducing pixel count per frame by 75%).
  • maxFrames: Caps the frame total regardless of total page length. If a target page uses infinite scroll or extensive vertical layouts, this setting prevents runaway execution.

Cost Breakdown

The pricing model for the Actor is structured as PAY_PER_EVENT, alongside separate charges for platform usage based on your plan's standard rates:

  • Actor Start Event: Charged at $0.005 per GB of memory allocated to the run when execution begins.
  • Dataset Result Event: Charged at $0.002 per dataset item emitted (the single output record containing gifUrl and run metadata).

Discount tiers apply to the "result" event depending on your account tier: FREE ($0.002), BRONZE ($0.00167), SILVER ($0.00133), GOLD ($0.001), PLATINUM ($0.001), and DIAMOND ($0.001).

Operational Limits and Error Modes

If a page implements strict bot challenge barriers or blocks headless Chromium instances, the actor will emit a error record containing {type: "gif_scroll_error", reason: "capture_failed"} rather than crashing unhandled.

Note that this tool is designed exclusively for step-based screenshot stitching into rasterized animated GIFs; it does not capture native browser audio, encode MP4/WebM video formats, or support fluid variable-rate motion graphics capture for high-frame-rate web applications.


Source for the runs in this article: GIF Scroll Animation. The input schema there is authoritative; treat anything in this post that contradicts it as out of date.

Prices quoted above are this Actor's published pay-per-event rates on the Apify Store, read from the Apify platform API on 2026-09-24. Check the Actor page for the current rates.

Top comments (0)