animate-pulse适用于单个占位元素的微光效果,但需避免父容器继承;更真实的扫描光效需自定义animate-shimmer配合渐变背景与位移动画,并注意SSR、响应式及性能优化。

用 animate-pulse 实现基础骨架屏微光效果
Tailwind 自带的 animate-pulse 是最直接的微光动画方案,它本质是通过 opacity 在 0.25–1 之间循环变化,配合 transform: scale(1) 避免重排,性能足够好。但要注意:它默认只作用于单个元素,且动画节奏偏慢(2s 周期),不适合细粒度控制。
常见错误是直接套在容器上导致整块区域“呼吸式”闪烁,实际骨架屏需要的是「局部高亮扫描感」。建议只用于占位块本身,比如头像、标题栏:
<div class="h-10 w-10 rounded-full bg-gray-200 animate-pulse"></div> <div class="h-4 w-3/4 bg-gray-200 rounded animate-pulse mt-3"></div>
- 不要给父容器加
animate-pulse,否则子元素会继承并叠加动画 - 若需加速,可自定义动画:在
tailwind.config.js的theme.extend.animation中添加pulse-fast: 'pulse 1s cubic-bezier(0.4, 0, 0.6, 1) infinite' -
bg-gray-200在深色模式下可能不显眼,建议配合dark:bg-gray-700
用 bg-gradient-to-r + animate-shimmer 模拟扫描光效
真正接近设计稿的「微光扫过」效果,靠纯 animate-pulse 不够——它没有方向性。你需要用渐变背景 + 位移动画模拟光束掠过。Tailwind 默认不提供 animate-shimmer,必须手动扩展。
核心思路:用 bg-gradient-to-r 创建从透明到浅灰再到透明的线性渐变,再用 animate-[shimmer_2s_infinite] 水平移动背景位置。关键点在于动画帧定义要精确控制位移范围,避免光效突兀或卡顿:
立即学习“前端免费学习笔记(深入)”;
@layer utilities {
.animate-shimmer {
background-image: linear-gradient(
to right,
transparent 0%,
rgba(255, 255, 255, 0.3) 20%,
transparent 40%
);
background-size: 200% 100%;
animation: shimmer 2s infinite;
}
}
@keyframes shimmer {
0% { background-position: -200% 0; }
100% { background-position: 200% 0; }
}
- 务必把
@layer utilities放在 CSS 文件靠前位置,否则可能被 Tailwind 的 purge 移除 -
background-size: 200% 100%是关键,它让渐变宽度是元素的两倍,才能平滑滚动 - 若骨架元素有圆角(如
rounded-lg),需额外加overflow-hidden,否则渐变边缘会溢出
响应式骨架屏中 animate-pulse 的兼容性陷阱
在移动端或低配设备上,animate-pulse 可能触发强制重绘,尤其当骨架元素嵌套较深或使用了 filter: blur() 类时,iOS Safari 会出现掉帧甚至卡死。这不是 Tailwind 的问题,而是浏览器对 opacity 动画的优化策略差异。
- 避免在
flex或grid容器上同时启用animate-pulse和gap,某些 Android WebView 会误判为 layout change - 测试时打开 Chrome DevTools → Rendering → “Paint flashing”,确认只有骨架元素在闪,而非整个卡片区域
- 若需降级,可用
opacity-75+transition-opacity手动 toggle,牺牲动画但保稳定
服务端渲染(SSR)下骨架屏的显示时机问题
Next.js / Nuxt 等框架中,骨架屏常在 useEffect 或 onMounted 后才挂载,导致首屏白屏时间变长。Tailwind 的类名不会自动注入,你得确保骨架样式在 HTML 初始渲染时就存在。
- 不要用动态拼接类名(如
className={`bg-gray-200 ${loading ? 'animate-pulse' : ''}`),SSR 时loading为 false,类名丢失 - 正确做法:骨架结构始终存在,用
hidden/block控制显隐,或服务端直出完整骨架 HTML - Vite + Vue 项目要注意
define:vars是否影响@layer中的动画定义,建议将 shimmer 动画抽成独立 CSS 文件并import
微光动画最难的不是写法,而是判断「什么时候该停」——数据加载完成瞬间取消动画,比实现动画本身更容易出错。别依赖 setTimeout,用真实的 loading 状态驱动显隐切换,哪怕多一次 re-render 也比视觉错乱强。


















