Styledex
← Index

Scrollytelling

滚动叙事

stylekitMITDashboard · dashboardExperimental · experimentalLight and dark · bothMedium · mediumBlue · blueMedium · mediumSerif · serifGeneral · general

滚动就是叙事进度条。一块 sticky 画布钉在视口中央,文字步骤逐段划过时触发画布状态切换——数字跳动、图形变形、图层点亮。信息随阅读进度一帧帧揭示,像纽约时报的数据长报道。

NNorthwind
Product · Pricing
Specimen
Make the work feel finished

One interface, dressed in this style. Compare type, color, radius, and density.

128
Active
4.8
Score
12ms
Edge

Attribution

"Scrollytelling" from https://github.com/AnxForever/stylekit (MIT). © AnxForever. api/styles/scrollytelling @ bfa7da909e86.

https://github.com/AnxForever/stylekit · api/styles/scrollytelling · bfa7da909e86 · © AnxForever

Do

  • 深底 #0E1116 + 暖白文字 #F7F5F0,营造数据长报道的沉浸感(也可反相:浅底深字)
  • 一块 sticky 画布 position: sticky; top: 0; height: 100vh 钉在视口,文字 steps 在其上滚动
  • 用 IntersectionObserver 监听每个 step 进入视口,切换画布到对应离散状态
  • 每一步只揭示一个焦点:一个数字、一条线、一个高亮层
  • 数字用 count-up 动画呈现,但真实数值写进 DOM 供读屏/SEO
  • 画布状态过渡只用 transform/opacity,时长 0.5-0.8s ease-out
  • 主强调信号蓝 #2F6FED 指示当前焦点,朱红 #E8503A 只用于对比/警示
  • step 文字块之间留足空白(每块至少 60-80vh),给读者思考与画布过渡的时间

Don't

  • 禁止在 scroll 事件里读 offsetTop/getBoundingClientRect 做连续 scrub(卡顿)——用 IntersectionObserver 触发离散状态
  • 禁止一步塞多个新信息(叙事张力崩塌)
  • 禁止画布状态改布局属性(top/width/height)——只用 transform/opacity
  • 禁止让计数动画成为唯一数值来源(读屏/SEO 读不到)
  • 禁止移动端画布挤掉文字
  • 禁止超过两个强调色
  • 禁止省略 prefers-reduced-motion 瞬时降级

Prompt

Create a scrollytelling data feature with:
1. Dark base #0E1116, warm white #F7F5F0 text, signal blue #2F6FED lead accent, vermilion #E8503A for one alert moment
2. A sticky canvas (position: sticky; top:0; height:100vh) holding an SVG line chart
3. 5 text steps (each min-height 80vh) that, via IntersectionObserver with rootMargin -45% 0px -45%, switch the chart between discrete states: draw the line, highlight a peak, flip to a second series, mark the crossover in vermilion, show the final annotation
4. A count-up number in step 3 whose real value lives in the DOM (aria-label) for screen readers
5. A top reading-progress bar driven by scroll
6. All canvas transitions transform/opacity only, 0.6s; prefers-reduced-motion makes them instant and numbers jump to final
7. Mobile: canvas pinned to top half, steps below

---
"Scrollytelling" from https://github.com/AnxForever/stylekit (MIT). © AnxForever. api/styles/scrollytelling @ bfa7da909e86.
powered by Styledex — https://styledex.dev

Tokens

DTCG

{
  "$schema": "https://www.designtokens.org/schemas/2025.10/format.json",
  "$description": "Scrollytelling. \"Scrollytelling\" from https://github.com/AnxForever/stylekit (MIT). © AnxForever. api/styles/scrollytelling @ bfa7da909e86.",
  "$extensions": {
    "dev.styledex": {
      "id": "stylekit-scrollytelling",
      "license": "MIT",
      "attribution": "\"Scrollytelling\" from https://github.com/AnxForever/stylekit (MIT). © AnxForever. api/styles/scrollytelling @ bfa7da909e86.",
      "banner": "powered by Styledex — https://styledex.dev"
    }
  },
  "color": {
    "background": {
      "$type": "color",
      "$value": "#0e1116"
    },
    "foreground": {
      "$type": "color",
      "$value": "#f7f5f0"
    },
    "card": {
      "$type": "color",
      "$value": "#0e1116"
    },
    "card-foreground": {
      "$type": "color",
      "$value": "#f7f5f0"
    },
    "primary": {
      "$type": "color",
      "$value": "#0e1116"
    },
    "primary-foreground": {
      "$type": "color",
      "$value": "#fafaf9"
    },
    "secondary": {
      "$type": "color",
      "$value": "#f7f5f0"
    },
    "secondary-foreground": {
      "$type": "color",
      "$value": "#f7f5f0"
    },
    "accent": {
      "$type": "color",
      "$value": "#2f6fed"
    },
    "accent-foreground": {
      "$type": "color",
      "$value": "#fafaf9"
    },
    "muted": {
      "$type": "color",
      "$value": "#1c1f24"
    },
    "muted-foreground": {
      "$type": "color",
      "$value": "#a19f9c"
    },
    "border": {
      "$type": "color",
      "$value": "#303237"
    },
    "destructive": {
      "$type": "color",
      "$value": "#dc2626"
    },
    "ring": {
      "$type": "color",
      "$value": "#0e1116"
    }
  },
  "font": {
    "sans": {
      "$type": "fontFamily",
      "$value": [
        "ui-sans-serif",
        "system-ui",
        "sans-serif"
      ]
    },
    "serif": {
      "$type": "fontFamily",
      "$value": [
        "ui-serif",
        "Georgia",
        "serif"
      ]
    },
    "mono": {
      "$type": "fontFamily",
      "$value": [
        "ui-monospace",
        "SFMono-Regular",
        "Menlo",
        "monospace"
      ]
    }
  },
  "radius": {
    "base": {
      "$type": "dimension",
      "$value": "0.5rem"
    }
  },
  "spacing": {
    "base": {
      "$type": "dimension",
      "$value": "1rem"
    }
  },
  "shadow": {
    "base": {
      "$type": "shadow",
      "$value": "0 4px 16px rgba(0,0,0,0.5)"
    }
  }
}

CSS

/* powered by Styledex — https://styledex.dev */
/* "Scrollytelling" from https://github.com/AnxForever/stylekit (MIT). © AnxForever. api/styles/scrollytelling @ bfa7da909e86. */
:root {
  --background: #0e1116;
  --foreground: #f7f5f0;
  --card: #0e1116;
  --card-foreground: #f7f5f0;
  --primary: #0e1116;
  --primary-foreground: #fafaf9;
  --secondary: #f7f5f0;
  --secondary-foreground: #f7f5f0;
  --accent: #2f6fed;
  --accent-foreground: #fafaf9;
  --muted: #1c1f24;
  --muted-foreground: #a19f9c;
  --border: #303237;
  --destructive: #dc2626;
  --ring: #0e1116;
  --radius: 0.5rem;
  --font-sans: ui-sans-serif, system-ui, sans-serif;
  --font-serif: ui-serif, Georgia, serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, monospace;
  --spacing: 1rem;
  --shadow: 0 4px 16px rgba(0,0,0,0.5);
}

Tailwind

/* powered by Styledex — https://styledex.dev */
/* "Scrollytelling" from https://github.com/AnxForever/stylekit (MIT). © AnxForever. api/styles/scrollytelling @ bfa7da909e86. */
/** Tailwind theme extend for Scrollytelling (stylekit-scrollytelling) */
module.exports = {
  "theme": {
    "extend": {
      "colors": {
        "background": "#0e1116",
        "foreground": "#f7f5f0",
        "card": "#0e1116",
        "primary": "#0e1116",
        "primary-foreground": "#fafaf9",
        "secondary": "#f7f5f0",
        "accent": "#2f6fed",
        "muted": "#1c1f24",
        "border": "#303237",
        "destructive": "#dc2626",
        "ring": "#0e1116"
      },
      "borderRadius": {
        "lg": "0.5rem"
      },
      "fontFamily": {
        "sans": [
          "ui-sans-serif",
          "system-ui",
          "sans-serif"
        ],
        "serif": [
          "ui-serif",
          "Georgia",
          "serif"
        ],
        "mono": [
          "ui-monospace",
          "SFMono-Regular",
          "Menlo",
          "monospace"
        ]
      },
      "boxShadow": {
        "styledex": "0 4px 16px rgba(0,0,0,0.5)"
      }
    }
  }
};

shadcn

/* powered by Styledex — https://styledex.dev */
/* "Scrollytelling" from https://github.com/AnxForever/stylekit (MIT). © AnxForever. api/styles/scrollytelling @ bfa7da909e86. */
@layer base {
:root {
  --background: #0e1116;
  --foreground: #f7f5f0;
  --card: #0e1116;
  --card-foreground: #f7f5f0;
  --primary: #0e1116;
  --primary-foreground: #fafaf9;
  --secondary: #f7f5f0;
  --secondary-foreground: #f7f5f0;
  --accent: #2f6fed;
  --accent-foreground: #fafaf9;
  --muted: #1c1f24;
  --muted-foreground: #a19f9c;
  --border: #303237;
  --destructive: #dc2626;
  --ring: #0e1116;
  --radius: 0.5rem;
  --font-sans: ui-sans-serif, system-ui, sans-serif;
  --font-serif: ui-serif, Georgia, serif;
  --font-mono: ui-monospace, SFMono-Regular, Menlo, monospace;
  --spacing: 1rem;
  --shadow: 0 4px 16px rgba(0,0,0,0.5);
}
}