Can wkhtmltoimage Take Scrolling Screenshots of Long Webpages?
wkhtmltoimage can produce a tall image of content already laid out, but it does not document scrolling to trigger lazy-loaded sections. Here’s what to use instead.
Short answer: wkhtmltoimage can produce a tall image when the page’s content is already laid out in the document. Its documented --height option sets screen height, whose default is calculated from page content. But the documented interface does not promise to scroll the page to trigger content that loads only when it enters the viewport. The Debian wkhtmltoimage manpage describes an HTML-to-image converter, not a scrolling screenshot workflow.
If your target page appends or reveals content only after scrolling, use browser automation that scrolls, waits for the page to respond, and then captures the result—or arrange for all required content to load before invoking wkhtmltoimage.
1. Tall page image versus scrolling screenshot
These are different operations:
- Tall capture: render the document’s existing layout into a single image. wkhtmltoimage may do this because its default screen height is calculated from page content.
- Scroll-triggered capture: move through the page so scroll listeners or viewport observers load or reveal more content, wait for that content, then capture. The documented wkhtmltoimage options do not establish that it simulates these scroll gestures.
A longer delay is not equivalent to scrolling. It can give ordinary post-load scripts time to run, but it does not guarantee that a site will reveal content that requires a scroll event. Pages with an internal scrolling panel or custom scrollbar also differ from pages that use the main document’s vertical scroll.
2. Capture a long page with wkhtmltoimage
For a page whose full content is already in the document layout, choose a width and allow a short JavaScript delay if the page performs ordinary post-load setup:
wkhtmltoimage --width 1280 --disable-smart-width --javascript-delay 2000 https://example.com/ page.png
--disable-smart-width makes the specified width strict. Without it, --width is a guideline. The delay is in milliseconds. This command does not scroll the page, and the two-second wait is not a readiness guarantee. Check the resulting image on the actual page.
Relevant wkhtmltoimage options
| Option | What it controls | What it does not do |
|---|---|---|
--width <px> |
Rendering screen width; treated as a guideline unless smart width is disabled. | Does not trigger lazy loading. |
--disable-smart-width |
Makes the selected width strict. | Does not set page height or scroll. |
--height <px> |
Sets screen height. The default is calculated from page content. | Does not simulate scrolling or cause scroll listeners to run. |
--javascript-delay <msec> |
Waits after page loading for JavaScript to finish. The library settings describe waiting for the interval or until JavaScript calls window.print(). |
Does not guarantee arbitrary scripts finish or scroll-triggered sections appear. |
--window-status <value> |
Waits for window.status to equal the supplied value. |
Only helps if the page sets that status as a reliable readiness signal; it does not scroll. |
--run-script <js> |
Runs additional JavaScript after page loading. | A single script is not a robust scroll-and-wait loop for dynamically appended content. |
--no-images, --disable-javascript |
Disable image loading or page JavaScript. | Usually unsuitable when the desired page depends on images or scripts. |
--load-error-handling, --load-media-error-handling |
Choose how page or media load failures are handled: abort, ignore, or skip. | Do not make missing content load successfully. |
See the manpage for the command-line options and the library settings reference for JavaScript delay and loading behavior.
3. When content needs scrolling: use an explicit browser workflow
Use a browser automation workflow when the site’s content depends on entering the viewport. The example below uses Python with Playwright. It opens the page, repeatedly scrolls the main document in viewport-sized steps, waits briefly after each step, stops when the document height stops growing, returns to the top, and saves a full-page screenshot.
Install Playwright and its Chromium browser first:
python -m pip install playwright
python -m playwright install chromium
Save this as capture_long_page.py:
import asyncio
from pathlib import Path
from playwright.async_api import async_playwright
URL = "https://example.com/"
OUTPUT = Path("page.png")
MAX_STEPS = 80
STEP_WAIT_MS = 700
async def main():
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
page = await browser.new_page(viewport={"width": 1280, "height": 900}, device_scale_factor=1)
response = await page.goto(URL, wait_until="domcontentloaded", timeout=60_000)
if response is not None and response.status >= 400:
raise RuntimeError(f"Page returned HTTP {response.status}: {URL}")
previous_height = 0
stable_rounds = 0
for _ in range(MAX_STEPS):
height = await page.evaluate("document.documentElement.scrollHeight")
viewport = await page.evaluate("window.innerHeight")
if height <= previous_height:
stable_rounds += 1
else:
stable_rounds = 0
if stable_rounds >= 3:
break
previous_height = height
await page.evaluate("window.scrollBy(0, window.innerHeight)")
await page.wait_for_timeout(STEP_WAIT_MS)
await page.evaluate("window.scrollTo(0, document.documentElement.scrollHeight)")
# Let content triggered near the final scroll settle, then capture from the top.
await page.wait_for_timeout(STEP_WAIT_MS)
await page.evaluate("window.scrollTo(0, 0)")
await page.screenshot(path=str(OUTPUT), full_page=True)
await browser.close()
print(f"Saved {OUTPUT}")
asyncio.run(main())
Run it with python capture_long_page.py. This is a practical starting point, not a universal loader detector: tune the step wait and maximum steps for the site. It scrolls the main document. If the feed is inside a nested element with overflow: auto, target that element and scroll it instead. Some pages require a click, authentication, a particular consent choice, or a page-specific readiness signal before more content is available.
Make the scrolling workflow more dependable
- Use a page-specific completion condition when available, such as a known “load more” button disappearing or an expected final item appearing.
- For infinite feeds, set a hard maximum step count and decide what “complete” means. A feed may never reach a natural end.
- Wait for a meaningful selector or state change where possible. A fixed sleep is simple but can be too short on a slow response and waste time on a fast one.
- Capture only after the final scroll-triggered content and images have had time to render. Inspect the bottom of the output for truncation or blank placeholders.
4. Choose the right approach for the page
| Page behavior | Recommended approach | Key check |
|---|---|---|
| All content is present in the document at load | wkhtmltoimage with a suitable width; optionally use a short delay. | Confirm the output height and inspect the bottom. |
| Images load as they approach the viewport | Explicit scrolling with pauses, then capture. | Check whether image placeholders became actual images. |
| Sections append after a scroll event | Scroll repeatedly and wait for the page to grow or for a known item to appear. | Use a stop condition and a maximum step count. |
| Content lives in a nested scrolling panel | Scroll the panel itself using browser automation. | Identify the element that owns the scrollbar. |
| Page uses a site-specific readiness signal | Wait for that signal before capturing. | Do not assume a generic delay means the page is ready. |
The project’s usage documentation calls out viewport-size considerations for custom scrollbars and CSS overflow. A project issue also reports a particular case where JavaScript page setup did not complete as expected despite timing options; treat it as evidence that behavior can vary by page and build, not as proof that all captures fail: issue #2142.
5. Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| The image ends before the page’s visible bottom. | The content is in a nested scrolling region, the page layout changed after capture, or this build did not size the capture as expected. | Inspect the page’s scrolling structure. Try a strict width and compare output; use explicit browser scrolling for dynamic content. |
| Lazy images or sections are missing. | They load only when scrolled into view; waiting alone did not trigger them. | Scroll through the relevant region, wait for content, then capture. Verify the image afterward. |
Increasing --javascript-delay changes nothing. |
The missing behavior depends on a scroll event or readiness condition rather than elapsed time. | Use explicit scrolling or a page-specific status/selector condition. A larger delay cannot create a scroll event. |
| Capture width is unexpectedly different. | --width is only a guide unless smart width is disabled. |
Add --disable-smart-width and set the intended width. |
| Content in an overflow panel is absent. | The panel scrolls independently from the main document. | Scroll the panel element explicitly; main-page scrolling will not necessarily move it. |
| Output is blank or incomplete after scripts run. | Page setup may not have completed, a resource may have failed, or the page’s behavior may differ in the installed build. | Enable JavaScript debugging, inspect load errors, test a page-specific readiness signal, and compare with a real browser automation capture. |
| Automation keeps scrolling without finishing. | An infinite feed has no natural end or height keeps changing. | Use a maximum step count and define a stopping condition such as a target item or known end marker. |
6. Performance, reliability, and cost
A tall full-page image can consume substantial memory and produce a large file, especially at a wide viewport or high device scale. Keep the viewport only as wide as the content requires, avoid an unnecessarily long fixed delay, and cap scroll iterations for feeds that may not end. If you need repeatable output, use stable page data and an explicit readiness condition; time-based waits alone can vary with network and script timing.
wkhtmltoimage is a local command-line converter, so there is no per-image ScreenshotNeo charge when you run it yourself; account for your own machine and maintenance time. The reliable choice depends on whether the page has already rendered everything at capture time. For scroll-triggered content, confirm the result rather than assuming that a successful command means every section appeared.
7. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. Make one GET request with the page URL; see the ScreenshotNeo API documentation for request 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)
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}`);
- Cookie banners are accepted and removed before capture, along with supported newsletter popups and chat widgets.
- Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed; response headers report the page verdict and billing status.
- An MCP server lets AI agents use
take_screenshot,get_page_info, andcapture_pdf. - The Free plan includes 1,000 screenshots each month with no card. Paid plans start at $5 for 3,000 screenshots.
Sign up for ScreenshotNeo’s free 1,000 screenshots per month, with no card required.
8. FAQ
Does --height tell wkhtmltoimage how far to scroll?
No. It sets screen height; the default is calculated from page content. It is not a scroll instruction.
Can --javascript-delay load every lazy section?
No. It waits after page loading. It cannot guarantee a section that requires scrolling or another page-specific trigger.
What if a long page uses an internal scrollbar?
Determine which element owns the scroll area. The main document and an overflow container can behave differently; automation may need to scroll the container itself.
How can I tell whether the capture is complete?
Inspect the bottom and any known lazy-loaded sections in the generated image. For automated capture, wait for a page-specific completion signal when possible.


