Scroll Fabric Unroll Bolt — Free HTML CSS JS Snippet
Scroll Fabric Unroll Bolt · Scroll · Plain HTML, CSS & JS · Live preview
Copy & paste
Source Code
<div class="wrap">
<div class="hint">Scroll ↓ to unroll the bolt of fabric</div>
<div class="table">
<div class="bolt-scene">
<div class="roll" id="roll">
<div class="roll-end"></div>
<div class="roll-core"></div>
</div>
<div class="fabric" id="fabric">
<div class="fabric-edge"></div>
<span class="fabric-label">Heritage Weave — 100% Cotton</span>
</div>
</div>
</div>
</div>* { box-sizing: border-box; }
body { margin: 0; font-family: 'Georgia', serif; background: #efe6d8; color: #4a3728; }
.hint { position: sticky; top: 12px; text-align: center; font-size: 13px; letter-spacing: 0.05em; color: #8a6a4a; z-index: 4; padding: 10px; font-style: italic; }
.wrap { padding-bottom: 30vh; }
.table { height: 260vh; display: flex; align-items: flex-start; justify-content: center; padding-top: 8vh; background: linear-gradient(180deg, #efe6d8 0%, #e6d8c2 100%); }
.bolt-scene { position: sticky; top: 20vh; width: min(90vw, 640px); height: 200px; display: flex; align-items: center; }
.roll { position: relative; width: 56px; height: 200px; flex-shrink: 0; z-index: 2; filter: drop-shadow(4px 6px 10px rgba(74,55,40,0.35)); }
.roll-core { position: absolute; inset: 0; border-radius: 28px; background: linear-gradient(90deg, #6b4a2f 0%, #9a6b3f 35%, #c9925a 55%, #8a5a35 80%, #5c3d24 100%); }
.roll-end { position: absolute; left: -6px; top: 0; width: 22px; height: 200px; border-radius: 50%; background: radial-gradient(circle at 35% 35%, #d8a96b, #6b4527 75%); border: 2px solid #4a3320; }
.fabric { position: relative; height: 190px; width: 4%; overflow: hidden; background:
repeating-linear-gradient(45deg, #b5493f 0 10px, #c65c4e 10px 20px),
repeating-linear-gradient(-45deg, rgba(255,255,255,0.08) 0 6px, transparent 6px 14px);
box-shadow: inset 0 0 0 3px rgba(74,55,40,0.25), 2px 4px 14px rgba(74,55,40,0.3);
border-radius: 0 4px 4px 0;
}
.fabric-edge { position: absolute; inset: 0; background: repeating-linear-gradient(0deg, rgba(74,55,40,0.15) 0 2px, transparent 2px 18px); }
.fabric-label { position: absolute; bottom: 10px; left: 16px; font-size: 13px; letter-spacing: 0.06em; color: #fdf3e7; text-shadow: 0 1px 2px rgba(0,0,0,0.4); white-space: nowrap; opacity: 0; }gsap.registerPlugin(ScrollTrigger);
var fabric = document.getElementById('fabric');
var roll = document.getElementById('roll');
var label = document.querySelector('.fabric-label');
var tl = gsap.timeline({
scrollTrigger: {
trigger: '.table',
start: 'top top',
end: 'bottom bottom',
scrub: 0.5,
},
});
tl.to(fabric, { width: '82%', ease: 'none', duration: 1 }, 0)
.to(roll, { rotation: 900, ease: 'none', duration: 1, transformOrigin: '50% 50%' }, 0)
.to(roll, { width: 34, ease: 'none', duration: 1 }, 0)
.to(label, { opacity: 1, duration: 0.15 }, 0.3);Extend with AI
Take This Snippet Further With AI
The AI prompt for this one is for Patreon supporters ($3 a month), along with a site with no ads. Supporters enter the passcode once and it is remembered in this browser.
What's included
Features
About this UI Snippet
Scroll Fabric Unroll Bolt — Scrubbed Width Growth, Roll Rotation & Sticky Staging

A textile-shop-style scroll effect: a rolled bolt of fabric sits at the left of a sticky stage, and as the page scrolls, a patterned strip grows out from it like cloth being unrolled across a cutting table. Pair with Scroll Parallax Layers for a complementary scrub technique, or Scroll Progress if you want an explicit completion indicator alongside it.
A sticky stage inside a tall section
The outer .table element is 260vh tall — it defines how much scroll distance the unroll takes. Inside it, .bolt-scene is position: sticky; top: 20vh, so it holds its position on screen for the duration of that scroll distance without GSAP's pin. This snippet intentionally uses pin: false on its ScrollTrigger — sticky positioning does the visual staging instead, which is lighter weight and avoids GSAP inserting spacer elements.
Growing width, not translateX
The fabric strip starts at width: 4% and is tweened to width: 82% with ease: none inside a scrubbed timeline. Because scrub is a number, the width tracks scroll position directly — scrolling up shrinks the fabric back into the roll exactly as it grew out.
Selling the unroll with rotation and shrink
Two more tweens run in parallel on the same timeline position (0): the roll rotates up to 900° (transformOrigin: '50% 50%') to look like it is spinning as cloth pays out, and its width tweens down from 56px to 34px, implying the roll is getting thinner as material leaves it. All three tweens share the same scrub-driven timeline, so they stay perfectly in sync in both scroll directions.
Pattern via layered repeating-linear-gradient
The fabric texture is pure CSS — two stacked repeating-linear-gradient backgrounds at opposing angles create a woven plaid-like pattern with no image asset, so it scales crisply as the strip's width changes.
Label fade-in as a milestone
The fabric label crossfades in via a short tween placed at timeline position 0.3, appearing only once enough fabric has unrolled to read comfortably — a simple example of layering a discrete "reveal" on top of a continuous scrub.
Step by step
How to Use
- 1Scroll through the sectionScroll down slowly — the fabric strip grows out from the roll, the roll spins and thins, and the label fades in partway through.
- 2Scroll back upThe whole sequence reverses smoothly — the fabric retracts, the roll unspins, and the label fades out.
- 3Change the fabric patternEdit the repeating-linear-gradient values on .fabric in the CSS panel to swap in stripes, checks, or a different colorway.
- 4Adjust unroll distanceChange the height of .table (currently 260vh) to make the unroll take more or less scroll distance.
- 5Tune the final fabric lengthChange the width: "82%" target in the JS panel to make the unrolled strip longer or shorter relative to the stage.
- 6Export in your formatClick "HTML" for a standalone file, "JSX" for React, or "Tailwind" for a React + Tailwind version.
Real-world uses
Common Use Cases
Got questions?
Frequently Asked Questions
Sticky positioning achieves the same "stays in view while scrolling through this section" effect without GSAP inserting a pin-spacer element, which keeps the DOM simpler for an effect like this that does not need pin's more advanced pinning controls.
The width tween uses ease: "none" inside a scrub-driven timeline, so GSAP maps scroll progress directly and linearly to the width value — no easing curve means no lag between scroll position and fabric length.
Two repeating-linear-gradient backgrounds are layered at opposing 45-degree angles with different colors and opacities, producing a plaid-like weave purely in CSS that stays crisp at any element size.
All three tweens are placed at the same timeline position (0) in one scrubbed GSAP timeline, so they progress in lockstep with scroll — this is what makes the roll look like it is actively unspooling the fabric rather than the two effects happening independently.
Yes — change the height of the .table element. A taller section spreads the same 0-to-1 animation across more scroll distance, making it feel slower; a shorter one speeds it up.