Scroll✨ Premium

Split Screen Sticky

A visual pinned 10 px from the top whose SVG icon turns 360° over the 720 px travel — scroll(), 0.5° per pixel, reversible with the gesture — while three blocks reveal themselves through view(). CSS only, 2.7 KB.

CSSSplitSticky

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

Raffle results of a village festival committee — the drum turns while the prizes reveal themselves, from the fifth to the first — Split Screen Sticky example 1

① Raffle results of a village festival committee — the drum turns while the prizes reveal themselves, from the fifth to the first

WhenThe “Raffle” page of a village festival committee's site, the day after the draw: the drum stays pinned at the top with the number of tickets sold and the date of the draw, and turns with the scroll while the prizes reveal themselves one by one, from the fifth to the first — the top prize is at the very bottom.
WhyThe prizes start invisible (opacity 0, 20 px lower) and only fade in the moment the reader brings them into the box, the top prize last: view() holds the suspense of the draw, panel by panel. The drum, for its part, points at nothing: it turns as far as you go down and turns back if you go back up, like a cage spun by hand. fx-0571 Rotation on Scroll turns a needle on a graduated dial above text that scrolls in plain sight, with no fade at all: a value to read, not a prize waiting its turn.
Settings320 px box under a 40 px bar, track in height: auto: the travel follows the content, 516 px measured, 0.7° per pixel; 84 px band in orange #c2410c → #f97316, 52 px drum (lattice and three solid balls) drawn inside the shipped <svg>, title and draw figures to its right (only the svg turns, the text stays put); five white blocks with dark text, from the 5th prize to the 1st, the top prize on a cream background with a #fdba74 border. Timelines unchanged: at rest, prizes 5 and 4 settled (1 and .99), the other three at 0 — the 3rd prize reveals itself between 79 and 145 px of scrolling, the top prize between 368 and 434 (measured).
Engineering firm team page — the certification gear turns as the profiles go by — Split Screen Sticky example 2

② Engineering firm team page — the certification gear turns as the profiles go by

WhenThe “Team” page of an engineering consultancy: the certification band (ISO 9001, Qualiopi, OPQIBI) stays visible with its gear turning with the scroll, while the team members and their specialities pass underneath.
WhyThe gear turns as far as you go down, without growing or changing colour: a stable landmark above a list of people. fx-0590 Zoom In Sticky enlarges its 120 px box from 1 to 2.2× (scale on scroll(), with no sticky despite its name): a reveal that ends up overflowing, not an anchor that stays in place.
Settings316 px box under a 44 px bar, four profiles (name in h4, role in p, three specialities in a list), measured travel 376 px, i.e. 0.96° per pixel; band #1e3a5f → #0f2745, gear in an amber stroke (.ss-split-visual svg { stroke: #fbbf24 }, the only band rule changed) with a solid dot on one tooth so the rotation can be read; page background #0c1424. At rest, the second profile is already at .93.
First-aid gestures learning module — the chest-compression pictogram pivots with the steps — Split Screen Sticky example 3

③ First-aid gestures learning module — the chest-compression pictogram pivots with the steps

WhenA lesson in an online first-aid course: the chest-compression pictogram (person lying down, stacked hands, arrow) stays displayed at the top and pivots while the protocol steps — check and call for help, 30 compressions, 2 rescue breaths, repeat the cycles in under 10 s — reveal themselves as you scroll.
WhyThe rotation is indexed on the reading: a learner going back up to step 2 sees the pictogram turn back, in sync with the step being reread — a reversibility only a scroll timeline gives. fx-0585 Sticky Image Change can only show a colour band that changes in steps, not a procedural illustration of a gesture.
Settings316 px box under a 44 px bar, four numbered steps, measured travel 376 px; red band #dc2626 → #ef4444, 52 px white-stroke pictogram drawn inside the shipped <svg> (the .ss-split-visual svg selector turns it as is); #fff5f5 background, white blocks with #fecaca borders, dark text. The protocol figures are in the lists: 5 to 6 cm, 100 to 120 per minute.

How it works

The sold HTML nests five levels: the .demo-preview frame (centered flex, min-height: 300px, position: relative, #0a0a0f background, white text), the .sdc-scroll-container box (100% × 280px, overflow-y: auto, scroll-behavior: smooth, 4 px bar through ::-webkit-scrollbar), the .ss-split-content track (1,000 px, padding: 12px, flex column), the .ss-split-visual band (full width × 100 px, #1a1a3e → #2d1b69 gradient, position: sticky; top: 10px; z-index: 2) with its 60 px white-stroke SVG, then the .ss-split-items stack and its three 120 px .ss-split-item blocks, laid out from 128 to 520 px. Below the box, the .sdc-scroll-hint label “Scrollez ↓” pulses between .3 and .7 every 2 s. Travel: 1000 − 280 = 720 px. 2.7 KB of CSS, empty JavaScript field, no site variable, no external resource, no console error; the only foreign leftover is an inline style="font-family: Inter…" on the three h4.

The rotation is one rule: .ss-split-visual svg { animation-name: ssRotateSvg; animation-timeline: scroll(); animation-range: normal; animation-duration: auto; animation-timing-function: linear; animation-fill-mode: none }, with @keyframes ssRotateSvg { to { transform: rotate(360deg) } }. scroll() with no argument takes the nearest scroller — the 280 px box, not the page — and normal spreads the animation over its whole travel: progress = scrollTop ÷ 720, i.e. 0.5° per pixel. Measured in Chromium 151 with 120 px wheel steps: 60°, 120°, 180°, 240°, 300°, then transform: none at 720 px, which is 360°. Three steps back give 180°, then 0°: nothing turns without the reader, and the icon retraces its steps with them. sticky; top: 10px holds the band 12 px from the top of the box at rest (the track's padding), 10 px from the first pixel, and all the way down — its containing block is the whole track.

The blocks follow a second timeline: .ss-split-item { animation-name: sbFadeUp; animation-timeline: view(); animation-range: entry entry 50%; animation-fill-mode: both }, from opacity: 0; transform: translateY(20px) to 1 and translateY(0). view() tracks the block's own position in the box: the fade starts when its top touches the bottom edge and ends once half the block (60 px) is in. At rest, block 1 (128 → 248 px) is fully inside the box: opacity 1. Block 2 starts at 264 px, 16 px before the edge: 13% in, 27% of its range — opacity .27, offset 14.7 px, measured; it reaches 1 after 44 px of scrolling. Block 3 (400 px) stays at 0 until 120 px and is 1 from 180. Every block that climbs passes under the band (z-index: 2). At 520 px, the last block has left the box: the last 200 px of travel (28%, 100° of rotation) show only the pinned band over the empty background.

Accessibility

  • prefers-reduced-motion is absent from the code: measured under emulation in Chromium, nothing changes — 60° at 120 px, block 2 at .27, pulsing hint, scroll-behavior: smooth. The motion is driven by the reader (interaction-triggered animation, WCAG 2.3.3) and nothing is hidden, but add @media (prefers-reduced-motion: reduce) { .ss-split-visual svg, .ss-split-item, .sdc-scroll-hint { animation: none } .ss-split-item { opacity: 1; transform: none } .sdc-scroll-container { scroll-behavior: auto } } — opacity and transform are essential: animation: none alone leaves the blocks invisible.
  • Keyboard: measured, Tab focuses the box in Chromium and Firefox with no tabindex — Down arrow moves 40 px, i.e. 20° (51 px in Firefox), PageDown 245 px, End goes to 720; WebKit does not focus it (focus stays on body). Add tabindex="0", an aria-label and a :focus-visible style. On touch, scrolling is native (no listener); overscroll-behavior: contain keeps the page from being dragged along at the end of the travel.
  • Contrast on the blocks (4% white over #0a0a0f): headings 18.4:1; paragraphs at 50% white 5.3:1 at 11.5 px; lists at 40% white 3.8:1, below the 4.5:1 threshold — raise them to rgba(255,255,255,.55). The “Scrollez ↓” hint is 30% white and its opacity pulses from .3 to .7 — the two multiply: 2.6:1 static, 1.2:1 to 1.8:1 under the pulse, at 10.4 px; decorative, give it aria-hidden="true". A settled block is never semi-transparent: the fade only concerns its entry.
  • Screen readers and frame: the SVG has neither <title> nor aria-hidden — give it one or the other depending on whether it carries meaning. Blocks at opacity: 0 are still read in order. Three h4 with no h2/h3 above: use the level that follows your heading. The demo texts are French with missing accents (“Renforcee”, “Protegez vos donnees”). Paste the HTML with its frame: without .demo-preview, measured, the box has no background left — white text on a white page — and the hint drops to the bottom of the viewport.

Browser compatibility

CSS only: animation-timeline: scroll() and view(), animation-range, animation-duration: auto — all three arrived together in Chrome and Edge 115, Safari 26 (iOS 26). Firefox: no version (module behind layout.css.scroll-driven-animations.enabled, off by default). The rest is classic: position: sticky, flexbox, gradient, @keyframes, scroll-behavior. Zero dependency, zero JavaScript. Measured in WebKit 26.5: same values as Chromium (60° at 120 px, block 2 at .27, 360° at 720).

Chrome 115+✓ Full
Edge 115+✓ Full
Safari 26+✓ Full (measured in WebKit 26.5)
Firefox✓ No version: band pinned but motionless, three blocks visible and flat (measured in Firefox 153)
Mobile iOS 26+✓ Full (touch scrolling of the box)
Android Chrome 115+✓ Full

Measured in Firefox 153, sold code, blank page: CSS.supports('animation-timeline: scroll()') returns false, animation-duration: auto is rejected and computes to 0s — both animations fall back to the document clock and last 0 s. The SVG keeps transform: none (fill-mode: none): the icon does not turn, at any position. The blocks take their final frame (fill-mode: both): opacity 1, translateY(0) from load — all three read flat, the third included, before any scrolling. The band stays pinned, the box scrolls with its native bar. No @supports fallback in the code, and there is nothing to catch: the keyframes' final state is the readable state. To spell it out: @supports not (animation-timeline: scroll()) { .ss-split-item { animation: none; opacity: 1; transform: none } }. Same rendering in Chrome before 115 and Safari before 26.

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="ss-split-content">
      <div class="ss-split-visual">
        <svg>…</svg>
      </div>
      <div class="ss-split-items">
        <div class="ss-split-item">
          <h4>…</h4>
          <p>…</p>
          <ul><li>…</li><li>…</li><li>…</li></ul>
        </div>
        <div class="ss-split-item">…</div>
        <div class="ss-split-item">…</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 Split Screen Sticky, ready to paste — plus all 811 effects. Or €299 once, lifetime access.

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
Travel (CSS — .ss-split-content { height: 1000px }) 1,000 px (travel 720, 0.5° per pixel) Travel = track − box, and the rotation always spreads over all of it. 570 px → travel 290, 1.24° per pixel, text and rotation end together (last block parked under the band, measured). height: auto with your own blocks: the travel follows the content.
Box height (CSS — .sdc-scroll-container { height: 280px }) 280 px At 400 px (measured): travel 600, blocks 1 and 2 settled from load, block 3 at 0. Remove height and overflow-y to let the page scroll itself: scroll() and view() then climb to the viewport.
Angle (CSS — @keyframes ssRotateSvg { to { transform: rotate(360deg) } }) 360° (one turn over the travel) 720deg two turns, -360deg the other way, 180deg a half turn — measured: the icon stays at 180° at the end of the travel despite fill-mode: none. Add scale(1.2) in the same frame to turn and grow together.
Fade range (CSS — .ss-split-item { animation-range: entry entry 50% }) entry 0% → entry 50% (60 of the 120 px) entry entry 100% spreads the fade over the whole entry (block 2 at .13 at rest instead of .27, measured); entry 0% entry 30% tightens it (.44). Keep an end before entry 100%: the block must be sharp before the eye reaches it.
Fade amplitude (CSS — .ss-split-item { opacity: 0; transform: translateY(20px) }) 0 → 1, 20 px rise The start is in the rule, the arrival in @keyframes sbFadeUp. translateY(40px) emphasises the rise; translateY(0) scale(.96) replaces it with a slight growth.
Pinning (CSS — .ss-split-visual { top: 10px; height: 100px; z-index: 2 }) 10 px from the top, 100 px tall, above the blocks top: 0 glues the band to the edge; height shifts the first block (12 px + height + 16 px); without z-index: 2, the climbing blocks would pass over the band instead of sliding under it.
Visual (HTML — <svg width="60" height="60">; CSS — .ss-split-visual svg { stroke: #fff; stroke-width: 1.5 }) 60 px, 1.5 white stroke, three layers Replace the content of the <svg> (logo, pictogram): every svg in the band turns, an <img> does not — widen the selector (.ss-split-visual svg, .ss-split-visual img). Text placed next to the SVG inside the band stays fixed: the three scenes on this page do that.

FAQ

Because view() measures each block's entry into the 280 px box and entry entry 50% ends the fade once 60 px have passed. At load, block 2 starts at 264 px: 16 px already in, 13% of its height, 27% of the range — opacity .27, measured. Block 3 starts at 400 px, 120 px below the edge: 0 until scrolling brings it there, 1 at 180 px. That is the geometry of the sold code, nothing is broken. For a clean rest state: a 400 px box (blocks 1 and 2 at 1, measured), a shorter range (entry 0% entry 30%: .44), or padding-top: 32px on .ss-split-items — block 2 then starts at 280 px, just outside the box, at 0.
Yes, in the sold code: the three blocks stop at 520 px on a 1,000 px track. After 520 px of scrolling the last block has left the box and 200 px of travel remain (28%) during which the icon goes from 260° to 360° over the empty background, measured. Three remedies: .ss-split-content { height: 570px } — travel 290, 1.24° per pixel, the third block stops parked under the band (110 → 230 px, measured); height: auto with enough blocks for the travel to come from the content (the scenes on this page: 516 px for five blocks, 376 for four); or keep 1,000 px and add three blocks — at the end of the travel, the sixth stops at 88 px, its top slid 22 px under the band (measured).
The skeleton is the same — a 280 px .sdc-scroll-container box, a band stuck at 10 px, panels on view() over entry 0% entry 50% — but not the top timeline. In fx-0585 (free), ssImgChange on scroll(nearest) animates the band's background in steps: four gradients held over 0–20%, 25–45%, 50–70% and 75–100% of the 920 px travel, with a 5% transition between two — four discrete states, a fixed “01”, no image. Here the timeline animates a continuous rotation of the SVG illustration: 0.5° per pixel, every position has its angle, and scrolling back undoes it degree by degree. fx-0585 to signal sections (one colour = one step); fx-0589 for an object the reader handles with the gesture — icon, gear, pictogram — that must come back with them.