Pin a Video on Scroll in Webflow (Picture-in-Picture)

Advanced 35:35 webflowgsapscrollvariablesvideo

Build a picture-in-picture video that pins into the corner on scroll in Webflow, using GSAP scroll triggers, sticky positioning, and a single animated variable.

Key takeaways

  • A "sticks, then shrinks into the corner" effect is a discrete scroll trigger, not a scrub — use trigger actions with leave → play and enter-back → reverse, thresholds set to top/top.
  • Sticky positioning needs three properties together (position: sticky, a top offset, and a high z-index) — bundle them into one combo class and toggle that class with a set action.
  • Animate three transforms (moveX, moveY, scale) with a single number variable that goes 0 → 1: multiply each calc formula by the variable, and write scale as calc(1 - 0.6 * factor) so it eases from 1 down to 0.4.
  • Position with calc, not guesswork: translateX of 50vw - 50% - padding puts the right edge at the corner, and transform-origin: top right makes the shrink feel natural.
  • translateY plus sticky throws the final layout off — compensate with a bottom margin equal to the scaled height, cancel it with a negative top margin on the next element, then disable on tablet-down and for reduced motion.

Video chapters

  1. 00:00 Intro
  2. 01:04 Breaking down the section structure
  3. 03:38 Breaking down the interaction behavior
  4. 06:37 Building the interaction: controls and thresholds
  5. 11:30 Building the interaction: the required actions
  6. 19:43 Bringing Webflow variables into the setup
  7. 24:32 Set and Animate Variable actions
  8. 26:12 Vertical shift compensation
  9. 32:39 Responsiveness and accessibility
  10. 34:34 Outro

Here’s a small interaction that makes long-form content far more engaging: a video that follows the reader as they scroll, smoothly shrinking into the bottom-right corner like a picture-in-picture player. We’ll build it with Webflow’s GSAP scroll interactions, sticky positioning, a bit of calc, and one clever Webflow variable that keeps the whole thing clean.

The interesting part isn’t the effect — it’s the technique behind it: driving three separate transforms with a single animated variable, and compensating for the layout quirks that sticky positioning introduces. Nail those and this pattern becomes reusable anywhere.

How it works

It’s a discrete scroll trigger, not a scrub. We don’t want the video’s shape tied continuously to scroll position; we want it to change state once, when it crosses a threshold. So the scroll interaction on the video wrapper uses trigger actions (not scrub on scroll), with leave → play (fire when the wrapper starts leaving the viewport) and enter back → reverse (undo when it fully re-enters). The thresholds are set so the change happens when the top of the wrapper reaches the top of the viewport (end threshold: top/top).

Sticky is three properties, so make it a class. Making the video stick isn’t just position: sticky — it also needs a top offset (0) and a high z-index (100) so it stays above the content it scrolls over. Because these must change together, bundle them into a combo class (is-sticky) and flip it with a set action. (Keep a copy of the wrapper with that class in your style guide so Webflow doesn’t prune it.)

One variable drives three transforms. The final resting transform is a translateX, a translateY, and a scale(0.4), all written as calc expressions in a custom transform property (the style panel’s transform controls can’t take calc), with transform-origin: top right so it shrinks toward the corner. The horizontal position is calc(50vw - 50% - 2rem) (push to center of viewport, pull back half its own width, then the page padding); the vertical is calc(100vh - 40% - 2rem). Animating three separate variables with calc is fragile, so instead we create one number variable (transform-factor, 0 → 1) and multiply each formula by it: translateX((…) * factor), translateY((…) * factor), and — the neat trick — scale(calc(1 - 0.6 * factor)) so scale eases from 1 (factor 0) to 0.4 (factor 1). A single animate variable action from 0 to 1 runs all three.

Compensating for the sticky shift. A translateY alongside position: sticky throws the final layout too low. The fix is a bottom margin on the wrapper equal to its scaled height — calc(100vh - 768px * 0.5625 - 2rem), where 768px is the desktop wrapper width and 0.5625 is the 16:9 height ratio — then a negative top margin on the element below to cancel the gap it opens. (Margin percentages reference parent width, and the margin resolves before the scale, which is why the pixel form is used.) Finally, this effect makes no sense on small screens or for users who dislike motion, so conditional playback disables it from tablet down and when prefers-reduced-motion is set.

How to use it

  1. Set up the wrapper. Video in a wrapper with a max width, margin-inline: auto, aspect-ratio: 16/9, and transform-origin: top right. Create a combo class is-sticky with position: sticky, top: 0, z-index: 100 (keep a copy in the style guide).

  2. Add the transform variable. Create a number variable transform-factor (default 0). In the wrapper’s custom transform property, write translateX((50vw - 50% - 2rem) * factor) translateY((100vh - 40% - 2rem) * factor) scale(calc(1 - 0.6 * factor)).

  3. Build the scroll interaction. On the wrapper: scroll interaction, trigger actions, leave → play, enter back → reverse, end threshold top/top. Add a set action toggling the is-sticky class at 0s, and an animate variable action taking transform-factor from 0 → 1 (e.g. 0.3s, sine in-out).

  4. Compensate the layout. Add the bottom margin calc(100vh - 768px * 0.5625 - 2rem) to the wrapper, and a matching negative top margin on the next element.

  5. Guard it. In conditional playback, set no animation for tablet-and-down breakpoints and for prefers-reduced-motion. Publish and test on the live link.

Resources

Frequently asked questions

How do I make a video stick and shrink into the corner as I scroll in Webflow?
Use a GSAP scroll interaction on the video wrapper set to trigger actions (not scrub on scroll). Fire it when the wrapper leaves the viewport (leave → play) and reverse it when it re-enters (enter back → reverse), then toggle a sticky combo class and animate the move/scale transforms.
What's the difference between scrub on scroll and trigger actions?
Scrub on scroll ties the animation's progress directly to scroll position — a continuous interaction. Trigger actions fire the animation once when a threshold is crossed and let it play on its own. For a pin-into-corner effect you want trigger actions, since it's a discrete state change, not a scrubbed timeline.
How can I animate multiple transforms with Webflow variables?
Instead of one variable per transform, use a single number variable (e.g. transform-factor) that animates from 0 to 1, and multiply each calc-based transform by it. moveX and moveY become (formula * factor); scale becomes calc(1 - 0.6 * factor) so it goes from 1 to 0.4. Animating that one variable drives all three at once.
Why does my sticky video end up in the wrong place after scaling and moving it?
A translateY combined with position: sticky shifts the final layout. Compensate by adding a bottom margin to the wrapper equal to its scaled height (e.g. calc(100vh - 768px * 0.5625 - 2rem)), then cancel that gap with a negative top margin on the element below. The margin is applied before the scale, so it resolves to the right amount.

← Back to the course