41 named effects · every one live on this page

Every effect
has a name

You cannot ask for what you cannot name. This is one page, not a folder — 41 interface effects running side by side, each labelled with the term practitioners actually use, so the next brief can just say “pin and scrub” or “progressive blur”.

Left — what it is, and what to call it Right — the effect, running Material — Kamisaka Sekka, 1909, CC0

Scroll

24 named effects

01 / scroll

Scroll Progress Timeline

How far you have scrolled is how far the animation has played. The hairline across the top of this page is one.

also called scroll-linked animation · page progress

The animation's progress is the scroll container's progress. Zero JavaScript, and it runs off the main thread — so it cannot jank even while the main thread is busy.

How it is builtanimation-timeline: scroll(root);
scroll the page
0%
02 / scroll

View Progress Timeline

Every row times its own animation by its own trip across the screen, so no two are at the same point at the same moment.

also called view timeline · element-in-viewport progress

Each element measures its own passage through the viewport, so several different values exist at once. animation-range is the part most people miss — it decides which slice of that passage the animation occupies.

How it is builtview-timeline-name: --plate; animation-timeline: --plate; animation-range: entry 0% cover 45%;
Exhibit 04 · Urformen
scroll the page
03 / scroll

Pinning & Scrub

The picture holds still while the page keeps moving, and your scrolling becomes the playhead. Scroll back up and it plays backwards.

also called sticky section · pinned stage · ScrollTrigger pin + scrub

Pin holds the stage still while the page keeps scrolling. Scrub is the separate idea that progress follows scroll position rather than elapsed time, which is why it reverses exactly. Most “cinematic” sites are these two words.

How it is builtposition: sticky; top: 0; /* GSAP */ ScrollTrigger({ pin: true, scrub: 1 })
Exhibit 01 · Annie G.
scroll the page
Scrub to wipe
04 / scroll

Parallax

The layers move at different rates, and the difference between the rates is the part you read as distance.

also called differential scroll · multiplane

The oldest trick here and the easiest to overdo. It reads as depth only when the rates differ by a little; large offsets read as broken layout. Built on a view progress timeline it costs no JavaScript.

How it is built@keyframes drift { from { transform: translateY(-10%) } to { transform: translateY(10%) } } animation: drift linear both; animation-timeline: view(); /* each layer gets its own distance; the difference is the depth */
scroll the page
Depth is a rate difference
05 / scroll

Horizontal Scroll Section

You scroll down; the content travels sideways. A list you ranked becomes a row you compare across.

also called pinned horizontal scroll · sideways gallery

Changing the axis changes what the content means: a stack implies rank, a track implies comparison. Travel must be measured from real content — a track that barely overflows reads as a glitch, not a journey.

How it is built/* --travel is the track's overflow, measured from its content */ transform: translateX(calc(-1 * var(--travel)));
Exhibit 02 · Night side
scroll the page
06 / scroll

Scroll Snap

Scrolling comes to rest on a whole item instead of halfway between two. It feels like turning pages.

also called snap points · scroll snapping

Two properties, and it inherits the platform's real scroll physics — momentum, rubber-banding, trackpad feel. Every JavaScript reimplementation is worse.

How it is builtscroll-snap-type: y mandatory; scroll-snap-align: start;
scroll inside ↓
Snap 01
Snap 02
Snap 03
Snap 04
07 / scroll

Sticky Stacking Cards

Cards catch at the top one after another and pile over each other as you keep going.

also called card stack · sticky stack

One property on a list. Each item sticks at the same offset so later items slide over earlier ones and the stack builds itself. No library, no measurement, no scroll listener.

How it is builtli { position: sticky; top: 76px; }
scroll the page
    08 / scroll

    Scroll Velocity Skew

    It reads how fast you are scrolling rather than where you have got to. Throw the page and the picture leans, then settles.

    also called skew on scroll · velocity distortion · momentum skew

    Velocity is a genuinely separate signal from position. The asymmetry is the craft: it rises about six times faster than it falls, which is what makes it read as momentum rather than twitch.

    How it is builtv += (target - v) * (target > v ? 0.34 : 0.055); transform: skewY(calc(var(--vel) * -4deg));
    Exhibit 03 · Florence
    throw the page

    velocity 0.00

    09 / scroll

    Sticky State Query

    The moment the bar catches at the top of its panel, it knows, and changes itself.

    also called stuck detection · scroll-state container query · sticky-aware header

    Until this shipped, every “shrink the header once it sticks” needed a scroll listener and a hand-picked pixel threshold that broke under zoom. Now the element is simply told what it is.

    How it is builtcontainer-type: scroll-state; @container scroll-state(stuck: top) { … }
    scroll inside ↓
    Momoyogusa · plate indexstuck
    10 / scroll

    Snapped State Query

    Whichever slide is parked at the centre knows it is the one you are looking at, and lights up.

    also called snapped detection · active-slide styling · scroll-state(snapped)

    The companion query. A carousel always needed to know which slide is current, and every hand-rolled version computed it from offsets and got the edges wrong. This is the browser answering the question itself.

    How it is builtscroll-snap-align: center; @container scroll-state(snapped: block) { … }
    scroll inside ↓
      11 / scroll

      Scroll Markers

      The dots under the strip are made by the strip itself, and follow wherever you are in it.

      also called ::scroll-marker · CSS-only carousel dots · scroll-marker-group

      The dots are generated by the list and track it for free — including keyboard traversal and focus order, which hand-built dot navigation almost never gets right.

      How it is builtscroll-marker-group: after; li::scroll-marker { content: '' } li::scroll-marker:target-current { … }
      drag · or click a dot
        12 / scroll

        Scroll Buttons

        The back and forward arrows come out of the row of items itself, and go grey when there is nothing left that way.

        also called ::scroll-button() · generated prev/next · CSS-only controls

        The same idea as the markers, for the arrows — and the :disabled state at each end arrives free, which is the part hand-rolled carousels forget. No JavaScript, and each one is a real button to a screen reader.

        How it is builtul::scroll-button(left) { content: '←' } ul::scroll-button(right) { content: '→' } ul::scroll-button(*):disabled { opacity: .25 }
        press the arrows
          13 / scroll

          Timeline Scope

          A figure far down the panel drives the bar at the top of it. One element's passage moves something it sits nowhere near.

          also called timeline-scope · remote timeline · cross-tree driving

          A scroll timeline is normally visible only to the element's own descendants. timeline-scope lifts it onto a shared ancestor, so a bar at the top can be driven by a figure far below it — something that previously required JavaScript by definition.

          How it is builttimeline-scope: --plate; /* on a child */ view-timeline-name: --plate; /* on a sibling */ animation-timeline: --plate;
          scroll inside ↓
          bar driven by the figure below
          keep scrolling ↓
          — end —
          14 / scroll

          Path Drawing

          The line draws itself as you arrive, as though someone were tracing it in front of you.

          also called stroke-dashoffset scrub · line draw · SVG path reveal

          The robustness trick is pathLength="1": it normalises any path to a length of one, so the same two lines of CSS draw any shape without measuring it in JavaScript first.

          How it is builtpath { pathLength: 1; stroke-dasharray: 1; stroke-dashoffset: 1 } animation-timeline: view(); animation-range: entry 5% cover 50%;
          scroll the page

          Draws as you arrive

          15 / scroll

          Word Scrub

          The sentence lights up a word at a time, and how much of it has arrived is how far you have come down the page.

          also called read-along highlight · per-word reveal · text scrub

          Each word owns a different slice of one timeline, so the sentence resolves left to right as you descend. The overlap is the entire craft: too little and it strobes word by word, too much and the sentence arrives all at once.

          How it is built/* per word, i = its index */ animation-range: cover calc(var(--i) * 1.5%) cover calc(var(--i) * 1.5% + 26%);
          scroll the page
          16 / scroll

          Direction State

          Going down puts the bar away so you can read. Going back up brings it straight back.

          also called auto-hide header · direction-aware nav · scroll-up reveal

          Position says where you are; direction says what you want. Descending reads as “reading, leave me alone”, ascending as “looking for something”. A few pixels of dead zone are mandatory, or a trackpad makes it flicker.

          How it is builtdir = y > last ? "down" : "up"; [data-dir="down"] .bar { transform: translateY(-100%) }
          scroll inside ↕
          Momoyogusaup
          17 / scroll

          Curtain Overlap

          The next panel covers the one before it rather than pushing it out of the way.

          also called panel slide-over · curtain reveal · section overlap

          Two rules and nothing else. The outgoing panel stays pinned while the incoming one rides over it, so the change reads as depth rather than travel. Note what is absent: no animation, no timeline, no measurement.

          How it is built.a { position: sticky; top: 0 } .b { position: relative; z-index: 2 }
          scroll the page
          01 — stays put
          02 — rides over
          18 / scroll

          Depth of Field

          Only the band across the middle of the screen is sharp. Everything above and below it falls out of focus.

          also called focus falloff · viewport-centre focus · scroll blur

          A camera metaphor applied to a list: attention is a narrow band, and everything outside it recedes. Keep the blur under about 6px — past that it stops reading as focus and starts reading as a rendering fault.

          How it is built@keyframes focusband { 0%, 100% { filter: blur(4.5px); opacity: .34 } 46%, 54% { filter: blur(0); opacity: 1 } }
          scroll the page
          19 / scroll

          Infinite Loop Scroll

          The list has no end. Keep going and it carries on, with no seam to catch on.

          also called endless scroll · recycled list · seamless wrap

          The correction must happen in the same frame as the move or the seam shows as a jump. Done right the list has no beginning and no end — and the scrollbar stops telling you where you are, which is the price.

          How it is builtel.append(el.firstElementChild); el.scrollTop -= rowHeight; /* same frame, or it jumps */
          never ends ↓
          20 / scroll

          Kinetic Drag

          Grab it and throw it. It keeps going after you let go, then slows to a stop as though it had weight.

          also called drag-to-scroll · inertial scrolling · momentum decay

          Everything lives in the decay constant. 0.90 stops abruptly and feels cheap; 0.98 slides forever and feels broken. Around 0.94 is where it starts feeling like an object with mass.

          How it is builtv *= 0.94; /* per frame */ if (Math.abs(v) < 0.1) stop();
          grab and throw
          21 / scroll

          Scroll-Triggered Count

          The figures wait until they are on screen, then run up to their value — once, not every time you pass.

          also called odometer · number roll · stat counter

          The tabular figures matter more than the easing — proportional numerals change width as they count and the whole row twitches. It fires once: a counter that replays on every pass is a tell that nobody checked it twice.

          How it is builtfont-variant-numeric: tabular-nums; new IntersectionObserver(fn, { threshold: 0.6 })
          plays once
          22 / scroll

          Scrollspy

          Whatever has reached eye level is the thing the index marks as where you are.

          also called section tracking · active-anchor highlight · reading position

          That rootMargin collapses the viewport to a thin band across its middle, so “current” means what is at eye level rather than what merely happens to be on screen. The index pinned to the top of this page is one.

          How it is builtnew IntersectionObserver(fn, { rootMargin: '-45% 0px -45% 0px' })
          scroll inside ↓
          23 / scroll

          Overscroll Containment

          Push both panels past their last line. One hands your gesture on to the page behind it; the other keeps it.

          also called scroll chaining · overscroll-behavior · gesture trapping

          One property, and it is the whole difference between a panel that feels solid and one that feels like a leak. The chaining default is right for a document and wrong for almost every overlay.

          How it is builtoverscroll-behavior: contain;
          scroll both to the end

          default — chains

          contain — absorbs

          24 / scroll

          Snap Stop

          Flick both tracks hard. The top one skips three or four items in one go; the bottom one stops at the very next one.

          also called scroll-snap-stop · no-skip snapping · mandatory stop

          Use it when every item must actually be seen, and leave it off for browsing. It is the difference between a catalogue and a queue.

          How it is builtscroll-snap-stop: always;
          flick both

          normal — a hard flick skips

            always — one at a time

              Image

              7 named effects

              25 / image

              Blur-up (LQIP)

              A small blurred stand-in appears at once and the real picture fades in over it — in a box that was already the right size, so nothing jumps.

              also called low-quality image placeholder · progressive loading

              Two jobs at once: something to look at immediately, and a reserved box so nothing shifts when the real file lands. Layout shift is a ranked Core Web Vital, so the aspect-ratio is not decoration.

              How it is builtfigure { aspect-ratio: 3 / 2 } /* the box is reserved before the file lands */ .placeholder { filter: blur(18px); transform: scale(1.1) } .full { opacity: 0; transition: opacity .9s } /* → 1 once loaded */
              watch it load
              26 / image

              Ken Burns

              A still photograph is given a slow drift and a slow push in — just enough to feel like time passing.

              also called slow pan and zoom · documentary pan

              Named for the documentarian who made a career of it. The rule is that the move must be slower than the viewer's attention — if they notice the motion starting, it is too fast.

              How it is built@keyframes kb { to { transform: scale(1.14) translate(-2%, 1.5%) } } animation: kb 18s ease-in-out infinite alternate;
              runs by itself
              27 / image

              Duotone

              Any photograph, whatever it arrived wearing, comes out in one colour. Drag back to see what it came in as.

              also called two-tone mapping · bichromate

              A brand-colour treatment that survives any source image. Grayscale the base, then lay a single hue over it with mix-blend-mode.

              How it is builtbackground: var(--accent); mix-blend-mode: color; /* over a grayscale base */
              drag the slider
              28 / image

              Progressive Blur

              The picture softens towards the bottom, so the caption sits on something readable without a black bar laid across it.

              also called gradient blur · layered blur · tapered blur

              Stacked layers, each blurring more than the last, each masked to a different band. Far more elegant than a scrim because the image stays legible while the text still reads.

              How it is builtbackdrop-filter: blur(Npx); mask-image: linear-gradient(to top, #000, transparent);
              read the caption

              Text sits on light

              29 / image

              Ordered Dithering

              The picture is cut down to a handful of tones, and the ones that are missing are made up out of a regular grain. Drag to take more away.

              also called Bayer dithering · halftone · posterize

              Real pixel work — it needs crossorigin="anonymous" and a CORS-clean host, otherwise the canvas is tainted and getImageData throws. Wikimedia sends the header; most museum CDNs do not.

              How it is builtconst BAYER8 = [...]; // 8×8 threshold matrix v = v + (BAYER8[y%8][x%8]/64 - 0.5) * spread;
              drag the slider
              30 / image

              Clip-path Reveal

              The picture is uncovered by an edge travelling across it, not by fading up underneath.

              also called mask reveal · wipe · shape reveal

              A wipe has a direction and an edge; a fade has neither. That edge is what makes the reveal feel authored rather than merely delayed.

              How it is builtclip-path: inset(0 100% 0 0) → inset(0 0 0 0); transition: clip-path .9s cubic-bezier(.16,.84,.24,1);
              hover
              31 / image

              Image Comparison Slider

              Two versions of the same frame, one laid over the other. Drag the divider across to compare them.

              also called before/after · juxtapose

              One image clipped over another, the clip driven by pointer position. Keep both layers exactly registered or the comparison lies.

              How it is builtclip-path: inset(0 calc(100% - var(--x)) 0 0);
              drag across

              Type

              5 named effects

              32 / type

              Split Text Reveal

              The lines rise up out of the page one after another, each a beat behind the last.

              also called staggered line reveal · line mask · SplitText

              The mask is what matters: a wrapper with overflow:hidden and a translated inner. That makes text rise out of the page instead of fading in on top of it.

              How it is built.line { overflow: hidden } .line span { transform: translateY(110%) → none }
              plays once
              A name you can say
              is a thing you can ask
              someone else to build.
              33 / type

              Variable Font Animation

              Weight is a dial here, not a set of steps. Drag it and the letterforms thicken without ever swapping to another file.

              also called axis interpolation · variable axes

              A variable font exposes continuous axes rather than fixed cuts, so weight can be animated instead of swapped. One file, the whole family, and no flash between weights.

              How it is builtfont-variation-settings: 'wght' 340; transition: font-variation-settings .6s;
              drag the slider

              Weight is a continuous axis

              34 / type

              Text Mask

              The picture shows only through the letters. The type is the window, not the caption.

              also called knockout text · background-clip · image-in-type

              The type becomes an aperture onto the image. It needs real weight to work — thin letterforms leave too little opening for the picture to read.

              How it is builtbackground-image: url(…); background-clip: text; color: transparent;
              look through the letters

              MOMOYOGUSA

              35 / type

              Vertical Writing Mode

              Set down the page instead of across it, the way Chinese, Japanese and Korean have always been set. Each column is written downward, and they arrive right to left. Latin and figures turn sideways to follow — all but the year, held upright in a single square.

              also called 竖排 · tate-gumi · vertical typesetting

              Native in CSS and correct for CJK — the browser rotates Latin runs and places the punctuation itself. text-combine-upright is the piece almost nobody reaches for: it sets a two- to four-character number upright inside one em box, which is how a printed vertical page has always handled a date. This is the experimental lane: the writing here is material to look at, never the interface.

              How it is builtwriting-mode: vertical-rl; text-orientation: mixed; .year { text-combine-upright: all } /* 縦中横 */
              scroll the page

              滚动即时间轴

              名は体を表す

              이름이 곧 형태다

              神坂雪佳 百々世草 1909

              36 / type

              Marquee

              A band of text running sideways under its own power, whether or not you touch the page. Hover to stop it.

              also called ticker · infinite scroller · running band

              The one effect here that ignores scroll entirely — it gives a page a pulse when nothing else moves. Duplicate the content exactly once and travel -50%; that is what makes the loop seamless.

              How it is built.row { width: max-content; animation: slide 26s linear infinite } @keyframes slide { to { transform: translateX(-50%) } } /* the content is duplicated once, so -50% lands exactly on the seam */
              runs by itself

              Colour

              5 named effects

              37 / colour

              Token Interpolation

              Every colour on the card is held to one dial. Scroll, and they all turn together, in step, never landing on a combination nobody chose.

              also called scroll-driven theme · animated custom properties · palette rotation

              The whole palette is one number. @property is what makes it animatable at all — an unregistered custom property has no interpolable type. Every token keeps its lightness and chroma and shares the angle, so nothing can drift out of step.

              How it is built@property --rot { syntax: '<angle>'; inherits: true } --accent: oklch(0.637 0.145 calc(40.2deg + var(--rot)));
              Exhibit 07 · Kīlauea
              scroll the page
              LedgerMomoyogusa

              Twelve plates, one angle.

              Open
              38 / colour

              Scoped Rotation

              The same colour change, asked of a few elements or of a great many. Press the switch and watch the counter beside it.

              also called custom property invalidation · style recalc scope

              Where the angle is declared decides what it costs. On the root, every element on the page re-resolves its colours each frame; on the stage, only the stage does. Scope is the whole difference between a palette that animates for free and one that eats the frame budget.

              How it is built.stage { --rot: 0deg; animation: turn linear both; animation-timeline: scroll(root) } /* declared on .stage, not :root — only this subtree re-resolves each frame */
              press the switch

              -- · 0 consumers

              39 / colour

              Gamut Arc

              Two ways to get from terra to blue. The left field goes round the outside and is a full colour the whole way; the right one goes straight across and turns to mud halfway.

              also called hue path · chroma preservation · colour interpolation

              How much colour a screen can hold changes with hue: the space bulges towards violet and pinches near cyan, so it is not a cylinder. That makes the route between two colours a gamut question rather than a matter of taste — and the default route, a straight line through the middle of the space, is the one that passes closest to grey. The strips under each field are the whole journey at once.

              How it is built/* round the outside */ background: oklch(0.637 0.148 calc(40.2deg + var(--t) * -135.8deg)); /* straight across — what you get by default */ background: color-mix(in srgb, <end> calc(var(--t) * 100%), <start>);
              scroll the page

              along the rimfull colour the whole way

              straight acrossgrey in the middle

              40 / colour

              Difference Inversion

              The words turn themselves into the opposite of whatever passes behind them, so they stay readable over a photograph, over white and over black — and then vanish over one particular grey.

              also called mix-blend-mode · invert on crossing · self-inverting overlay

              One property, no JavaScript, and the overlay never has to know what is behind it. What it cannot survive is mid-grey, where inverting a value returns very nearly the value you started from. Plan for that band rather than discovering it on the one slide that has it.

              How it is builtmix-blend-mode: difference;
              watch it cross the grey

              Legible on anything

              41 / colour

              Threshold Flip

              At one point on the way down, the whole panel changes its colours at once rather than easing between them.

              also called section theming · discrete palette swap · data-theme

              The common form, and the honest one when two palettes are unrelated rather than a rotation apart. Everything rests on the transition: without one it strobes, and much past a second it lags the scroll and reads as a bug.

              How it is builtnew IntersectionObserver(fn, { rootMargin: '-45% 0px -45% 0px' }) .flip { transition: background-color .7s var(--settle) } /* these three palettes are unrelated by design, so they are written out rather than derived from the house tokens */
              Exhibit 07 · Kīlauea
              scroll inside ↓