• AaronConlon.dev
  • Talk
  • Weekly
  • 工具
  • 编程
  • 翻译
  • 资讯
  • 通识

CSS 文字高亮扫光,为什么看起来会动?

2026年07月20上次更新于 大约 2 个月前
编程

hello!大家好!

shimmer text 是一种很常见的 CSS 文字高亮扫光效果:文字本身不动,一条亮色渐变从字形里扫过去,让它看起来像被光擦了一下。

你可能在 loading 文案、AI 正在生成的提示、产品首页 hero、价格标签里都见过它。效果很小,但如果写得舒服,页面会多一点“正在运行”的感觉。

现在 AI 已经能帮我们生成很多代码了,但我反而更觉得,一些基础技术还是值得自己弄懂。至少要做到**知其然!**看到一个 CSS 动效时,知道它大概由哪些属性拼起来,出了问题也知道从哪里拆。

所以这篇先不急着贴页面和仓库。我会先一步步拆 shimmer text 的原理:渐变怎么做出来,为什么能裁进文字,背景为什么要移动,倾斜高亮为什么还要算安全距离。

专门的教程页面和源码仓库我放在文末,读完原理以后再打开看,会更顺。

这个效果叫什么

一般可以叫它:

  • shimmer text

  • text shimmer

  • gradient text shimmer

  • 或者更直白一点:文字高亮扫光效果

它经常出现在这些场景里:

  • loading 状态里的文字或骨架屏;

  • AI 产品里“正在生成”的提示;

  • 首页 hero 里的重点短句;

  • 产品价格、功能标签、活动入口里的强调文案;

  • 需要一点“正在运行”感觉的交互界面。

我个人会比较克制地用它。

因为 shimmer 很容易从“有质感”滑到“过度动效”。如果整个页面到处都在闪,用户的注意力会被拖着走,阅读会变累。我的倾向是:只在一个短句、一个状态、一个核心 CTA 周围使用它。

先一步步拆开原理

先把页面和工具放一边,单看这个效果本身。

它背后其实有三层东西:

  • 文字本身;

  • 一张比文字更宽的渐变背景;

  • 把背景裁剪进文字形状的 background-clip:text。

这张图基本就是它的工作方式:小黑在后面推一条高亮带,文字像窗口一样,只露出背景穿过字形的那一部分。

第一步:先做一条静态高亮背景

先不考虑文字,也不考虑动画。

我们只需要一条渐变:


.shimmer {

  background-image: linear-gradient(

    80deg,

    #8a8a8a,

    #ffffff,

    #8a8a8a

  );

}

这一步只是准备材料。

#ffffff 是最亮的高光,左右两侧的灰色负责让它过渡得自然一点。shimmer 的质感,通常来自这个亮区的宽度:太宽会像一大片灯扫过去,太窄又会显得硬。

在 demo 里,我把高亮宽度做成了可调参数。这样读者可以直接看到:高亮区域越窄,效果越像一道光;高亮区域越宽,效果越像一块亮背景。

第二步:把背景裁剪进文字

这里进入关键 CSS:


.shimmer {

  color: transparent;

  background-image: linear-gradient(

    90deg,

    #ff4d6d,

    #ffd166,

    #06d6a0,

    #4cc9f0,

    #8a5cf6

  );

  background-clip: text;

  -webkit-background-clip: text;

}

这里有三个点要一起看:

  • color: transparent:把原本的文字填充色藏起来;

  • background-image:给文字背后放一张背景;

  • background-clip: text:只在文字字形范围里显示背景。

这时候读者看到的“字”,其实是背景图透过字形露出来的区域。

这里也有一个小坑。

如果你前面已经设置了 background-clip:text,后面又写了一次 background: ...,background 这个 shorthand 可能会把一些 background 相关属性重置掉。为了少踩坑,我更推荐在这个效果里使用 background-image,再单独写 background-size、background-position。

第三步:让背景比文字更宽

如果背景和文字一样宽,动画空间会很紧。

高亮带刚开始移动,就可能已经压在第一个字母上了。restart 的时候尤其明显:你以为动画会从最左侧外面进入,结果高亮一刷新就贴在文字上。

所以 demo 里用了:


.shimmer {

  background-size: 400% 100%;

}

意思是:背景的宽度是文字可见区域的 4 倍。

这样高亮可以从文字左侧外面进入,穿过文字,再从右侧离开。文字本身不动,动的是这张更大的背景。

第四步:动画移动 background-position

动画部分其实很短:


.shimmer {

  animation: shimmer var(--speed) ease-in-out infinite both;

}



@keyframes shimmer {

  0% {

    background-position: var(--start-position) 0;

  }



  72%,

  100% {

    background-position: var(--end-position) 0;

  }

}

重点是 background-position。

0% 是背景起点,72% 到 100% 是收尾段。后面留一点时间不继续移动,动画循环时会更稳,不会像机械循环一样马上跳回来。

这个处理很小,但体感明显。

我在 demo 里还加了 pause、restart 和 speed 控制。它们方便观察参数变化:当速度变慢、角度改变、宽度收窄时,背景移动的边界会变得更容易看出来。

第五步:为什么要算 safeDistance

做到这里,普通 shimmer 已经能跑了。

让我多花时间的是这个问题:

点击 restart 时,怎样让高亮真的从文字外侧进入?

如果高亮是水平的,事情还算直观。多留一点左右距离就行。

但这次我们希望高亮是 45deg 倾斜的。倾斜以后,高亮带会因为文字高度产生额外横向投影。说得直白一点:同样一条斜线,要保证它完全不碰到文字,就要比水平情况多留一些距离。

于是 demo 里算了一个 safeDistance:


const angle = 45 * Math.PI / 180



const safeDistance =

  (shineWidth + textHeight * Math.abs(Math.cos(angle)) / 2)

  / Math.abs(Math.sin(angle))

这里不用把它想得太复杂。

我们只是根据:

  • 高亮带宽度 shineWidth;

  • 文字高度 textHeight;

  • 高亮角度 angle;

算出一段额外的水平移动距离。

demo 里蓝色虚线和黄色安全范围就对应这个关系:蓝色范围表示文字可见区域,黄色范围表示高亮为了完整进出,需要额外走过的区域。

如果你把角度调得更斜,或者把高亮宽度调得更大,安全距离就会变大。这个关系可以用三角函数解释,实际工程里把它封装成一个计算函数就够了。

最小版本可以这样写

一份比较完整的最小版本大概是这样:


<p class="shimmer">CSS is awesome, right?</p>



<style>

  .shimmer {

    --speed: 2.4s;

    --angle: 45deg;

    --shine-width: 120px;

    --start-position: 72%;

    --end-position: 28%;



    display: inline-block;

    color: transparent;

    font: 700 48px/1.1 system-ui;

    background-image: linear-gradient(

      var(--angle),

      #8a8a8a calc(50% - var(--shine-width)),

      #ffffff 50%,

      #8a8a8a calc(50% + var(--shine-width))

    );

    background-size: 400% 100%;

    background-position: var(--start-position) 0;

    background-clip: text;

    -webkit-background-clip: text;

    animation: shimmer var(--speed) ease-in-out infinite both;

  }



  @keyframes shimmer {

    0% {

      background-position: var(--start-position) 0;

    }



    72%,

    100% {

      background-position: var(--end-position) 0;

    }

  }

</style>

如果只想做一个静态展示,这些就够了。

如果想像 demo 一样支持角度、宽度、restart、pause,就需要再加一点 JS,动态更新 CSS 变量。我的建议是:先把 CSS 版本跑通,再做控制器。不要一上来就把调参面板、localStorage、教程轮播全塞进去。

我会注意的几个细节

这个效果写起来不难,影响质感的是细节:

  • 高亮不要太宽:短句里 2 个字母左右通常比较舒服。

  • 角度不要乱跳:45deg 很稳定,识别度也好。

  • 背景要足够大:background-size: 400% 100% 是一个好起点。

  • restart 要从外面进:不要只重置动画,还要确认 start-position 真的在文字外。

  • 正文阅读场景慎用:长段落一直闪,读者会累。

我个人觉得,shimmer text 最适合放在“状态变化”附近。

比如 AI 正在生成、模型正在思考、结果即将出现。它传达的是一种轻微的进行中状态。放在纯装饰场景里也能看,但容易喧宾夺主。

最后

这个小 demo 对我来说有一个挺有意思的点:很多 CSS 动效看起来像视觉技巧,拆开以后其实是几个很朴素的概念在配合。

linear-gradient() 做材料,background-clip:text 做裁剪,background-position 做移动,再用一点几何计算处理边界。

读者如果只想拿走一个结论,我会建议记住这句:shimmer text 的文字通常不动,动的是文字背后的背景。

先写到这里。后面如果我继续把这个 demo 做成更完整的 CSS 小课,可能会再补一篇,专门聊“如何把视觉效果做成可学习的交互说明”。

相关链接

专门的教程页面在这里:

CSS Shimmer Text Demo

源码也放到了 GitHub:

AaronConlon/shimmer-text-demo

这个页面一开始只是想复刻一个文本闪光效果,后来越做越像一个小教学工具:可以调速度、调高亮宽度、调角度,可以看完整背景,也可以看文字可见范围和计算后的安全范围。


not-by-ainot-by-ai
已读 0%
文章推荐
文章封面
TypeScript 中的 never、unknown 的使用时机
更新于 2024-09-24
编程
文章封面
JavaScript Class 浅解
更新于 2024-08-08
编程
GitHubTwitter / XMediumDevJuejin

友情链接

Jimmy
Jimmy
老胡
老胡
Submara
Submara
Bruce Song
Bruce Song
Scarsu
Scarsu
宇阳
宇阳
Steven Lynn's Blog
Steven Lynn's Blog
OJ·Jimmy (Other Jimmy)
OJ·Jimmy (Other Jimmy)
liruifengv - Web 开发者,Astro 项目成员,开源爱好者。
liruifengv - Web 开发者,Astro 项目成员,开源爱好者。