# GIF Scroll Animation (`crawlerbros/gif-scroll-animation`) Actor

Generate an animated GIF that scrolls down a webpage.

- **URL**: https://apify.com/crawlerbros/gif-scroll-animation.md
- **Developed by:** [Crawler Bros](https://apify.com/crawlerbros) (community)
- **Categories:** Automation, Developer tools, Other
- **Stats:** 6 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## GIF Scroll Animation

Generate an animated GIF that scrolls down a webpage. The actor opens the URL in a headless Chromium browser, captures one frame per scroll step, then assembles the frames into a GIF using Pillow. The GIF is saved to the run's key-value store; a companion record with metadata + a public GIF URL is pushed to the dataset.

### What it does

You provide a webpage URL; the actor:

1. Renders the page in headless Chromium at the configured viewport size.
2. Scrolls the page in `scrollStepPx`-sized increments, capturing a screenshot per step (up to `maxFrames`).
3. Optionally downscales each frame, then encodes them as a single GIF with Pillow.
4. Writes the GIF binary to the key-value store under `output.gif`.
5. Pushes one dataset record with `{url, gifUrl, frameCount, width, height, fileSizeBytes, frameDelayMs, scrapedAt}`.

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `url` | string (required) | `https://apify.com` | Page to capture. Must start with `http://` or `https://`. |
| `viewportWidth` | integer | `1280` (320–2560) | Browser viewport width in pixels. |
| `viewportHeight` | integer | `720` (240–1440) | Browser viewport height in pixels. |
| `scrollStepPx` | integer | `250` (50–2000) | Pixels to scroll between captured frames. Smaller values → smoother animation but more frames. |
| `frameDelayMs` | integer | `200` (50–5000) | Per-frame delay encoded into the GIF. |
| `maxFrames` | integer | `60` (2–300) | Hard cap on captured frames so very tall pages don't run forever. |
| `downscaleFactor` | integer | `2` (1–8) | Resize each frame down by this integer factor before encoding. `1` = full resolution, `2` = half, `4` = quarter. Lower = sharper but bigger GIF. |
| `cookieWindowSelector` | string (optional) | – | CSS selector of a cookie-consent dismiss button (e.g. `button#accept-all`). Clicked after page load so the consent banner doesn't appear in every frame. |
| `waitToLoadPageMs` | integer | `0` (0–30000) | Extra wait (ms) after `networkidle` for async-loaded content (lazy images, animations) to settle before capture starts. |

#### Example input

```json
{
  "url": "https://apify.com/",
  "viewportWidth": 1280,
  "viewportHeight": 720,
  "scrollStepPx": 250,
  "frameDelayMs": 200,
  "maxFrames": 40,
  "downscaleFactor": 2
}
```

### Output

The dataset receives a single record per run:

```json
{
  "url": "https://apify.com/",
  "gifUrl": "https://api.apify.com/v2/key-value-stores/%3Ckvs-id%3E/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"
}
```

The GIF binary itself is stored under key `output.gif` in the run's default key-value store and is reachable at the public `gifUrl` shown above.

#### Output fields

- **`url`** — the source URL captured.
- **`gifUrl`** — public URL pointing to the rendered GIF in the key-value store.
- **`frameCount`** — how many frames were captured before reaching the bottom or `maxFrames`.
- **`width`** / **`height`** — final GIF dimensions in pixels (after `downscaleFactor`).
- **`aspectRatio`** — derived: `width / height` rounded to 3 decimal places.
- **`fileSizeBytes`** — encoded GIF size in bytes.
- **`frameDelayMs`** — per-frame delay used in the GIF.
- **`durationMs`** — derived: `frameDelayMs * frameCount` — total GIF duration in milliseconds.
- **`scrapedAt`** — ISO-8601 UTC timestamp.

### Use cases

- **Marketing previews** — generate a quick animated preview of a landing page for social media, slack messages, or PR demos.
- **Scroll-test recording** — visualise long pages for accessibility / visual-regression review.
- **Documentation screenshots** — capture a page-tour as a single embeddable GIF instead of multiple stills.
- **Visual diffs** — re-run on the same URL across deploys to compare scroll appearance over time.

### FAQ

**Does it need a proxy?**
No — the actor uses the run's default network. If you need to capture a page that's geo- or IP-restricted, configure proxy at the run level via Apify's run-options panel.

**How big can the GIF get?**
Pillow uses optimised palette quantisation and disposal=2 to keep frames small, but a 30-frame full-1280×720 capture is still ~8–12 MB. Use `downscaleFactor: 2` (default) to roughly quarter the size.

**Why does the GIF stop early?**

- Reached the bottom of the page (`scrollY + viewportHeight >= scrollHeight`).
- Hit `maxFrames`. Increase the limit if you have a very tall page.

**Why isn't it perfectly smooth?**
GIF can only encode 1×, 2×, 5×, 10× hundredths-of-a-second, and Pillow rounds to the nearest. For motion-graphics quality, render to MP4 instead (this actor doesn't do video).

**How do I get just the GIF without the dataset record?**
Run the actor and download `output.gif` from the run's key-value store (the URL is in the dataset record's `gifUrl`).

**The page didn't render — what happened?**
Some pages block headless Chromium with bot challenges. The actor emits a sentinel record `{type: "gif_scroll_error", reason: "capture_failed", ...}` rather than crashing. Try the page in a normal browser first to confirm it isn't paywalled or geo-blocked.

# Actor input Schema

## `url` (type: `string`):

The URL of the webpage to capture.

## `viewportWidth` (type: `integer`):

Browser viewport width.

## `viewportHeight` (type: `integer`):

Browser viewport height.

## `scrollStepPx` (type: `integer`):

How many pixels to scroll between each captured frame.

## `frameDelayMs` (type: `integer`):

Per-frame delay encoded into the GIF.

## `maxFrames` (type: `integer`):

Hard cap on frames captured (protects very-long pages from runaway runs).

## `cookieWindowSelector` (type: `string`):

CSS selector of a cookie-consent dismiss button to click after page load (e.g. button\[aria-label="Accept all"]). Skipped if not found.

## `waitToLoadPageMs` (type: `integer`):

Additional milliseconds to wait after networkidle before capturing the first frame. Use for sites with async/sliding content.

## `downscaleFactor` (type: `integer`):

Resize each frame down by this integer factor before encoding (1 = no resize, 2 = half, 3 = third). Lower factors → larger but sharper GIF.

## Actor input object example

```json
{
  "url": "https://apify.com/",
  "viewportWidth": 1280,
  "viewportHeight": 720,
  "scrollStepPx": 250,
  "frameDelayMs": 200,
  "maxFrames": 60,
  "waitToLoadPageMs": 0,
  "downscaleFactor": 2
}
```

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "url": "https://apify.com/"
};

// Run the Actor and wait for it to finish
const run = await client.actor("crawlerbros/gif-scroll-animation").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "url": "https://apify.com/" }

# Run the Actor and wait for it to finish
run = client.actor("crawlerbros/gif-scroll-animation").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "url": "https://apify.com/"
}' |
apify call crawlerbros/gif-scroll-animation --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,crawlerbros/gif-scroll-animation"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/Q3cQdP08jvxr6wEvQ/builds/s08kcaU7nNqECwCMf/openapi.json
