动画与Keyframes #
基本概念 #
Emotion 提供了 keyframes 函数来定义 CSS 关键帧动画,让你可以在 JavaScript 中创建和管理动画效果。
keyframes 基本用法 #
定义动画 #
jsx
import { keyframes } from '@emotion/react'
import styled from '@emotion/styled'
const fadeIn = keyframes`
from {
opacity: 0;
}
to {
opacity: 1;
}
`
const FadeInBox = styled.div`
animation: ${fadeIn} 1s ease-in-out;
padding: 20px;
background: #007bff;
color: white;
`
多步骤动画 #
jsx
import { keyframes } from '@emotion/react'
import styled from '@emotion/styled'
const bounce = keyframes`
0%, 20%, 50%, 80%, 100% {
transform: translateY(0);
}
40% {
transform: translateY(-30px);
}
60% {
transform: translateY(-15px);
}
`
const BouncingBall = styled.div`
width: 50px;
height: 50px;
background: #007bff;
border-radius: 50%;
animation: ${bounce} 1s infinite;
`
复杂动画 #
jsx
import { keyframes } from '@emotion/react'
import styled from '@emotion/styled'
const pulse = keyframes`
0% {
transform: scale(1);
box-shadow: 0 0 0 0 rgba(0, 123, 255, 0.7);
}
70% {
transform: scale(1.1);
box-shadow: 0 0 0 10px rgba(0, 123, 255, 0);
}
100% {
transform: scale(1);
box-shadow: 0 0 0 0 rgba(0, 123, 255, 0);
}
`
const PulseButton = styled.button`
padding: 12px 24px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
animation: ${pulse} 2s infinite;
`
动画属性 #
完整动画属性 #
jsx
import { keyframes } from '@emotion/react'
import styled from '@emotion/styled'
const slideIn = keyframes`
from {
transform: translateX(-100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
`
const SlideInPanel = styled.div`
padding: 20px;
background: white;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
animation-name: ${slideIn};
animation-duration: 0.5s;
animation-timing-function: ease-out;
animation-delay: 0s;
animation-iteration-count: 1;
animation-direction: normal;
animation-fill-mode: forwards;
animation-play-state: running;
`
简写形式 #
jsx
const AnimatedBox = styled.div`
animation: ${slideIn} 0.5s ease-out forwards;
`
动态动画 #
基于 Props 的动画 #
jsx
import { keyframes } from '@emotion/react'
import styled from '@emotion/styled'
const spin = keyframes`
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
`
const Spinner = styled.div`
width: ${props => props.size || 40}px;
height: ${props => props.size || 40}px;
border: ${props => props.thickness || 4}px solid #f3f3f3;
border-top: ${props => props.thickness || 4}px solid #007bff;
border-radius: 50%;
animation: ${spin} ${props => props.speed || 1}s linear infinite;
`
function App() {
return (
<>
<Spinner />
<Spinner size={60} thickness={6} speed={0.5} />
<Spinner size={20} thickness={2} speed={2} />
</>
)
}
条件动画 #
jsx
import { keyframes } from '@emotion/react'
import styled from '@emotion/styled'
const shake = keyframes`
0%, 100% { transform: translateX(0); }
10%, 30%, 50%, 70%, 90% { transform: translateX(-5px); }
20%, 40%, 60%, 80% { transform: translateX(5px); }
`
const Input = styled.input`
padding: 12px;
border: 2px solid ${props => props.error ? '#dc3545' : '#ddd'};
border-radius: 4px;
animation: ${props => props.error ? `${shake} 0.5s ease-in-out` : 'none'};
`
function Form() {
const [value, setValue] = useState('')
const error = value.length > 0 && value.length < 3
return (
<Input
value={value}
onChange={e => setValue(e.target.value)}
error={error}
/>
)
}
组合动画 #
多个动画 #
jsx
import { keyframes } from '@emotion/react'
import styled from '@emotion/styled'
const fadeIn = keyframes`
from { opacity: 0; }
to { opacity: 1; }
`
const slideUp = keyframes`
from { transform: translateY(20px); }
to { transform: translateY(0); }
`
const AnimatedCard = styled.div`
padding: 20px;
background: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
animation:
${fadeIn} 0.3s ease-out,
${slideUp} 0.3s ease-out;
`
动画序列 #
jsx
import { keyframes } from '@emotion/react'
import styled from '@emotion/styled'
const expand = keyframes`
from { width: 0; }
to { width: 100%; }
`
const fadeIn = keyframes`
from { opacity: 0; }
to { opacity: 1; }
`
const ProgressBar = styled.div`
height: 4px;
background: #007bff;
border-radius: 2px;
animation:
${expand} 2s ease-out forwards,
${fadeIn} 0.3s ease-out;
& span {
animation: ${fadeIn} 0.3s ease-out 2s forwards;
opacity: 0;
}
`
过渡效果 #
基本过渡 #
jsx
import styled from '@emotion/styled'
const Button = styled.button`
padding: 12px 24px;
background: #007bff;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s ease;
&:hover {
background: #0056b3;
transform: translateY(-2px);
}
&:active {
transform: translateY(0);
}
`
多属性过渡 #
jsx
import styled from '@emotion/styled'
const Card = styled.div`
padding: 20px;
background: white;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transition:
transform 0.3s ease,
box-shadow 0.3s ease,
background-color 0.3s ease;
&:hover {
transform: translateY(-4px);
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.15);
}
`
动态过渡 #
jsx
import styled from '@emotion/styled'
const AnimatedBox = styled.div`
width: 100px;
height: 100px;
background: #007bff;
border-radius: ${props => props.rounded ? '50%' : '8px'};
transition:
border-radius ${props => props.duration || '0.3s'} ease,
background-color ${props => props.duration || '0.3s'} ease;
&:hover {
background: #0056b3;
}
`
常用动画示例 #
加载动画 #
jsx
import { keyframes } from '@emotion/react'
import styled from '@emotion/styled'
const dotBounce = keyframes`
0%, 80%, 100% {
transform: scale(0);
}
40% {
transform: scale(1);
}
`
const LoadingDots = styled.div`
display: flex;
gap: 8px;
& span {
width: 12px;
height: 12px;
background: #007bff;
border-radius: 50%;
animation: ${dotBounce} 1.4s ease-in-out infinite both;
&:nth-child(1) { animation-delay: -0.32s; }
&:nth-child(2) { animation-delay: -0.16s; }
&:nth-child(3) { animation-delay: 0s; }
}
`
function Loading() {
return (
<LoadingDots>
<span></span>
<span></span>
<span></span>
</LoadingDots>
)
}
淡入淡出 #
jsx
import { keyframes } from '@emotion/react'
import styled from '@emotion/styled'
const fadeIn = keyframes`
from { opacity: 0; }
to { opacity: 1; }
`
const fadeOut = keyframes`
from { opacity: 1; }
to { opacity: 0; }
`
const FadeContainer = styled.div`
animation: ${props => props.visible ? fadeIn : fadeOut} 0.3s ease forwards;
`
function FadeWrapper({ children, visible }) {
if (!visible) return null
return (
<FadeContainer visible={visible}>
{children}
</FadeContainer>
)
}
滑动效果 #
jsx
import { keyframes } from '@emotion/react'
import styled from '@emotion/styled'
const slideInLeft = keyframes`
from {
transform: translateX(-100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
`
const slideInRight = keyframes`
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
`
const slideInUp = keyframes`
from {
transform: translateY(100%);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
`
const slideInDown = keyframes`
from {
transform: translateY(-100%);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
`
const SlidePanel = styled.div`
padding: 20px;
background: white;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
animation: ${props => {
switch (props.direction) {
case 'left': return slideInLeft
case 'right': return slideInRight
case 'up': return slideInUp
case 'down': return slideInDown
default: return slideInLeft
}
}} 0.3s ease forwards;
`
缩放效果 #
jsx
import { keyframes } from '@emotion/react'
import styled from '@emotion/styled'
const scaleIn = keyframes`
from {
transform: scale(0);
opacity: 0;
}
to {
transform: scale(1);
opacity: 1;
}
`
const scaleOut = keyframes`
from {
transform: scale(1);
opacity: 1;
}
to {
transform: scale(0);
opacity: 0;
}
`
const Modal = styled.div`
padding: 24px;
background: white;
border-radius: 8px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.2);
animation: ${props => props.open ? scaleIn : scaleOut} 0.3s ease forwards;
`
动画库集成 #
使用动画变量 #
jsx
import { keyframes } from '@emotion/react'
import styled from '@emotion/styled'
const animations = {
fadeIn: keyframes`
from { opacity: 0; }
to { opacity: 1; }
`,
slideUp: keyframes`
from { transform: translateY(20px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
`,
pulse: keyframes`
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
`,
spin: keyframes`
from { transform: rotate(0deg); }
to { transform: rotate(360deg); }
`,
}
const AnimatedComponent = styled.div`
animation: ${props => animations[props.animation] || animations.fadeIn} 0.3s ease;
`
性能优化 #
使用 transform 和 opacity #
优先使用 transform 和 opacity 实现动画,它们不会触发重排:
jsx
const GoodAnimation = styled.div`
transition: transform 0.3s ease, opacity 0.3s ease;
&:hover {
transform: translateY(-4px);
opacity: 0.9;
}
`
will-change #
对于复杂动画,使用 will-change 提示浏览器:
jsx
const ComplexAnimation = styled.div`
will-change: transform, opacity;
animation: ${complexKeyframes} 1s ease;
`
下一步 #
掌握了动画与 Keyframes 后,继续学习 SSR服务端渲染,了解如何在服务端渲染中使用 Emotion。
最后更新:2026-03-28