# 流光 Shimmer > 一道光从表面扫过:骨架屏上表示正在加载,按钮上吸引人去点。 分类:动效 · 角色:基础(手感,哪里都能用、不抢眼) [全部组件和流程](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. 告诉用户用了什么、放在哪个文件、以后想改快慢、颜色、大小改哪个参数。 ## 什么时候用 - 适合:数据加载时的骨架屏(列表、卡片、后台都适用);一个最想让人点的按钮。 - 不适合:一页好几个按钮都扫光;加载很快(不到 0.3 秒)时不用骨架屏。 - 适合的网站:官网、本地小店、电商、作品集、SaaS、后台、内容站、活动页 - 放在:列表、卡片、按钮 - 手机上:好用:iOS Safari 不支持固定背景,光带会每块各扫各的,不影响使用。 - 性能代价:低:纯 CSS 动画。 - 常搭配:[边缘流光 Border Beam](https://www.dachuiai.com/c/border-beam.md)、[错峰 Stagger](https://www.dachuiai.com/c/stagger.md) ## 装进项目 - 写法:React 18 以上 + TypeScript + Tailwind CSS v4,一个文件就能用,不引用别的组件。 - 依赖:`npm i lucide-react`(lucide-react 只有最后的演示示例用到;不留示例的话,删掉对应的 import 就不用装) - 文件:原样存成 `shimmer.tsx`(比如 `src/components/shimmer.tsx`)。能跑命令就下载[原文](https://www.dachuiai.com/code/shimmer.tsx),比手抄准:`curl -fsSL -o src/components/shimmer.tsx https://www.dachuiai.com/code/shimmer.tsx`(Windows 写 `curl.exe`)。下载完看一眼第一行是 `import`,不是网页。 - 导出:`Skeleton`、`ShimmerButton` 是可以直接用的部分;最后的 `export default` 是演示示例,只作参考。 - 图片:示例用了本站的 [/tex/s5.jpg](https://www.dachuiai.com/tex/s5.jpg)、[/tex/s2.jpg](https://www.dachuiai.com/tex/s2.jpg)。换成用户自己的图;只想先看效果,可以从本站同样的路径下载,放进项目的 public 目录。 - 注意:Skeleton 是骨架块,ShimmerButton 是扫光按钮,可以分开用。Skeleton 默认深色底、8px 圆角,className 里写 rounded-full、bg-zinc-200 就能换;光带是白色半透明,浅色底上想更明显,把 SKEL_CSS 里的 .16 调高。骨架屏只在第一次没数据、加载超过 0.3 秒时显示;之后重新加载时保留旧数据、稍微变淡。 - 保留代码里 `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、有赞这类建站平台装不进代码,告诉用户去平台自带的动效设置里找。 ## 参数 **Skeleton** | 参数 | 默认值 | 作用 | | --- | --- | --- | | `className` | `''` | 加在最外层的类名,用来调大小、位置、间距 | **ShimmerButton** | 参数 | 默认值 | 作用 | | --- | --- | --- | | `children` | | 包在里面的内容 | | `className` | `''` | 加在最外层的类名,用来调大小、位置、间距 | ## 你可以说 > 加载中的卡片用 Shimmer 流光骨架屏,一道光从左往右扫过;「立即领取」按钮也隔几秒扫过一道光。 ## 完整代码 ```tsx import {useEffect, useState, type ReactNode} from 'react'; import {Gift, RotateCcw, Star, Tent} from 'lucide-react'; /* --------------------------------------------------------------- * Shimmer 流光 * 一道光从表面扫过去。 * 用在骨架屏(Skeleton)上表示"正在加载";用在按钮上吸引人去点。 * 骨架块都用同一个固定背景(background-attachment: fixed), * 光带在整张卡片上是连成一条的,而不是每块各扫各的。 * 骨架块的默认圆角和底色放在 @layer components 里,className 里写的 rounded-full、bg-zinc-200 能盖过它。 * ------------------------------------------------------------- */ const SKEL_CSS = ` @layer components { .sh-skel { border-radius: .5rem; background: #20212B linear-gradient(100deg, transparent 30%, rgba(255,255,255,.16) 50%, transparent 70%) no-repeat fixed; background-size: 60vw 100%; animation: sh-skel 1.6s ease-in-out infinite; } @media (prefers-reduced-motion: reduce) { .sh-skel { animation: none; } } } @keyframes sh-skel { from { background-position: -60vw 0; } to { background-position: 120vw 0; } } `; const SWEEP_CSS = ` .sh-sweep { transform: translateX(-150%) skewX(-20deg); animation: sh-sweep 2.6s cubic-bezier(.4,0,.2,1) infinite; } @keyframes sh-sweep { 0%, 35% { transform: translateX(-150%) skewX(-20deg); } 75%, 100% { transform: translateX(400%) skewX(-20deg); } } @media (prefers-reduced-motion: reduce) { .sh-sweep { animation: none; } } `; export function Skeleton({className = ''}: {className?: string}) { return (
); } export function ShimmerButton({children, className = '', ...rest}: React.ButtonHTMLAttributes & {children: ReactNode}) { return ( ); } /* ---------------- 示例:商品卡加载中 → 加载完成,加一个领取按钮 ---------------- */ export default function ShimmerDemo() { const [loading, setLoading] = useState(true); useEffect(() => { if (!loading) return; const t = setTimeout(() => setLoading(false), 2400); return () => clearTimeout(t); }, [loading]); return (
{/* 加载完的内容淡入 */}
{loading ? (
) : (
山野官方店

轻量双人帐篷

1.6 kg,三分钟搭好,防风等级 6 级。

4.9 · 已售 2k
¥899
)}
立即领取
); } ```