Skip to content

Dual scroll

<cinq-dual-scroll> drives two columns with inverted scroll and spring easing. Layout, pane content, and zone height stay in your markup and CSS. The component listens for wheel/touch inside the host and updates column transforms.

Inspired by scroll-you-fools, scoped to a zone instead of hijacking the full page.

Two columns, identified by data-scroll-column. Stack one pane per “step” inside each column (same count on both sides works best).

<cinq-dual-scroll spring="0.1" style="height: 70vh; overflow: hidden;">
<div class="dual-scroll__columns">
<div data-scroll-column="left">
<div class="dual-scroll__pane"><!-- pane --></div>
<div class="dual-scroll__pane"><!-- pane --></div>
</div>
<div data-scroll-column="right">
<div class="dual-scroll__pane"><!-- pane --></div>
<div class="dual-scroll__pane"><!-- pane --></div>
</div>
</div>
</cinq-dual-scroll>

Zone height is your responsibility: set an explicit height on <cinq-dual-scroll> (e.g. 70vh, 100% of a parent, or 100vh for full-page). The host needs overflow: hidden and touch-action: none so mobile swipes stay in the zone instead of scrolling the page.

Wheel input is captured when the pointer is over the host (desktop). Touch input starts inside the host (mobile).

Each pane should match the host height. The component sets --dual-scroll-pane-height on sync so panes can use:

cinq-dual-scroll {
overflow: hidden;
touch-action: none;
}
.dual-scroll__pane {
height: var(--dual-scroll-pane-height, 100%);
}

Column positioning (start off-screen, move on scroll) is also yours. See the demos below.

.dual-scroll-fallback {
display: none;
}
@media (prefers-reduced-motion: reduce) {
cinq-dual-scroll {
display: none;
}
.dual-scroll-fallback {
display: block;
}
}

When prefers-reduced-motion: reduce is set, hide <cinq-dual-scroll> and show a linear .dual-scroll-fallback in CSS, or set spring="1" yourself if you keep the effect without inertia. The component does not read that media query.

Scroll or swipe inside the zone. Right column goes down, left goes up.

<cinq-dual-scroll spring="0.1">
<!-- columns + panes -->
</cinq-dual-scroll>

spring is 0.01-1 (default 0.1). Lower = more inertia, 1 = instant.

spring="0.04": slow, floaty

spring="0.35": snappy

<cinq-dual-scroll spring="0.04">...</cinq-dual-scroll>
<cinq-dual-scroll spring="0.35">...</cinq-dual-scroll>
  • spring: easing factor (0.01-1, default 0.1)
  • Methods: init(), destroy(), sync()
  • CSS variable: --dual-scroll-pane-height (host height in px, set by sync())

Use init() / destroy() if you need to pause the effect without removing the element. sync() re-measures after DOM or size changes.

This pattern is decorative / editorial. It is not a replacement for normal page scroll or accessible reading flow. Prefer a linear fallback or skip link when the content must be reachable without wheel gestures inside a fixed zone.