Quick Start
1. Add to your HTML <head>:
Code snippet omitted: it ships with the download.
2. Add before closing </body> tag:
Code snippet omitted: it ships with the download.
3. Add the effect HTML anywhere in your <body>:
Code snippet omitted: it ships with the download.
Every layer except .szp-stage, .szp-card, and .szp-scene is optional; the script skips anything that is missing.
Options
All attributes go on the element carrying data-zoom-portal.
| Attribute | Values | Default | Description |
|---|---|---|---|
data-portal-distance |
Any number | 220 |
Pin length as a percentage of viewport height. Touch devices automatically use 80% of it so the gesture count stays reasonable |
data-portal-scrub |
Number or true |
1 |
Scrub smoothing in seconds. true hard-links the timeline 1:1 to the scrollbar |
data-portal-radius |
Any number | 28 |
Corner radius of the framed panel in px, morphed to 0 during the zoom |
data-portal-spread |
Any number | 46 |
How far each headline half travels, as a percentage of viewport width |
data-portal-blur |
Any number | 10 |
Peak blur on the parting headline halves, in px |
data-portal-reveal |
0.4 to 0.9 |
0.68 |
Where in the timeline the inner content starts revealing. Clamped so it always lands after the zoom |
Swapping the scene
The demo zooms into one photograph, but .szp-scene accepts anything: an image, a video, a WebGL canvas. Three things to keep:
- Size it to the viewport. Under
.szp-jsthe scene is100vwby100svh, centred in the card. That is what makes the world hold still while the frame grows, and what makes it land at exactly 1:1 when full-bleed. - Let the card do the cropping. The card has
overflow: hidden; the scene should not clip itself. - Ship the image at about twice the widest viewport you care about. The scene is full-bleed at the end of the zoom, so anything smaller is visibly soft exactly when it fills the screen.
Choose a photograph with real depth: an archway, a tunnel, a corridor, a gap between hills. The card starts as a portrait window onto the middle of the scene, so put the thing you are travelling towards near the centre. A wide photograph on a portrait phone gets cropped hard, which is why the demo nudges object-position on narrow viewports rather than letting the subject slide off the edge.
Image
assets/tunnel.webp is a bundled, optimised derivative (2400 × 1500, ~232 KB), not a hotlink, and it is used unmodified: no tint, no duotone, no colour overlay. Everything the reveal text needs for contrast is on .szp-scrim, which sits under the words rather than over the picture. Swap the file, or change the src, and nothing else has to move.
Credit: Two People in a Tunnel on Pexels, used under the Pexels licence (free for commercial use, no attribution required, modification permitted).
Accessibility
- Reduced motion:
prefers-reduced-motion: reducegets no pin and no zoom. The portal renders full-bleed with the headline, chip, and inner content all visible statically - No JavaScript: the default layout is a normal stacked flow with everything visible; hidden states only exist behind the JS-added
.szp-jsclass - Keyboard: the reveal content is hidden with
visibility: hiddenwhile off-stage, so its links and buttons are unfocusable until they are actually visible - Screen readers: decorative layers (
.szp-scene,.szp-grade,.szp-frame,.szp-scrim) arearia-hidden; give the section a meaningfularia-label
Dependencies
Required:
- GSAP 3.12+
- ScrollTrigger plugin
Optional:
- Lenis (smooth scroll; the script wires it up automatically when present)
Worked examples, the events and programmatic API, and the class reference ship with the download, alongside the full source.