动画
为 HeroUI v3 组件添加流畅的动画和过渡
HeroUI 组件支持多种动画方法:内置 CSS 过渡、自定义 CSS 动画以及 Framer Motion 等 JavaScript 库。
内置动画
HeroUI 组件使用数据属性来公开其动画状态:
/* Popover entrance/exit */.popover[data-entering] { @apply animate-in zoom-in-90 fade-in-0 duration-200;}
.popover[data-exiting] { @apply animate-out zoom-out-95 fade-out duration-150;}
/* Button press effect */.button:active,.button[data-pressed="true"] { transform: scale(0.97);}
/* Accordion expansion */.accordion__panel[aria-hidden="false"] { @apply h-[var(--panel-height)] opacity-100;}状态样式属性:
[data-hovered="true"]- 悬停状态[data-pressed="true"]- 活动/按下状态[data-focus-visible="true"]- 键盘焦点[data-disabled="true"]- 禁用状态[data-entering]/[data-exiting]- 过渡状态[aria-expanded="true"]- 展开状态
CSS 动画
使用 Tailwind 实用程序:
// Pulse on hover<Button className="hover:animate-pulse"> Hover me</Button>
// Fade in entrance<Alert className="animate-fade-in"> Welcome message</Alert>
// Staggered list<div className="space-y-2"> <Card className="animate-fade-in animate-delay-100">Item 1</Card> <Card className="animate-fade-in animate-delay-200">Item 2</Card></div>自定义过渡:
/* Slower accordion */.accordion__panel { @apply transition-all duration-500;}
/* Bouncy button */.button:active { animation: bounce 0.3s;}
@keyframes bounce { 50% { transform: scale(0.95); }}Framer Motion
HeroUI 组件与 Framer Motion 无缝协作,实现高级动画。
基本用法:
import { motion } from 'framer-motion';import { Button } from '@lenso/ui';
const MotionButton = motion(Button);
<MotionButton whileHover={{ scale: 1.05 }} whileTap={{ scale: 0.95 }}> Animated Button</MotionButton>入口动画:
<motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.5 }}> <Alert> <Alert.Title>Welcome!</Alert.Title> </Alert></motion.div>布局动画:
import { AnimatePresence, motion } from 'framer-motion';
function Tabs({ items, selected }) { return ( <div className="flex gap-2"> {items.map((item, i) => ( <Button key={i} onPress={() => setSelected(i)}> {item} {selected === i && ( <motion.div layoutId="active" className="absolute inset-0 bg-accent" transition={{ type: "spring", bounce: 0.2 }} /> )} </Button> ))} </div> );}渲染属性
根据组件状态应用动态动画:
<Button> {({ isPressed, isHovered }) => ( <motion.span animate={{ scale: isPressed ? 0.95 : isHovered ? 1.05 : 1 }} > Interactive Button </motion.span> )}</Button>无障碍
尊重动态偏好: HeroUI 使用 Tailwind 的 motion-reduce 工具自动尊重用户动态效果偏好。当用户在系统设置中启用“减少动态效果”时,所有内置的过渡和动画效果都将被禁用。
HeroUI 扩展了 Tailwind 的 motion-reduce: 变体,以同时支持原生 prefers-reduced-motion 媒体查询和 data-reduce-motion 属性。
/* HeroUI pattern - uses Tailwind's motion-reduce: */.button { @apply transition-colors motion-reduce:transition-none;}
/* Expands to support both approaches: */@media (prefers-reduced-motion: reduce) { .button { transition: none; }}
[data-reduce-motion="true"] .button { transition: none;}使用Framer Motion:
import { useReducedMotion } from 'framer-motion';
function AnimatedCard() { const shouldReduceMotion = useReducedMotion();
return ( <motion.div initial={{ opacity: 0 }} animate={{ opacity: 1 }} transition={{ duration: shouldReduceMotion ? 0 : 0.5 }} > <Card>Content</Card> </motion.div> );}全局禁用动画: 添加data-reduce-motion="true"到<html>或者<body>标签:
<html data-reduce-motion="true"> <!-- All HeroUI animations will be disabled --></html>HeroUI自动检测用户的prefers-reduced-motion: reduce相应地设置并禁用动画。
性能技巧
使用 GPU 加速属性: 首选transform和opacity对于流畅的动画:
/* Good - GPU accelerated */.slide-in { transform: translateX(-100%); transition: transform 0.3s;}
/* Avoid - Triggers layout */.slide-in { left: -100%; transition: left 0.3s;}will-change优化: 使用will-change优化动画,但在不设置动画时将其删除:
.button { will-change: transform;}
.button:not(:hover) { will-change: auto;}