直接用 animate-pulse 在 SSR 下会闪烁,因为服务端不执行动画逻辑,hydration 前后样式不一致;应通过 useEffect 延迟添加动画类,或自定义 shine 动画并配合加载状态精准控制。

为什么直接用 animate-pulse 在 SSR 下会闪烁
Next.js 默认服务端渲染,而 animate-pulse 依赖 CSS @keyframes pulse 和浏览器的动画时钟。服务端不执行动画逻辑,生成的 HTML 没有运行时状态,导致 hydration 前后样式不一致 —— 页面首次加载时骨架突然“跳一下”或闪退。
根本原因不是 Tailwind 写法错,而是 SSR + CSS 动画的天然冲突。解决思路不是禁用 SSR,而是让骨架只在客户端挂载后才启用动画。
- 避免在服务端渲染带
animate-pulse的元素(比如用useEffect控制显示) - 用
className动态拼接,初始不加动画类,hydration 后再补上 - 不要对骨架组件做
ssr: false全局禁用(影响 SEO 和首屏内容可访问性)
如何用 useEffect 安全添加 animate-pulse
最轻量的做法:把骨架容器包裹一层,初始只给基础样式(如背景色、圆角),等组件挂载完成再 patch 动画类。
import { useState, useEffect } from 'react';
function SkeletonCard() {
const [mounted, setMounted] = useState(false);
useEffect(() => {
setMounted(true);
}, []);
return (
<div
className={`
bg-gray-200 rounded-lg h-48 w-full
${mounted ? 'animate-pulse' : ''}
`}
/>
);
}
注意点:
立即学习“前端免费学习笔记(深入)”;
使用固定的 Next.js/Tailwind/Prisma 技术栈构建并记录独立 SaaS 项目,仅将 FastAPI 用于 Python 工作负载,并遵循严格的架构规范。
-
useEffect空依赖数组确保只在客户端执行一次 - 不要用
mounted && <div className="animate-pulse...">条件渲染整个 DOM —— 这会导致两次渲染,破坏布局稳定性 - 如果骨架是列表项,建议提升
mounted到父组件统一控制,避免每个 item 都触发重排
自定义骨架动画:比 animate-pulse 更可控
animate-pulse 是简单缩放,实际骨架常需模拟“光扫过”的流动感。Tailwind 允许你直接扩展 keyframes 和 animation,无需脱离工具链。
在 tailwind.config.js 的 theme.extend 中加入:
module.exports = {
theme: {
extend: {
animation: {
'shine': 'shine 2s infinite',
},
keyframes: {
shine: {
'0%': { 'background-position': '-200% 0' },
'100%': { 'background-position': '200% 0' },
}
}
}
}
}
然后在组件中使用:
<div
className="bg-gradient-to-r from-transparent via-gray-300 to-transparent
bg-[length:200%_100%] rounded-lg h-48 w-full
animate-shine"
/>
- 必须用
bg-[length:200%_100%]才能让渐变动起来(background-size控制扫描范围) - 渐变方向
from-transparent via-gray-300 to-transparent比纯灰更接近真实加载态 - 动画时长
2s比默认pulse的2s更平滑;太快易晕,太慢显卡顿
配合数据加载状态:别让骨架“假死”
骨架不是装饰,是加载反馈。常见错误是数据还没开始 fetch 就渲染骨架,或数据报错后骨架还在空转。
- 用
isLoading(如swr的isValidating或useQuery的isPending)精准控制骨架显隐 - 加超时 fallback:比如
setTimeout3 秒后若仍无数据,显示错误提示而非无限骨架 - 服务端预取数据(
getServerSideProps或generateStaticParams)时,骨架应完全跳过 —— 此时不应出现 - 移动端尤其注意:骨架高度要和最终内容一致,否则加载完成会触发页面“抖动”
真正难的不是写动画,是判断什么时候该动、什么时候该停、动多快、停多久 —— 这些都得贴着业务数据流走,而不是套个 class 就完事。


















