# 缓动 Easing > 一组现成的速度曲线:进场先快后慢,离场更快更轻,一起动的元素用同一条。 分类:动效 · 角色:基础(手感,哪里都能用、不抢眼) [全部组件和流程](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. 告诉用户用了什么、放在哪个文件、以后想改快慢、颜色、大小改哪个参数。 ## 什么时候用 - 适合:任何项目都先统一它:过渡、弹窗、菜单、提示。 - 不适合:跟着滚动或手指走的动画(它们跟着手,不需要曲线)。 - 适合的网站:官网、本地小店、电商、作品集、SaaS、后台、内容站、活动页 - 放在:全站 - 手机上:好用:和电脑上一样。 - 性能代价:低:只是参数。 - 常搭配:[弹性 Spring](https://www.dachuiai.com/c/spring.md)、[错峰 Stagger](https://www.dachuiai.com/c/stagger.md) ## 装进项目 - 写法:React 18 以上 + TypeScript + Tailwind CSS v4,一个文件就能用,不引用别的组件。 - 依赖:`npm i lucide-react`(lucide-react 只有最后的演示示例用到;不留示例的话,删掉对应的 import 就不用装) - 文件:原样存成 `easing.tsx`(比如 `src/components/easing.tsx`)。能跑命令就下载[原文](https://www.dachuiai.com/code/easing.tsx),比手抄准:`curl -fsSL -o src/components/easing.tsx https://www.dachuiai.com/code/easing.tsx`(Windows 写 `curl.exe`)。下载完看一眼第一行是 `import`,不是网页。 - 导出:`EASE`,是做这个效果用的工具(hook、函数或常量);页面结构在最后的 `export default` 示例里,照着示例搭。 - 注意:Tailwind 项目不用下载这个文件,改默认曲线就行,现有的 transition 全部跟着变:v4 在 CSS 的 @theme 里写 --default-transition-timing-function 和 --ease-out;v3 在 tailwind.config 的 theme.extend.transitionTimingFunction 里写 DEFAULT。不用 Tailwind 时把 EASE 里的曲线抄进 transition / animation。 - 保留代码里 `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、有赞这类建站平台装不进代码,告诉用户去平台自带的动效设置里找。 ## 你可以说 > 所有进场动画改成 ease-out 缓动(先快后慢),离场比进场更快一点,一起动的元素用同一条曲线。 ## 完整代码 ```tsx import {useState} from 'react'; import {Bell, Play} from 'lucide-react'; /* --------------------------------------------------------------- * Easing 缓动 * 动的时候速度怎么变。匀速像机器;先快后慢(ease-out)最自然,进场都用它; * 离场比进场快一点、轻一点;一起动的东西用同一条曲线。 * 下面这几条可以直接抄进 transition / animation 里。 * ------------------------------------------------------------- */ export const EASE = { linear: 'linear', easeOut: 'cubic-bezier(.22, 1, .36, 1)', // 进场、展开、出现 easeIn: 'cubic-bezier(.55, 0, 1, .45)', // 离场、收起、消失 easeInOut: 'cubic-bezier(.65, 0, .35, 1)', // 在屏幕里从一处移到另一处 } as const; /* ---------------- 示例:同一张通知卡片,四种速度一起比 ---------------- */ const ROWS = [ {key: 'linear', zh: '匀速', css: EASE.linear, pts: [0.333, 0.333, 0.667, 0.667], hot: false}, {key: 'easeOut', zh: '先快后慢', css: EASE.easeOut, pts: [0.22, 1, 0.36, 1], hot: true}, {key: 'easeIn', zh: '先慢后快', css: EASE.easeIn, pts: [0.55, 0, 1, 0.45], hot: false}, {key: 'easeInOut', zh: '慢快慢', css: EASE.easeInOut, pts: [0.65, 0, 0.35, 1], hot: false}, ]; function Curve({pts, hot}: {pts: number[]; hot: boolean}) { const [a, b, c, d] = pts; return ( ); } export default function EasingDemo() { const [run, setRun] = useState(0); const [one, setOne] = useState>({}); const duration = 900; return ( { setRun((n) => n + 1); setOne({}); }} className="flex h-10 items-center gap-2 rounded-full bg-white px-5 text-sm font-bold text-black transition hover:bg-zinc-200 active:scale-[.97] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600" > 播放 {ROWS.map((r) => ( setOne((o) => ({...o, [r.key]: (o[r.key] ?? 0) + 1}))} className={`flex items-center gap-4 rounded-2xl border p-3 transition-colors ${r.hot ? 'border-sky-400/40 bg-sky-500/10' : 'border-white/10 bg-white/[.03]'}`} > {r.zh} {r.css} {/* 轨道宽 = 盒子宽 − 卡片宽,轨道平移 100% 正好把卡片送到最右边 */} 订单已发货 ))} ); } ```
{r.css}