Scroll✨ Premium

Color Shift

A section background (.sdc-color-bg, absolute, inset 0) whose 135° gradient runs through five palettes — night, indigo, violet, pink, orange — over a 520 px travel, in CSS alone: animation-timeline: scroll() reads the 280 px box, zero JavaScript. Measured: the palette switches in one step, no cross-fade, at 66, 196, 326 and 456 px in Chromium.

CSSGradientScroll

Updated

This effect is part of Effect.Labs — 811 vanilla effects, some free, some premium. Scroll has 70 effects, including 12 free. Explore the category →

3 usage examples

Bus line timetable from the first to the last departure — transport page of a metropolitan area — Color Shift example 1

① Bus line timetable from the first to the last departure — transport page of a metropolitan area

WhenA line's page: the list of departures (5:40 am → 11:50 pm) scrolls inside the frame; the background goes from night to dawn, to full day, to dusk, then to night.
WhyFive steps pinned to the travel: the background follows the time of the departures without counting rows or writing a rule per time slot — fx-0585 Sticky Image Change changes the color of a pinned block in stages, not that of the whole section.
SettingsSold code untouched (same classes, same scroll()) under a 52 px bar; stage at height: auto (travel measured by the content, 27 departures), palettes replaced by night #0b1026 → #1e1b4b, dawn #4c1d95 → #db2777, day #075985 → #0284c7, dusk #c2410c → #7e22ce, night; text as a left-aligned list, times in tabular figures, tabindex="0" and aria-label on the box. The list advances on its own until the first gesture — a demo device.
From the lobby to the rooftop — visit page of a panoramic tower — Color Shift example 2

② From the lobby to the rooftop — visit page of a panoramic tower

WhenThe “Visit” page: the floors (lobby, lift, gallery, terrace, rooftop) scroll by, the background goes from the shade of the lobby to the light of the summit.
WhyThe gradient covers the whole background behind the text, with no object to stage: the color is the mood of the floor — fx-0554 Color Change requires a colored block in the middle of the text.
SettingsFive translucent cards (rgba(2, 6, 23, .6), white border at 15%) on the sold background, palettes lobby #0f172a → #1e293b, lift #1e3a8a → #312e81, gallery #1d4ed8 → #2563eb, terrace #0284c7 → #38bdf8, rooftop #7dd3fc → #fef3c7: the last one is light, the cards keep the white text above 5:1. 48 px bar, box on the rest, scrollbar-width: none.
Planting calendar by season — a garden centre's website — Color Shift example 3

③ Planting calendar by season — a garden centre's website

WhenThe “When to plant” page: the months scroll by with their sowings; the background slides from the cold tones of January to the warm ones of summer, then back toward autumn.
WhyA season does not blend into the next: each palette holds until the switch and the whole background of the list — not a block set inside it — takes the tint of the month being read; fx-0554 Color Change, which slides continuously, would show a half-tone between winter and spring.
SettingsEleven months in 56 px blocks, each sowing in a white chip at 18%; palettes winter #1e3a8a → #0c4a6e, spring #065f46 → #15803d, summer #b45309 → #c2410c, late summer #c2410c → #9a3412, autumn #7c2d12 → #44403c — the switches fall at 12.5 / 37.5 / 62.5 / 87.5% of the list, around February, May, August and November. 50 px bar, the rest of the sold code as is.

How it works

The sold HTML stacks five boxes: the frame .demo-preview (centered flex, min-height: 300px, position: relative, #0a0a0f background, white text), the scrolling box .sdc-scroll-container (100% × 280px, overflow-y: auto, scroll-behavior: smooth, a 4 px ::-webkit-scrollbar), the stage .sdc-color-content (800px, flex column, position: relative) and, inside it, side by side, the background .sdc-color-bg (position: absolute; inset: 0, empty) then the section .sdc-color-section (flex: 1, centered flex) holding .sdc-color-text: a 24 px weight-800 h3 and a 12 px p in white at 60%, at z-index: 1 above the background. The travel is 800 − 280 = 520 px. The JS field is empty; no site variable, no hosted file, no console error: 2.4 KB of CSS.

The background has no background of its own: it comes entirely from @keyframes sdcColorShift, five steps each setting a two-color linear-gradient(135deg, …) — #1e1b4b → #0f172a (0%), #4c1d95 → #1e1b4b (25%), #7c3aed → #4c1d95 (50%), #ec4899 → #7c3aed (75%), #f97316 → #ec4899 (100%) — read by animation-timeline: scroll(): with no argument, the timeline is that of the nearest scroll container, here .sdc-scroll-container; animation-duration: auto and animation-range: normal spread the 100% over the whole travel. Measured in Chromium 151, wheel in 104 px notches: progress 0 → .2 → .4 → .6 → .8 → 1, and the background keeps the last palette at the end (finished state; nothing vanishes despite animation-fill-mode: none). But a gradient does not interpolate: between two steps the browser does not blend the colors, it switches. Chromium changes palette halfway through each segment — to the pixel: 66, 196, 326 and 456 px — and WebKit 26.5 from the very first pixel of the segment — 1, 131, 261 and 391 px —, so the “night” palette only exists there at scrollTop = 0. Five colors, four clean cuts; for a real cross-fade, animate a color, not a gradient (see the FAQ).

The background is a sibling of the section, not its child: its inset: 0 is measured against the first positioned ancestor. The position: relative line on .sdc-color-content is what makes it cover the stage's 800 px; without it (measured with the line removed), the positioned ancestor becomes the 280 px box, the background is only 280 px tall, scrolls away with the content and leaves the frame — at 260 px only a 20 px strip remains, at 326 px nothing, and the pink and orange palettes are never seen. The text, meanwhile, travels with the stage: centered at 400 px, it is out of frame at rest (375–425 px for a 280 px window), enters from the bottom at 96 px, leaves through the top at 425 px — visible over 329 of the 520 px of travel, under the indigo, violet and pink palettes. At rest, the visitor therefore sees an empty “night” box; the title only appears while scrolling. Two copies pasted in the same page give two independent boxes, each reading its own container; the h3 font comes from an inline style (Inter is not loaded, the browser falls back to the system font).

Accessibility

  • prefers-reduced-motion not in the code: measured under emulation, the palettes switch at the same pixels and the hint keeps pulsing (sdcHintPulse running). The background change is driven by the reader's gesture — nothing moves without it —, which makes it an interaction-triggered animation (WCAG 2.3.3). Add @media (prefers-reduced-motion: reduce) { .sdc-scroll-hint { animation: none } .sdc-scroll-container { scroll-behavior: auto } }; if the hard cuts bother you for a sensitive reader, freeze one palette: .sdc-color-bg { animation: none; background: linear-gradient(135deg, #1e1b4b, #0f172a) }.
  • Contrast (computed on both colors of each palette): the white 24 px weight-800 title (3:1 threshold) passes on night (16:1), indigo (11:1), violet (5.7:1) and pink (3.5:1), but drops to 2.8:1 on the orange #f97316. The 12 px paragraph in white at 60% (4.5:1 threshold) only passes on night (6.5:1) and indigo (4.9:1): 3.0:1 on violet, 2.1:1 on pink, 1.9:1 on orange. For text read over the whole travel, keep #fff at 100% and darken the last two palettes, or put the text on a translucent card (the tower scene).
  • Keyboard: measured, Chromium and Firefox focus the box on Tab without a tabindex (Arrow Down: 40 px in Chromium, 51 px in Firefox; End: 520 px); WebKit does not — focus stays on body and the arrows do nothing. Add tabindex="0", an aria-label and a :focus-visible style on .sdc-scroll-container. Wheel and finger are native (no listener); overscroll-behavior: contain keeps the page from being dragged along at the end.
  • Screen readers and meaning of color: the palette is not announced and must not carry information on its own — the hour, floor or month it illustrates must be written in the content (WCAG 1.4.1). .sdc-color-bg is an empty div: put aria-hidden="true" on it, and on the “Scrollez ↓” hint (hard-coded French; white at 30% whose opacity pulses from .3 to .7, the two multiply: 1.2:1 at the low point of its pulse, 1.8:1 at the peak). The sold h3 must follow your page's current heading level. Without CSS, everything reads in document order.

Browser compatibility

CSS only: animation-timeline: scroll(), animation-duration: auto, animation-range, inset, @keyframes on background, flexbox, scroll-behavior, ::-webkit-scrollbar (Blink and WebKit only). Zero JavaScript, zero dependencies. Scroll-driven animations exist in Chrome and Edge since version 115, in Safari and iOS since 26; Firefox reads them in no version (measured in Firefox 153: CSS.supports('animation-timeline: scroll()') returns false, animation-duration: auto is rejected and falls back to 0s). The sold code contains no @supports fallback.

Chrome 115+✓ Full (switch at mid-segment)
Firefox✓ No version (flat box, readable text)
Safari 26+✓ Full (switch at the start of each step)
Edge 115+✓ Full
Mobile iOS 26+✓ Full (touch scrolling of the box)
Android Chrome 115+✓ Full

Firefox, measured: the background computes background-image: none, the box is flat, the color of the frame (#0a0a0f); the white title and the paragraph stay readable as they pass, the hint pulses, nothing is hidden, no console error — but no color ever changes. To give those visitors the starting palette, three lines to paste under the code: @supports not (animation-timeline: scroll()) { .sdc-color-bg { background: linear-gradient(135deg, #1e1b4b, #0f172a) } }. Before Chrome 115 and Safari 26, same rendering as Firefox. Without JavaScript: nothing to lose, there is none.

The code

HTML structure to paste into your page (the CSS + JS come with Premium):

index.html — structure
<div class="demo-preview">
  <div class="sdc-scroll-container">
    <div class="sdc-color-content">
      <div class="sdc-color-bg"></div>
      <div class="sdc-color-section">
        <div class="sdc-color-text">
          <h3>…</h3>
          <p>…</p>
        </div>
      </div>
    </div>
  </div>
  <span class="sdc-scroll-hint">…</span>
</div>
Unlock the full code — €9.90/mo excl. tax

The full HTML + CSS + JS of Color Shift, ready to paste — plus all 811 effects. Or €299 once, lifetime access.

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
Palettes (CSS — @keyframes sdcColorShift) 5 steps: #1e1b4b→#0f172a, #4c1d95→#1e1b4b, #7c3aed→#4c1d95, #ec4899→#7c3aed, #f97316→#ec4899 Each step is a complete two-color gradient: replace them with yours, keeping the form. The number of steps sets the number of cuts — four steps, three switches, at 1/6, 3/6 and 5/6 of the travel in Chromium. A solid color per step (background: #1e1b4b) behaves the same: by switch, no fade.
Travel (CSS — .sdc-color-content { height: 800px }, .sdc-scroll-container { height: 280px }) 520 px (switches at 66, 196, 326 and 456 px) The timeline covers exactly stage − box. Your content replaces the 800 px (height: auto) and the switches reposition themselves at 12.5 / 37.5 / 62.5 / 87.5% of the new travel; a taller box (60vh) needs nothing else changed.
Scroller (CSS — animation-timeline: scroll()) scroll() = nearest scroll container scroll(root) reads the page's scroll: the background then follows the whole page, with no box (remove overflow-y, set the background to position: fixed; inset: 0 behind the content). A named timeline — scroll-timeline: --fond y on the scroller, animation-timeline: --fond on the background — drives a background placed outside the scroller.
Travel window (CSS — animation-range: normal) normal (0 → 100%) animation-range: 0 60% runs through the five palettes by three fifths of the travel then keeps the orange; 20% 100% leaves the night on the first fifth. The switches tighten accordingly.
Gradient angle (CSS — 135deg in each step) 135° (top left → bottom right) The gradient is drawn over the stage's 800 px, not the 280 px window: the box only shows a slice of it, almost flat, sliding with the scroll. 90deg makes it horizontal and fully visible at every position.
Text (CSS — .sdc-color-text; h3 1.5 rem weight 800, p .75 rem at 60%) centered at 400 px, visible from 96 to 424 px The block travels with the stage. For a title visible from rest, switch it to position: sticky; top: 0 inside the section, or fill the whole height (lists, paragraphs): the background covers the whole stage.
Box dressing (CSS — scroll-behavior: smooth, ::-webkit-scrollbar 4 px, .sdc-scroll-hint) smooth / 4 px purple bar / “Scrollez ↓” pulsing 2 s smooth only eases programmatic scrolls; the styled bar only exists in Blink and WebKit (Firefox keeps its own). The hint is a catalog leftover: hard-coded French, anchored to .demo-preview — delete the span, the @keyframes sdcHintPulse goes with it.

FAQ

Because the steps animate background with linear-gradient() values, and a gradient is not interpolable: the specification treats it as a discrete value, the browser shows one step or the other, never a mix. Measured in Chromium 151: the switch happens at mid-segment (66, 196, 326, 456 px for 520 of travel); in WebKit 26.5, from the first pixel of the segment (1, 131, 261, 391 px). For a real cross-fade, animate colors, not images: declare two registered properties — @property --c1 { syntax: '<color>'; inherits: false; initial-value: #1e1b4b }, same for --c2 —, write background: linear-gradient(135deg, var(--c1), var(--c2)) on .sdc-color-bg and vary --c1 and --c2 in the steps: a registered <color> interpolates, so the gradient then slides continuously (Chrome 85+, Safari 16.4+).
That the element containing .sdc-color-bg and all your content is positioned and spans the whole scrollable height. The background is position: absolute; inset: 0: it takes the dimensions of the first positioned ancestor. In the sold code that is .sdc-color-content (position: relative, 800 px). If your section has no position, the positioned ancestor becomes the scrolling box itself (.sdc-scroll-container is position: relative): the background is only as tall as the visible height, 280 px, leaves with the content and exits through the top — measured: a 20 px strip at 260 px of scrolling, nothing from 326 on, the last two palettes never shown. One line is enough: position: relative on the background's direct parent.
Same box (280 px, 800 px stage, 520 px travel), same animation-timeline: scroll(), but neither the same property nor the same surface. fx-0554 animates the background-color of a 180 px square — #6366f1 → #d946ef → #06b6d4 → #10b981, four steps — and the color of a caption: a color interpolates, so it slides continuously (measured: rgb(133, 93, 240) at 50 px, rgb(168, 83, 240) at 100 px), but around the square the background stays black. fx-0559 animates the background of the whole backdrop with five gradients: the whole section changes mood, in clean switches. fx-0585 Sticky Image Change is closer on the mechanism — four linear-gradient(135deg, …) held in stages (0–20, 25–45, 50–70, 75–100%) on animation-timeline: scroll(nearest), hence the same clean switches — but not on the surface nor the structure: what changes color is a 120 px band in position: sticky; top: 10px, which stays in view and signals the section being read by its tint, while four text panels each reveal themselves on entry through animation-timeline: view() (animation-range: entry 0% entry 50%); the box's own background stays dark (#0a0a0f). fx-0559 has neither sticky nor panels: a single absolute background under the whole section, and nothing else moves. Pick fx-0554 to tint an object set in a text, fx-0585 for a pinned marker that goes along with panels, fx-0559 to change the atmosphere of an entire section.