167

Box Unfold Scroll

£7
or the whole Vault, £99

A pinned, printed product carton built from CSS 3D panels that unfolds into its flat dieline as you scroll: the tuck lid opens, a real-time three.js glass jar lifts out turning in 3D, and every flap, side and the glue tab hinges flat with card-stock overshoot.

ScrollTrigger Lenis advanced
7 more details
3d-transformspinningscrubvelocity-reactivepaper-foldwebgl-shaderprogressive-enhancement

Released

About this effect

A packaging reveal that goes further than turning a box around. A closed, printed carton sits pinned in the frame, built from thirteen real CSS 3D panels joined at their fold lines. As the visitor scrolls, the tuck lid lifts and swings back and a glass jar of chili crisp rises out of the opening. The jar is a real-time three.js model: clear glass with a shoulder and neck threads, dark oil and flakes inside, a ribbed black cap and a printed label that wraps right round it. It turns as it climbs out, then swings round to face you with a small overshoot as it glides forward to stand in front.

Read the full effect overview

Meanwhile the dust flaps, side panels, back, glue tab and base hinge open one after another until the carton lies flat as its dieline, with the fold lines dashed in and every panel's print readable. Each panel is shaded from its true angle to the light, so the box reads as card rather than flat cards in space, and each hinge swings a few degrees past flat before settling back. A fast flick makes mid-fold panels and the jar lag and flap, then spring back once scrolling stops. Scrolling up folds the carton shut around the jar again.

Box Unfold Scroll - GSAP animation effect preview

What's included

9 items
  • A real carton net in HTML and CSS: front, two sides, back, lid with tuck flap, base with tuck flap, four dust flaps and a glue tab, each a plain element you can fill with any print
  • A real-time three.js glass jar built in code: lathed glass with transmission and reflections, a volume of chili crisp inside, a knurled cap and a wrap-around printed label
  • The jar's canvas lives inside the carton's CSS 3D world, so the carton's walls hide it while it is inside the box
  • One scroll-scrubbed sequence: lid opens, the jar lifts out turning in 3D and settles in front, then the panels unfold in order to a flat reverse tuck end dieline
  • Per-panel lighting computed from each panel's real 3D orientation, with darker unprinted board on the inside faces
  • Card-stock hinges: a slow start off the crease, a small swing past flat and a settle
  • Velocity flutter: a damped spring makes mid-fold panels and the jar lag behind a fast scroll and flap back when it stops
  • The jar redraws only when its pose changes, never while idle, and disposes its renderer and GL context on teardown
  • The flat dieline is the stylesheet's default and the jar has a baked render of itself, so reduced motion, no-JS and no-WebGL visitors see the finished net and product

Perfect for

5 use cases
  • Packaging design studios showing a dieline and the finished pack in one scroll
  • Food, drink and cosmetics brands revealing what is printed on every side of the box
  • Product launch pages where the unboxing is the story
  • Print and packaging suppliers explaining how a folding carton is constructed
  • Portfolio case studies for brand identity and packaging work

How it works

2 sections

The carton is a tree of hinged panels: the front is the root and every other panel folds from its parent's edge, so the whole box is ordinary HTML you can print onto. A pinned ScrollTrigger scrubs one sequence in which the lid, the product, the flaps, the sides, the back, the glue tab and the base each have their own slot, while the camera eases from a three-quarter pack shot to a straight-on view of the dieline. Lighting follows the geometry: each face is shaded by how far it is turned from the light, so the box reads as card at every angle.

The jar is a three.js scene drawn into a small canvas that travels inside the carton's 3D space, which is how the carton's walls can hide it. The scroll position sets the jar's turn and viewing angle, and it is redrawn only when those change. GSAP owns the pin, the scrub and the teardown; three.js draws the jar. Without WebGL, or under reduced motion, a render of the same jar stands in, and the flat layout is plain CSS, so the unfolded state needs no JavaScript at all.

Plugins ScrollTrigger
Difficulty Advanced
Smooth scroll Lenis integration
Includes HTML + JS + CSS source, documentation, AI setup prompt, lifetime updates

Lighthouse, as measured

Google Lighthouse on this effect's demo, from the latest scan. A measurement of the demo as shipped, not a promise for your page.

Accessibility
100
Best practices
100

No performance score, on purpose. That figure depends on how you deploy: your server's compression and caching, your CDN, the connection and the device doing the test, none of which the code controls. The same page can score very differently on two consecutive runs, so measure it where it will live.

The Vault opens this source and the whole library.

Each product gives you the HTML, CSS and JavaScript source, the full documentation and an AI setup prompt, licensed for unlimited personal and commercial projects. Every paid product is tested for desktop and mobile layouts, reduced-motion handling, animation cleanup and keyboard controls wherever there is something to operate.

One payment, over 300 products, and future additions to the library.

Documentation

Quick Start

1. Add to your HTML <head>:

In your downloadThe working code for this step, in index.html.

2. Add before the closing </body> tag:

In your downloadThe working code for this step, in index.html.

3. Add the carton to your <body>. The full printed demo carton is in index.html; this is its skeleton. Every panel has an outer (printed) face and an inner (board) face, each ending in a shade element:

In your downloadThe working code for this step, in index.html.

Using It With Your Own Design

What the effect needs from your markup: one [data-carton] scene holding a [data-carton-world], and inside it the thirteen [data-carton-panel] elements nested exactly as above. The nesting is the hinge structure: each panel folds from its parent's edge, so moving a panel to another parent changes the box. The panel names (front, lid, tuck, bottom, btuck, sideR, sideL, back, glue, dustTR, dustTL, dustBR, dustBL) select each hinge's fold angle and timing. Each panel's direct children include an [data-carton-face="out"] and an [data-carton-face="in"] face, and each face holds one [data-carton-shade] element for its lighting. [data-carton-product] and [data-carton-cue] are optional.

What is only the demo's styling: everything with a print or print__ class (the Sundra brand, the copy, the sun illustration, the barcode, the paper cards), the vivid orange, the cream paper, Mona Sans, the jar's print and the pill-shaped cue. Replace the contents of each outer face with your own artwork: HTML, an SVG or a background image cropped to that panel all work. The jar is restyled in code (see "The 3D Jar" below); to use a flat product image instead, set data-carton-jar="off" and point the <img> at a cutout with a transparent background. If it has a different shape, change the 1.645 height ratio on .carton__product to its height divided by its width.

CSS the effect depends on:

  • --u on .carton is the size of everything: one unit is the front panel's width, the depth is 0.82u, the height 1.2u, and the print is sized in em because the world's font-size is 1u. Change the proportions through --W, --D, --H, --T (tuck), --G (glue) and --F (dust flap), and move the netshadow path to match.
  • transform-style: preserve-3d on .carton__world and every .carton__panel, backface-visibility: hidden on the faces, and the transform origins on each panel's hinge edge. Do not put overflow, filter, opacity below 1 or clip-path on a panel or the world: any of them flattens the 3D. Clip and filter the faces instead, as the die-cut flap shapes do.
  • The panels' positions in the stylesheet are the flat dieline, and that is what no-JS and reduced-motion visitors see. Keep that layout legible on its own.
  • .carton__world is hidden under .has-js until the script has folded the box, so visitors never see the flat net flash first. The gate is lifted under reduced motion, and the script removes .has-js if GSAP fails to load.
  • The product's resting place is plain CSS too: --pw (width), --px and --py (bottom-centre anchor, measured from the front panel's top-left corner) and --pz-k (how far in front of the net it stands, in units). The script reads them, so moving the product only takes CSS.
  • --world-x and --world-y place the flat composition in the stage. The portrait media query stacks the product under the net and sizes --u so the net fits the screen width.

Options

Set these on the [data-carton] element.

Attribute Values Default Description
data-carton-runway 400 to 4000 (px) 1700 Scroll distance the pin lasts on pointer devices
data-carton-mobile-runway 300 to 3000 (px) 1300 Scroll distance the pin lasts on touch devices
data-carton-tilt -60 to 60 (deg) -22 Camera tilt on the closed box; negative looks down onto the lid
data-carton-turn -80 to 80 (deg) -34 Camera turn on the closed box; negative shows the right-hand side
data-carton-zoom 0.5 to 4 auto Scale of the closed box. By default it fills a little over half the stage
data-carton-flutter 0 to 3 1 Strength of the velocity flutter. 0 turns it off
data-carton-scrub 0 to 3 (s) 0.8 ScrollTrigger scrub smoothing
data-carton-static true or 0 to 1 unset Render a fixed progress with no pin and no scroll, for a still or a print page. true shows the flat dieline
data-carton-jar off unset Skip the three.js jar and show the <img> as the product

Changing the Fold Order

The order in which panels open is a schedule at the top of script.js: each hinge has a closed angle, a start and a duration, as fractions of the whole scroll. Move the start values to reorder the sequence, or lengthen a duration to slow one panel down. Keep the tuck flap starting after the lid, so on the way back it folds down before the lid closes over it, and keep the product rising only after the top dust flaps have opened, so it never passes through them. The lid, camera and product timings sit next to it.

Accessibility

  • Reduced motion: no pin, no scroll runway and no smooth scrolling. The carton is shown flat as its dieline with the product presented, so every printed panel is readable. Switching the preference while the page is open swaps between the two modes.
  • No JavaScript, no WebGL or blocked CDN: the stylesheet's default layout is the flat dieline with the product, and the product is a baked render of the same 3D jar, so the content is never blank. Under reduced motion three.js is not downloaded at all.
  • Screen readers: the print is real text inside a labelled group, the barcode and the product image have text alternatives (the canvas itself is hidden from assistive technology), and the decorative illustration, edges and shadows are hidden. The scroll cue is decorative and hidden from assistive technology.
  • Keyboard: the effect follows the page scroll, so arrow keys, Page Down and Space drive it like any other scroll. There are no controls to focus.
  • Touch: touch scrolling drives the same sequence. Phones in portrait get the stacked layout with the net fitting the screen width; the smallest print (ingredients and nutrition) is proportionally small there, so keep critical copy large.

The 3D Jar

The jar is built entirely in code with three.js and drawn into a canvas inside the carton's CSS 3D world, so the carton's front wall still hides it while it is inside the box. It is a wide-mouth jar: a lathed glass body with a short shoulder and neck threads hidden under the cap, real glass (transmission, thickness, index of refraction and reflections of a small built-in studio), chili crisp filled right up into the shoulder in two layers (a dark packed mass, and in front of it chili skin, crisp garlic and seeds pressed against the glass with clear oil between), a knurled black cap nearly as wide as the body, and a label printed onto a canvas and wrapped right round the body. It turns as it rises out of the carton, swings round to face you as it glides to its place with a small overshoot, and flutters slightly with scroll speed.

Restyling it: everything is in JAR_STYLE near the top of script.js:

  • label: the kicker, brand, product name, heat rating, weight, the two side lines and the back panel text. The label waits for the web fonts before it is drawn, so it uses Mona Sans like the carton.
  • colours: the label's print colour, ink and paper.
  • glassTint: the colour seen through the thickest glass.
  • cap: the cap colour.
  • contents: the oil, the dark packed crisp and the six flake colours (chili skin, its highlight, garlic, seeds, toasted bits and the deep red behind). The textures are drawn in drawCrispMass and drawCrispFlakes at 2048 across the jar's circumference (1024 on touch screens).

JAR_YAW_INSIDE, JAR_YAW_RISEN and JAR_ELEVATION set how far it turns on the way up and how steeply you look down on it.

Fallback: assets/img/jar-render.webp is a render of this same jar captured from the live scene, with a transparent background. It shows without JavaScript, without WebGL, under reduced motion, while three.js loads and if the download fails. After restyling, capture a new one: scroll the demo to the end, hide everything but the canvas, and save it as 640 x 1053 with alpha.

Performance: the jar redraws only when its pose changes (scroll progress, the turn, the flutter settling) and never while the page is idle or the scene is off screen. The canvas is only as big as the jar's frame, at a pixel ratio capped at 2. The glass samples the contents behind it without three's roughness blur, so the crisp stays sharp through it. If a device struggles, the first lever is data-carton-jar="off"; the next is a lower cap in createJar.

Dependencies

Required:

  • GSAP 3.12+ (the demo is built against 3.15.0)
  • ScrollTrigger plugin

For the 3D jar (optional):

  • three.js r180 (three@0.180.0) as an ES module through the import map in Quick Start, and WebGL. Without either, the baked render is shown. GSAP does not animate the jar: the carton's scroll progress sets its pose and three.js draws it.

Optional:

  • Lenis 1.3+ for smooth scrolling (the demo loads 1.3.17). Turn it off with data-smooth="off" on <html> or ?smooth=off in the URL. If the page already runs its own window.lenis, the effect uses it and never destroys it.

Browser support: current Chrome, Edge, Safari and Firefox. Registered custom properties (@property) let the script read --u as pixels; older browsers fall back to measuring the front panel.

Also in your download: the full source, worked examples, the events and programmatic API, and the class reference.

£7