# 逐字点亮 Scroll Text Highlight > 一大段灰色文字跟着滚动一个字一个字亮起来,往回滚就暗回去。 分类:动效 · 角色:点缀(贴在某个元素上,一页两三个) [全部组件和流程](https://www.dachuiai.com/llms.txt) · [动效选型规则](https://www.dachuiai.com/guides/motion.md) ## 给 AI:用户把这个组件发给了你 1. 先看用户的项目:框架、页面类型、现在的风格、手机还是电脑为主。 2. 对照下面的「适合 / 不适合」判断合不合适。不合适就直接说,并从[动效选型规则](https://www.dachuiai.com/guides/motion.md)或[全部组件](https://www.dachuiai.com/llms.txt)里另挑一个。 3. 合适就按「装进项目」装好,示例里的文字和图片换成用户自己的。 4. 告诉用户用了什么、放在哪个文件、以后想改快慢、颜色、大小改哪个参数。 ## 什么时候用 - 适合:品牌理念、一句宣言、关于我们的一段话(50 到 150 字)。 - 不适合:长文章正文(逐字亮读着累);要快速扫读的信息。 - 适合的网站:官网、SaaS、作品集、活动页、本地小店 - 放在:内容区 - 手机上:好用:手指滑动一样逐字点亮。 - 性能代价:低:JS 只改一个 CSS 变量,几百个字也不卡。 - 常搭配:[视差滚动 Parallax](https://www.dachuiai.com/c/parallax.md)、[数字滚动 Count Up](https://www.dachuiai.com/c/count-up.md) ## 装进项目 - 写法:React 18 以上 + TypeScript + Tailwind CSS v4,一个文件就能用,不引用别的组件。 - 依赖:只用 react,不用另外安装 - 文件:原样存成 `scroll-highlight.tsx`(比如 `src/components/scroll-highlight.tsx`)。能跑命令就下载[原文](https://www.dachuiai.com/code/scroll-highlight.tsx),比手抄准:`curl -fsSL -o src/components/scroll-highlight.tsx https://www.dachuiai.com/code/scroll-highlight.tsx`(Windows 写 `curl.exe`)。下载完看一眼第一行是 `import`,不是网页。 - 导出:`ScrollHighlight` 是可以直接用的部分;最后的 `export default` 是演示示例,只作参考。 - 注意:accent 传要强调的词;强调色是 CSS 变量 --hl(默认天蓝 #7DD3FC),在 className 里写 [--hl:#B4642D] 换成网站的颜色。段落底部要能滚到屏幕中间偏上才会全部亮完,放在页面最后时下面留出大半屏空白。 - 保留代码里 `prefers-reduced-motion` 的处理:用户开了「减弱动态效果」时内容照样完整显示。 - Next.js(有 app 文件夹):文件第一行加 `'use client';`。 - 用 JavaScript 不用 TypeScript:Vite 和 Next.js 都能直接用 .tsx 文件;也可以删掉类型标注,改名 .jsx。 - 没有 Tailwind:先装 Tailwind CSS v4(Vite 项目 `npm i tailwindcss @tailwindcss/vite`,vite 配置的 plugins 里加 `tailwindcss()`,入口引入的那个 CSS 文件(一般是 `src/index.css`)第一行写 `@import "tailwindcss";`),或者把类名改写成项目用的样式。 - 已经装了 Tailwind v3:别为了一个组件升级。类名大多通用,少数 v4 写法要换,比如焦点框 `outline-2` 在 v3 里要写成 `outline outline-2`。 - 不是 React(Vue、Svelte、小程序、纯 HTML):照着代码的做法改写,时长、曲线、参数保持一样。Wix、有赞这类建站平台装不进代码,告诉用户去平台自带的动效设置里找。 ## 参数 | 参数 | 默认值 | 作用 | | --- | --- | --- | | `text` | | 要显示的文字 | | `scroller` | | 滚动盒子的 ref;整页滚动时不传 | | `soft` | `12` | 亮起来的过渡有几个字宽,越大越柔 | | `className` | `''` | 加在最外层的类名,用来调大小、位置、间距 | | `accent` | `[]` | 要换成强调色的词 | ## 你可以说 > 这段话做成 Scroll Text Highlight 逐字点亮:文字一开始是灰的,跟着滚动一个字一个字亮起来,往回滚就暗回去。 ## 完整代码 ```tsx import {useEffect, useRef, type CSSProperties, type RefObject} from 'react'; /* --------------------------------------------------------------- * ScrollHighlight 逐字点亮 * 一大段灰色的字,跟着滚动一个字一个字亮起来,读到哪亮到哪。 * 每个字的亮度都由 CSS 里的一个变量 --p(滚动进度)算出来, * JS 只改这一个数,几百个字也不卡。 * 整页用时 scroller 不传;放在可滚动的盒子里时,传这个盒子。 * ------------------------------------------------------------- */ type Props = {text: string; scroller?: RefObject; soft?: number; className?: string; accent?: string[]}; export function ScrollHighlight({text, scroller, soft = 12, className = '', accent = []}: Props) { const ref = useRef(null); const chars = [...text]; useEffect(() => { const el = scroller?.current; const target = el ?? window; let raf = 0, cur = 0, goal = 0; const tick = () => { const paragraph = ref.current; if (!paragraph) { raf = 0; return; } cur += (goal - cur) * 0.18; if (Math.abs(goal - cur) < 0.0004) cur = goal; paragraph.style.setProperty('--p', cur.toFixed(4)); raf = cur === goal ? 0 : requestAnimationFrame(tick); }; const read = () => { const paragraph = ref.current; if (!paragraph) return; const r = paragraph.getBoundingClientRect(); const top = el ? el.getBoundingClientRect().top : 0, h = el ? el.clientHeight : innerHeight; // 段落顶部到视口 70% 处开始亮,段落底部到视口 45% 处全部亮完 goal = Math.min(1, Math.max(0, (top + h * 0.7 - r.top) / (r.height + h * 0.25))); if (matchMedia('(prefers-reduced-motion: reduce)').matches) goal = 1; if (!raf) raf = requestAnimationFrame(tick); }; target.addEventListener('scroll', read, {passive: true}); read(); return () => { target.removeEventListener('scroll', read); cancelAnimationFrame(raf); }; }, [scroller]); const isAccent = new Array(chars.length).fill(false); for (const w of accent) { const at = text.indexOf(w); if (at >= 0) for (let i = at; i < at + w.length; i++) isAccent[i] = true; } return (

{chars.map((c, i) => ( {c} ))}

); } /* ---------------- 示例:产品理念段落 ---------------- */ const TEXT = '以前做一个网站,要先学三年代码。现在你只需要知道想要什么,再把它的名字说出来。名字说对了,AI 一次就能改对。这不是魔法,只是你终于学会了它的语言。'; export default function ScrollHighlightDemo() { const box = useRef(null); return (
); } ```