Skip to content
Back to skills

Web Screenshot

ASecurity

Take reliable full-page screenshots of web pages that use scroll-reveal animations (IntersectionObserver, opacity:0, fade-up, scroll-triggered transitions). Solves the common problem where sections appear blank or invisible in screenshots because CSS animations haven't triggered. Use when taking screenshots of any modern website (Astro, React, Next.js, Vue, etc.) with scroll animations, for QA review, visual comparison, or documentation. Supports desktop and mobile viewports.

  • 2 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 29, 2026
developmentjavascriptgojavabashreactvuenextjsnodedocumentation

Security analysis

A96/100
  • mediumInstalls packages at runtime which could introduce malicious dependencies

Pro scans all 2 files and shows the line behind each finding

Scanned September 29, 2026

npx -y skills add Arry8/openclaw-edge --skill web-screenshot --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Web Screenshot?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Web Screenshot
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/arry8-web-screenshot/badge)](https://www.skillsdirectory.com/skills/arry8-web-screenshot)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: web-screenshot
description: Take reliable full-page screenshots of web pages that use scroll-reveal animations (IntersectionObserver, opacity:0, fade-up, scroll-triggered transitions). Solves the common problem where sections appear blank or invisible in screenshots because CSS animations haven't triggered. Use when taking screenshots of any modern website (Astro, React, Next.js, Vue, etc.) with scroll animations, for QA review, visual comparison, or documentation. Supports desktop and mobile viewports.
---

# Web Screenshot

Takes reliable full-page screenshots of pages with scroll-reveal animations.

## The Problem

Modern websites commonly use IntersectionObserver-based animations (`.reveal`, `.fade-up`, `.animate-on-scroll`, etc.) that initialize elements with `opacity: 0` or `transform: translateY(30px)` and animate them in when scrolled into view. When taking a full-page screenshot without scrolling, all sections below the fold appear blank or invisible because the IntersectionObserver never fired.

This affects virtually every modern website framework (Astro, React, Next.js, Vue, Svelte) and CSS animation libraries (AOS, GSAP ScrollTrigger, Framer Motion, etc.).

## Solution

Inject JavaScript to force all hidden elements visible, trigger lazy-loaded images (both native and `data-src`), incrementally scroll through the page to fire IntersectionObservers, then take the screenshot.

## Usage: Browser Tool (Recommended)

**IMPORTANT**: The `fn` parameter in `browser(action="act", kind="evaluate")` does NOT support semicolons as statement separators. Always wrap multi-statement code in an IIFE: `(() => { ...; return 'done' })()`

### Step-by-step

```
1. browser(action="navigate", url="https://example.com/page")

2. browser(action="act", kind="evaluate", fn="(() => { document.querySelectorAll('[class*=reveal],[class*=fade],[class*=animate],[class*=scroll]').forEach(el => { el.style.opacity = '1'; el.style.transform = 'none'; el.style.visibility = 'visible'; el.style.transition = 'none' }); document.querySelectorAll('[style*=\"opacity: 0\"],[style*=\"opacity:0\"]').forEach(el => { el.style.opacity = '1'; el.style.transform = 'none' }); document.querySelectorAll('img[loading=lazy]').forEach(img => { img.loading = 'eager'; if (img.dataset.src) img.src = img.dataset.src }); return 'done' })()")

3. Incremental scroll in viewport-sized steps to trigger IntersectionObservers:
   browser(action="act", kind="evaluate", fn="(async () => { const h = document.body.scrollHeight; const step = window.innerHeight; for (let i = step; i <= h; i += step) { window.scrollTo(0, i); await new Promise(r => setTimeout(r, 300)) } return 'scrolled' })()")

4. Wait 2 seconds (exec: sleep 2)

5. Re-apply reveal injection (observers may have added new classes during scroll):
   browser(action="act", kind="evaluate", fn="<same reveal script as step 2>")

6. browser(action="act", kind="evaluate", fn="(() => { window.scrollTo(0, 0); return 'top' })()")

7. Wait 1 second

8. browser(action="screenshot", fullPage=true)  ← Desktop

9. Resize to mobile and repeat full reveal + scroll pass:
   browser(action="act", kind="resize", width=375, height=812)
   browser(action="act", kind="evaluate", fn="<same reveal script as step 2>")
   browser(action="act", kind="evaluate", fn="<same async scroll script as step 3>")
   Wait 2 seconds
   browser(action="act", kind="evaluate", fn="<same reveal script as step 2>")
   browser(action="act", kind="evaluate", fn="(() => { window.scrollTo(0, 0); return 'top' })()")
   Wait 1 second

10. browser(action="screenshot", fullPage=true)  ← Mobile

11. browser(action="act", kind="resize", width=1280, height=800)  ← Reset viewport
```

### What the Script Does

1. **Forces all reveal elements visible** — Selects elements with class names containing `reveal`, `fade`, `animate`, or `scroll` and overrides their opacity, transform, and visibility
2. **Catches inline-styled hidden elements** — Finds elements with `opacity: 0` in their inline style
3. **Triggers lazy images** — Changes `loading="lazy"` to `eager` and copies `data-src` to `src` for custom lazy-loading implementations
4. **Incrementally scrolls in viewport-sized steps** — Steps through the page one viewport height at a time with 300ms async pauses, ensuring every vertical band enters view and IntersectionObservers fire reliably
5. **Re-applies reveal after scroll** — Observers may add new hidden classes during scroll, so reveal injection runs again
6. **Scrolls back to top** — Returns to page start for a clean full-page screenshot
7. **Full mobile pass** — After resize, repeats the complete reveal + scroll + reveal cycle because responsive breakpoints often mount mobile-only blocks or reinitialize observers

### Compact One-Liner (Copy-Paste Ready)

For the reveal injection (steps 2/8):

```
(() => { document.querySelectorAll('[class*=reveal],[class*=fade],[class*=animate],[class*=scroll]').forEach(el => { el.style.opacity = '1'; el.style.transform = 'none'; el.style.visibility = 'visible'; el.style.transition = 'none' }); document.querySelectorAll('[style*="opacity: 0"],[style*="opacity:0"]').forEach(el => { el.style.opacity = '1'; el.style.transform = 'none' }); document.querySelectorAll('img[loading=lazy]').forEach(img => { img.loading = 'eager'; if (img.dataset.src) img.src = img.dataset.src }); return 'done' })()
```

## Usage: Standalone Script

For automation outside the browser tool, use the bundled script. Run from any directory — use the full path to the script:

```bash
# From the skill directory:
bash scripts/screenshot.sh <url> <output-dir> [slug]

# From workspace root (typical usage):
bash skills/web-screenshot/scripts/screenshot.sh <url> <output-dir> [slug]

# Examples:
bash skills/web-screenshot/scripts/screenshot.sh http://localhost:4321/my-page ./screenshots my-page
bash skills/web-screenshot/scripts/screenshot.sh https://example.com/page ./screenshots example
```

Outputs `{slug}-desktop.png` (1280px) and `{slug}-mobile.png` (375px).

**Requirements**: Node.js and Playwright. Install with:

```bash
npm install playwright
npx playwright install chromium
```

## Known Limitations

- **Google Maps iframes** may still appear blank (loads asynchronously via iframe)
- **Video backgrounds** won't play in static screenshots
- **CSS `animation-play-state`** — if the page uses CSS keyframe animations that are paused until scroll, add: `document.querySelectorAll('*').forEach(el => el.style.animationPlayState = 'running')` to the inject script
- **Canvas/WebGL elements** render normally but may show loading states

Files in this skill

  • SKILL.md6.5 KB
  • scripts/screenshot.sh2.8 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…