_❯Scroll Morph

A scroll-progress-driven scale & translate morph using an easeInOutQuad curve — scrub it here with the slider.

scrollmorpheasingCSS
0%
1import { useState } from "react";
2import { LabsDemoLayout } from "../../components/labs-experiment-frame/labs-experiment-frame.component";
3import {
4 ControlGroup,
5 ControlPanel,
6 ResetButton,
7 SliderControl,
8} from "../../components/labs-control/labs-control.component";
9import * as styles from "./scroll-morph.demo.css";
10
11// Same easing the hero uses to map scroll progress (0..1) onto the morph.
12const easeInOutQuad = (t: number): number => (t < 0.5 ? 2 * t * t : 1 - (-2 * t + 2) ** 2 / 2);
13
14const MIN_SCALE = 0.62;
15const MAX_TRANSLATE_PX = 80;
16
17export function ScrollMorphDemo() {
18 const [percent, setPercent] = useState(0);
19
20 const progress = percent / 100;
21 const eased = easeInOutQuad(progress);
22 const scale = 1 - eased * (1 - MIN_SCALE);
23 const translateY = eased * MAX_TRANSLATE_PX;
24
25 return (
26 <LabsDemoLayout
27 stage={
28 <div className={styles.stageInner}>
29 <div
30 className={styles.morphCard}
31 style={{ transform: `translateY(${translateY}px) scale(${scale})` }}
32 >
33 {percent}%
34 </div>
35 </div>
36 }
37 controls={
38 <ControlPanel>
39 <ControlGroup title="Scroll progress">
40 <SliderControl
41 label="Progress"
42 value={percent}
43 min={0}
44 max={100}
45 step={1}
46 onChange={setPercent}
47 format={(value) => `${value}%`}
48 />
49 </ControlGroup>
50
51 <ResetButton onReset={() => setPercent(0)} />
52 </ControlPanel>
53 }
54 />
55 );
56}