056

Scroll Morph Mask Video

£10 or the whole Vault, £99

A looping surf film that never stops playing, seen through a vector mask that scroll morphs from a mark into the letterforms of a word, then zooms through a letter to full bleed. Flick and the mask breathes: it inflates, the edge splits into an accent and a chalk fringe, and the footage speeds up.

ScrollTriggerMorphSVG Lenis advanced
7 more details
video-maskmorph-svgclip-pathpinningscrubvelocity-reactiveelastic

This demo reads better at your own screen size than in the frame below:

Updated

About this effect

A video hero where the film keeps playing and scroll changes only the shape you see it through. One real <video> element loops underneath a vector clip-path, and MorphSVG morphs that path along a sequence you author: a small mark, the letterforms of your word, then a zoom straight through one letter until its solid stroke fills the screen and hands off to your headline and call to action. Every frame of that exit is still the real letterform, just closer.

Read the full effect overview

The mask is alive rather than mechanical. At rest it breathes gently. Flick the wheel and it inflates and tilts with your scroll velocity, the edge splits into an accent and a chalk fringe, a displacement ripple runs along the cut, and the footage itself speeds up, before the whole thing settles back with an elastic overshoot.

Because the video plays rather than scrubs, it needs no special encoding: any muted, looping MP4 works, and the mask shapes are ordinary SVG paths you can export from Figma or Illustrator.

Scroll Morph Mask Video - GSAP animation effect preview

What's included

9 items
  • Real <video> playing continuously under a morphing SVG clip-path (no canvas, no frame sequence, no scrubbed encode)
  • MorphSVG morphs through any number of authored shapes in order, then zooms through a point you choose on the last shape to exact full bleed
  • Velocity-reactive inflate and tilt with an elastic settle, plus a gentle idle breath so the mask is never static
  • Edge split: two solid echoes (the demo uses the accent and chalk) clipped by the same mask slide apart with scroll speed
  • Footage playbackRate rises with velocity (capped via data attribute) and eases back to real time
  • Desktop-only SVG displacement ripple along the mask edge, removed at rest so idle frames cost nothing
  • Design-space fitting: shapes authored in one viewBox are centred and scaled to any stage without distortion
  • Touch-friendly: the same morph runs on a shorter pin; reduced motion gets a designed still over the poster frame
  • Replay button scrolls back to the start of the journey through Lenis when present

Perfect for

5 use cases
  • Brand and campaign heroes where a looping film should feel alive rather than scrubbed
  • Studio and agency intros that reveal the name in the footage before the page opens up
  • Product launches that want an Awwwards-grade scroll moment from one MP4 and one word
  • Film, music and event sites with strong motion footage worth showing through type
  • Portfolio landings where the mark morphs into the name and then into the work

How it works

4 sections

The stage holds three full-size layers, two solid echo layers and the film, all pointing at one SVG <clipPath> in userSpaceOnUse units. The shapes you author live in a hidden SVG with a design viewBox; on every ScrollTrigger refresh the script re-derives the stage size, computes a contain-fit transform, and writes it onto the clip path together with the live inflate, breath and tilt values, so the word is never stretched and the animation needs no per-breakpoint paths.

A pinned, scrubbed timeline morphs the clip path with MorphSVG from shape to shape with explicit start and end pairs, so a refresh can never re-record a mid-morph start. It then zooms the clip path about a point on the last shape (data-mask-zoom), with the scale needed to cover the stage worked out on refresh from the farthest corner, so the solid stroke covers the stage exactly at the end of the tween. The same timeline pushes the video's scale out slowly, fades the grade layer, and reveals the content.

Scroll speed feeds one decaying smear value each frame. That value drives the echo offsets, the video's playback rate, the distortion strength (applied to the layer wrapper only while there is movement), and a tilt on the clip transform; a hard flick also kicks an inflate value that springs home. Every live channel is multiplied by (1 - bleed) so the full-bleed frame never shows a gap.

Desktop, touch and reduced motion are separate branches. Reduced motion sets the word shape on the poster frame with no pin and the content visible; cleanup removes the frame callback, the refresh listener, the replay handler and the ScrollTrigger, and restores the video's playback rate.

Plugins ScrollTrigger, MorphSVG
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.

Paid effect

Purchase to unlock the code.

Buying Scroll Morph Mask Video opens the HTML, CSS and JavaScript source, the full documentation, an AI setup prompt for your editor, and every update we ship to it. Standard license: 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.

£10 Standard license, unlimited projects

Browse free effects

Or the whole library: the Vault, £99, one payment.

Documentation

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 section to your <body>:

Code snippet omitted: it ships with the download.

Copy the two d attributes from the demo's index.html to start with, then replace the word with your own (see below).

Using It With Your Own Design

What the effect needs from your markup: one [data-mask-video] section containing a [data-mask-stage] (this is what gets pinned), a [data-mask-layers] wrapper holding zero or more [data-mask-echo] divs and one [data-mask-film] with a <video> inside, a <clipPath clipPathUnits="userSpaceOnUse"> with a single empty <path>, and a hidden <svg viewBox="…"> holding the [data-mask-shape] paths in the order you want them morphed. Everything else ([data-mask-grade], [data-mask-scrim], [data-mask-reveal], [data-mask-hint], [data-mask-replay], the <filter>) is optional and skipped when absent. The script never reads a class name; all hooks are data- attributes.

Authoring the mask shapes: every shape is an ordinary SVG path in one shared design box, the viewBox of the shapes SVG (the demo uses 0 0 1200 675). The script fits that box inside the stage with a contain scale and centres it, so shapes are never stretched at other aspect ratios. To use your own word or logo: set the text in Figma or Illustrator, outline it (Figma: Flatten; Illustrator: Create Outlines, then Compound Path > Make), copy as SVG, and paste the d of the resulting path onto a [data-mask-shape]. Draw it at the size and position you want it to appear inside the design box. Shapes with no counters (holes) morph most cleanly; a letter with a counter (the D in the demo's TIDE, an O, an A) still works but passes through a stranger middle state, and a stem beside the counter makes a good zoom point. The demo's outlines come from Mona Sans at weight 800; any face you can outline works. Add more shapes for more stages: the script morphs through them in document order and always zooms through the last one to full bleed. One shape is also fine (mark, then zoom).

The zoom point: put data-mask-zoom="x y r" on the last shape: a point in design units that sits inside a solid part of the shape, and the radius of solid around it. The mask scales up about that point until the solid covers the stage, so choose a fat stroke or a crossing (the demo uses the stem of the D). Without the attribute the script zooms about the centre of the design box, which is only right if that point is inside your shape.

Footage: in the default play mode the video plays rather than scrubs, so any muted, looping MP4 works: no all-intra encode, no frame sequence. Because the film is seen through thin letter apertures for part of the journey, pick footage with bold, continuously moving texture and real depth (water, weather, a drone pass, a crowd) and avoid fine detail or a static frame. A 10-15s seamless loop is plenty. The demo clip is a drone pass over a surf break by Nadezhda Moryak on Pexels (https://www.pexels.com/video/9806388/, licence permits modification and redistribution; credited anyway), graded for contrast with a slight desaturation and no colour tint, then cut into a 12s loop by cross-fading the last second onto the first (xfade=transition=fade:duration=1 between the tail and the head, concatenated after the body). Ship a VP9 WebM first and an H.264 MP4 fallback: at 1600x900 the demo's land at 2.2MB each (-c:v libvpx-vp9 -b:v 0 -crf 52 -row-mt 1 and -c:v libx264 -crf 34); foam and water are expensive to encode, so calmer footage goes smaller. Supply a poster so the reduced-motion and autoplay-blocked paths show a real frame.

Scrub mode footage: seeking decodes from the previous keyframe, so a normal long-GOP encode stutters. Encode a dedicated file with a short GOP and point data-scrub-src on the <video> at it; the script swaps it in only when scrubbing. ffmpeg -i in.mp4 -t 10 -an -vf scale=1280:720 -c:v libx264 -crf 30 -g 6 -keyint_min 6 -sc_threshold 0 -movflags +faststart -pix_fmt yuv420p scrub.mp4 gives about 5MB for 10s with seeks around 20ms; all-intra (-g 1) is smoother still but three times the size. A scrubbed clip does not need to loop.

What is only the demo's styling: the font (Mona Sans from Google Fonts), the near-black ground and grade, the prompt, the content block's typography and the hairline button are all demo CSS; reskin or delete them freely. The echo colours are the --echo-a and --echo-b custom properties (the demo sets them to its accent and its chalk ink) applied on .mask-video__echo--a/--b; set them to anything, or remove the echo divs to drop the edge split altogether.

CSS the effect depends on: .mask-video__stage must be position: relative; overflow: hidden with a real height (the demo uses 100svh), and the layers, echoes, film and scrim must stay position: absolute; inset: 0 so all three masked elements share the same user space as the clip path. The <video> needs object-fit: cover at full size. Keep the .has-js gates: .has-js .mask-video__echo { display: block }, .has-js .mask-video__grade { display: block }, .has-js .mask-video__scrim { opacity: 0 } and the [data-mask-reveal] pre-hide, each with its reduced-motion restore; without JS the film simply fills the stage under your content, which is the correct fallback. The script sets clip-path: url(#…) inline on the film and echoes, so do not set clip-path on them in CSS.

Options

All attributes go on the [data-mask-video] section.

Attribute Values Default Description
data-mask-length Number 2.6 Pin distance as a multiple of the viewport height. Lower is a quicker journey.
data-mask-scrub Number (seconds) 1 ScrollTrigger scrub smoothing. The lag is part of the feel; 0 locks it 1:1.
data-mask-velocity Number 1 Strength of every flick reaction (inflate, tilt, echo split, playback rate, ripple). 0 disables them all.
data-mask-max-rate Number 3 Fastest playbackRate the footage reaches on a hard flick.
data-mask-ripple true, false true Edge displacement ripple on flicks. Desktop only; it is removed at rest so idle frames cost nothing.
data-mask-playback play, scrub play play loops the film on its own clock and scroll only moves the mask. scrub makes scroll position the playhead (currentTime follows progress), so the film is frozen when you stop; touch devices always play. Add ?film=play or ?film=scrub to the URL to compare without editing.

Accessibility

  • Reduced motion: no pin, no ticker, no morph. The word shape is set once over the poster frame, positioned in the upper half so the content below it is fully readable, and the scrim and content are shown immediately. The video is never started.
  • No JavaScript: the grade, echoes and hint are hidden, nothing is clipped, and the film fills the stage under the visible content.
  • Keyboard: the only control is a real <button> (or <a> in your page) with :focus-visible styling matching :hover.
  • Screen readers: the video, echoes and decorative layers are aria-hidden; the section carries an aria-label.

Dependencies

Required:

  • GSAP 3.13+ (MorphSVGPlugin is free as of 3.13)
  • ScrollTrigger plugin
  • MorphSVGPlugin

Optional:

  • Lenis (smooth scroll; opt out with data-smooth="off" on <html> or ?smooth=off)

Browser Notes

  • clip-path: url(#id) on HTML elements is supported in all current browsers. The clip path uses userSpaceOnUse units so its coordinates are the element's own pixels, which is why every masked layer must be the same size as the stage.
  • The ripple uses an SVG filter referenced from CSS; it is applied only on fine-pointer devices and only while there is scroll velocity.
  • iOS Low Power Mode may refuse autoplay; the poster shows and the mask still morphs over it.

Worked examples, the events and programmatic API, and the class reference ship with the download, alongside the full source.