Skip to the content

Pinned gallery

A horizontal gallery driven by the vertical scroll.

Become a member to unlock the full effect today.

Join now

Already a member? Sign in

Documentation

  1. Copy to Webflow

    Add Geist and Geist Mono to your site's fonts (Site settings, Fonts, Google Fonts) before pasting: Webflow removes any font your site has not installed.

  2. Add custom CSS

    The section's height is the engine's: pinned, it is the frame plus the length of the row, so give it none. And no parent of the section may cut its overflow with overflow: hidden, which stops the frame from pinning; use overflow-x: clip where a parent has to cut.
  3. Add custom JS

  4. Tune it

    Custom attributes
    Select the section on the canvas and open the Settings panel. Under Custom attributes, add a pair: the name on the left, the value on the right.
    Custom attributeWhat it does
    data-pinned-gallery Marks the section that holds the screen while its row runs. It takes no value: leave the value field empty. That section is what the engine makes as tall as the row is long, and what carries the settings. Without it on any section of the page, the embed marks the section holding each row (data-pinned-gallery-track).
    data-pinned-gallery-frame Marks the block inside the section that holds the screen: pinned, it is sticky, one screen tall, and clips what runs past its edges. Without it the parent of the track is taken, which pins the row alone, with no head or foot around it. [ Required ]
    data-pinned-gallery-viewport Marks the box that clips the row, the parent of the track. Under 768px it is the row the finger swipes. Without it the parent of the track is taken. [ Required ]
    data-pinned-gallery-track Marks the row that moves, the block holding the cards. Nothing is mounted without it, and the engine says so in the console. [ Required ]
    data-pinned-gallery-card Marks one card, a child of the track. Without it on any of them the engine takes the track's own children as the cards. [ Optional ]
    data-pinned-gallery-image Marks the picture of a card, the one that runs slower than its card. A card without it keeps a still picture, and only shrinks away from the reading line. [ Optional ]
    data-pinned-gallery-current Marks the number of the card on the reading line. The engine fills it with a rolling strip of every number. Without it there is no current number. [ Optional ]
    data-pinned-gallery-total Marks the total of the counter, which the engine writes as the card count. Without it there is no total. [ Optional ]
    data-pinned-gallery-bar Marks the fill of the progress rule, which the engine grows from the left as the row goes. Without it there is no rule to fill. [ Optional ]
    data-pinned-gallery-state Written by the engine on the section: pinned, native under the native width, reduced under reduced motion. Never written by hand; it is what the stylesheet reads to pin the frame. [ Optional ]
    data-pinned-gallery-active Written by the engine on the card on the reading line. Never written by hand; the stylesheet reads it to put that card's number in ink. [ Optional ]
    Settings
    Every setting is a custom attribute too, added the same way and on the same section. An attribute always wins over the defaults written in the code.
    AttributeDefaultWhat it is
    data-pinned-gallery-speed 1.15 How much page scroll the row takes, in pixels of scroll per pixel the row travels.
    data-pinned-gallery-scrub 0.8 How long the row takes to catch up with the scroll, in seconds.
    data-pinned-gallery-drift 0.08 How far each picture travels inside its card, as a share of the card width, from one edge of the screen to the other.
    data-pinned-gallery-depth 0.06 How much a card one step from the reading line shrinks, as a share of its size.
    data-pinned-gallery-native 768 Width in pixels under which the row is a plain swipe row instead of a pinned one.

What it does

Pinned gallery is a scroll animation for Webflow and vanilla JavaScript, with no library. Also called horizontal scroll gallery, horizontal scroll or sticky horizontal scroll.

A horizontal gallery driven by the vertical scroll. The section holds the screen while its row of cards runs sideways, a counter reads the card in front ("03 / 08") and a rule fills as the row goes; past the last card the section lets go and the page scrolls on. Each picture runs a little slower than its card and is enlarged by as much, so it never shows an edge, and the cards away from the reading line are a little smaller, which gives the row its depth. No library: the hold is a sticky frame inside a section the engine makes exactly as tall as the row is long, and the row is one transform, so there is no spacer, no scroll trigger, and nothing for Webflow.destroy() to take away. Under 768px the row becomes a plain swipe row that snaps card by card, and under reduced motion a plain sideways row. Pattern type horizontal scroll, pinned gallery, scroll jacking without the jacking.

Updated

Questions

Mark the section with the custom attribute data-pinned-gallery, then inside it add data-pinned-gallery-frame, data-pinned-gallery-viewport, data-pinned-gallery-track and data-pinned-gallery-card on each card. Do not give the section a height: the engine makes it as tall as the frame plus the length of the row.

Yes. This horizontal scroll gallery uses no library and no ScrollTrigger: the hold is a position: sticky frame, and the row is one transform per frame. Webflow.destroy() has nothing to take away.

Under 768 pixels wide, the default of the native setting, nothing is pinned: the row becomes a plain swipe row that snaps card by card. Raise data-pinned-gallery-native and the swipe row takes over on tablets too. Under prefers-reduced-motion: reduce it is a plain row that scrolls sideways, with no pin and no snap.

Raise data-pinned-gallery-speed, the pixels of page scroll per pixel the row travels, 1.15 by default: higher and the row runs slowly while the page stays pinned for longer. data-pinned-gallery-scrub, 0.8 seconds by default, is how long the row takes to catch up with the scroll, and 0 locks it to the scroll frame for frame.

A parent of the section set to overflow: hidden becomes the scroll container of the sticky frame, so the frame scrolls away with the page. Switch that parent to overflow-x: clip. Check the stylesheet too: without pinned-gallery.css nothing is sticky.