前端动画的多种实现方式

动画的本质是把内容的两个状态做平滑的过渡(中间状态的展示)

tsx 复制代码
import React, { useEffect, useRef, useState } from 'react';
import './styles.css';

// 动画实践

// 1.1 translation
const TranslationDemo = () => {
    return (
        <div style={{ padding: '20px', border: '1px solid #ccc', margin: '10px' }}>
            <h3>1.1 CSS Translation</h3>
            <div
                className="translation-box"
                style={{
                    width: '100px',
                    height: '100px',
                    backgroundColor: '#3498db',
                    transition: 'transform 1s ease-in-out',
                    cursor: 'pointer'
                }}
                onMouseEnter={(e) => {
                    e.currentTarget.style.transform = 'translateX(200px)';
                }}
                onMouseLeave={(e) => {
                    e.currentTarget.style.transform = 'translateX(0)';
                }}
            >
                Hover me
            </div>
        </div>
    );
};

// 1.2 animation + @keyframes
const KeyframesDemo = () => {
    return (
        <div style={{ padding: '20px', border: '1px solid #ccc', margin: '10px' }}>
            <h3>1.2 CSS Animation + @keyframes</h3>
            <div
                className="keyframes-box"
                style={{
                    width: '100px',
                    height: '100px',
                    backgroundColor: '#e74c3c',
                    animation: 'bounce 2s infinite'
                }}
            >
                Bouncing
            </div>
            <style>{`
        @keyframes bounce {
          0%, 100% { transform: translateY(0); }
          50% { transform: translateY(-50px); }
        }
      `}</style>
        </div>
    );
};

// 2.1 requestAnimationFrame
const RequestAnimationFrameDemo = () => {
    const boxRef = useRef<HTMLDivElement>(null);
    const [isAnimating, setIsAnimating] = useState(false);
    const animationRef = useRef<number>();

    const animate = () => {
        if (boxRef.current) {
            const currentX = parseFloat(boxRef.current.style.left || '0');
            if (currentX < 300) {
                boxRef.current.style.left = `${currentX + 2}px`;
                animationRef.current = requestAnimationFrame(animate);
            } else {
                boxRef.current.style.left = '0px';
                setIsAnimating(false);
            }
        }
    };

    const startAnimation = () => {
        if (!isAnimating) {
            setIsAnimating(true);
            animate();
        }
    };

    useEffect(() => {
        return () => {
            if (animationRef.current) {
                cancelAnimationFrame(animationRef.current);
            }
        };
    }, []);

    return (
        <div style={{ padding: '20px', border: '1px solid #ccc', margin: '10px' }}>
            <h3>2.1 requestAnimationFrame</h3>
            <div style={{ position: 'relative', height: '120px' }}>
                <div
                    ref={boxRef}
                    style={{
                        position: 'absolute',
                        left: '0px',
                        width: '100px',
                        height: '100px',
                        backgroundColor: '#2ecc71'
                    }}
                >
                    RAF
                </div>
            </div>
            <button onClick={startAnimation} disabled={isAnimating}>
                Start Animation
            </button>
        </div>
    );
};

// 3.1 Canvas + requestAnimationFrame
const CanvasDemo = () => {
    const canvasRef = useRef<HTMLCanvasElement>(null);
    const animationRef = useRef<number>();
    const xRef = useRef(0);
    const [isRunning, setIsRunning] = useState(false);

    const draw = () => {
        const canvas = canvasRef.current;
        if (!canvas) return;

        const ctx = canvas.getContext('2d');
        if (!ctx) return;

        // 清空画布
        ctx.clearRect(0, 0, canvas.width, canvas.height);

        // 绘制圆形
        ctx.fillStyle = '#9b59b6';
        ctx.beginPath();
        ctx.arc(xRef.current, 50, 20, 0, Math.PI * 2);
        ctx.fill();

        // 更新位置
        xRef.current += 2;
        if (xRef.current > canvas.width) {
            xRef.current = 0;
        }

        animationRef.current = requestAnimationFrame(draw);
    };

    const toggleAnimation = () => {
        if (isRunning) {
            if (animationRef.current) {
                cancelAnimationFrame(animationRef.current);
            }
            setIsRunning(false);
        } else {
            draw();
            setIsRunning(true);
        }
    };

    useEffect(() => {
        return () => {
            if (animationRef.current) {
                cancelAnimationFrame(animationRef.current);
            }
        };
    }, []);

    return (
        <div style={{ padding: '20px', border: '1px solid #ccc', margin: '10px' }}>
            <h3>3.1 Canvas + requestAnimationFrame</h3>
            <canvas
                ref={canvasRef}
                width={400}
                height={100}
                style={{ border: '1px solid #000' }}
            />
            <div>
                <button onClick={toggleAnimation}>
                    {isRunning ? 'Pause' : 'Start'}
                </button>
            </div>
        </div>
    );
};

// 4.1 SVG + CSS / SMIL / JS
const SVGDemo = () => {
    const [rotate, setRotate] = useState(0);

    useEffect(() => {
        const interval = setInterval(() => {
            setRotate(prev => (prev + 5) % 360);
        }, 50);

        return () => clearInterval(interval);
    }, []);

    return (
        <div style={{ padding: '20px', border: '1px solid #ccc', margin: '10px' }}>
            <h3>4.1 SVG Animation</h3>
            <svg width="200" height="200">
                {/* CSS Animation */}
                <circle cx="50" cy="50" r="20" fill="#e67e22">
                    <animate
                        attributeName="r"
                        values="20;30;20"
                        dur="2s"
                        repeatCount="indefinite"
                    />
                </circle>

                {/* JS Animation */}
                <rect
                    x="100"
                    y="30"
                    width="40"
                    height="40"
                    fill="#1abc9c"
                    transform={`rotate(${rotate} 120 50)`}
                />
            </svg>
        </div>
    );
};

// 5.1 GSAP
const GSAPDemo = () => {
    const gsapRef = useRef<HTMLDivElement>(null);

    const animateWithGSAP = () => {
        // 注意: 需要安装 gsap 库: npm install gsap
        // import gsap from 'gsap';
        // gsap.to(gsapRef.current, { x: 200, duration: 1, ease: 'power2.inOut' });

        // 简化版演示 (不依赖GSAP库)
        if (gsapRef.current) {
            gsapRef.current.style.transition = 'transform 1s ease-in-out';
            gsapRef.current.style.transform = 'translateX(200px)';
            setTimeout(() => {
                if (gsapRef.current) {
                    gsapRef.current.style.transform = 'translateX(0)';
                }
            }, 1000);
        }
    };

    return (
        <div style={{ padding: '20px', border: '1px solid #ccc', margin: '10px' }}>
            <h3>5.1 GSAP (简化演示)</h3>
            <div
                ref={gsapRef}
                style={{
                    width: '100px',
                    height: '100px',
                    backgroundColor: '#f39c12'
                }}
            >
                GSAP
            </div>
            <button onClick={animateWithGSAP}>Animate</button>
        </div>
    );
};

// 5.2 Framer Motion
const FramerMotionDemo = () => {
    const [isVisible, setIsVisible] = useState(true);

    return (
        <div style={{ padding: '20px', border: '1px solid #ccc', margin: '10px' }}>
            <h3>5.2 Framer Motion (简化演示)</h3>
            {/* 注意: 需要安装 framer-motion: npm install framer-motion */}
            {/* import { motion } from 'framer-motion'; */}
            <div
                style={{
                    width: '100px',
                    height: '100px',
                    backgroundColor: '#c0392b',
                    opacity: isVisible ? 1 : 0,
                    transform: isVisible ? 'scale(1)' : 'scale(0.5)',
                    transition: 'all 0.5s ease-in-out'
                }}
            >
                Framer
            </div>
            <button onClick={() => setIsVisible(!isVisible)}>
                Toggle
            </button>
        </div>
    );
};

// 6.1 Web Animations API
const WebAnimationsAPIDemo = () => {
    const wapiRef = useRef<HTMLDivElement>(null);

    const animateWithWAPI = () => {
        if (wapiRef.current) {
            wapiRef.current.animate([
                { transform: 'translateX(0) rotate(0deg)', backgroundColor: '#16a085' },
                { transform: 'translateX(200px) rotate(360deg)', backgroundColor: '#27ae60' }
            ], {
                duration: 1000,
                easing: 'ease-in-out',
                iterations: 1,
                fill: 'forwards'
            });

            setTimeout(() => {
                if (wapiRef.current) {
                    wapiRef.current.animate([
                        { transform: 'translateX(200px) rotate(360deg)', backgroundColor: '#27ae60' },
                        { transform: 'translateX(0) rotate(0deg)', backgroundColor: '#16a085' }
                    ], {
                        duration: 1000,
                        easing: 'ease-in-out',
                        fill: 'forwards'
                    });
                }
            }, 1000);
        }
    };

    return (
        <div style={{ padding: '20px', border: '1px solid #ccc', margin: '10px' }}>
            <h3>6.1 Web Animations API</h3>
            <div
                ref={wapiRef}
                style={{
                    width: '100px',
                    height: '100px',
                    backgroundColor: '#16a085'
                }}
            >
                WAAPI
            </div>
            <button onClick={animateWithWAPI}>Animate</button>
        </div>
    );
};

// 主组件
const AnimationPractice = () => {
    return (
        <div style={{ padding: '20px', fontFamily: 'Arial, sans-serif' }}>
            <h1>动画实践合集</h1>
            <TranslationDemo />
            <KeyframesDemo />
            <RequestAnimationFrameDemo />
            <CanvasDemo />
            <SVGDemo />
            <GSAPDemo />
            <FramerMotionDemo />
            <WebAnimationsAPIDemo />
        </div>
    );
};

export default AnimationPractice;

CSS

JS

Canvas(脱离DOM)

SVG(结构化+矢量)

动画库

浏览器Web Animations API

相关推荐
xhxxx4 小时前
别再被 CSS 定位搞晕了!5 种 position 一图打尽 + 实战代码全公开
前端·css·html
OpenTiny社区4 小时前
从千问灵光 App 看生成式 UI 技术的发展
前端·vue.js·ai编程
路修远i4 小时前
前端单元测试
前端·单元测试
明川4 小时前
Android Gradle学习 - Gradle插件开发与发布指南
android·前端·gradle
不一样的少年_4 小时前
【用户行为监控】别只做工具人了!手把手带你写一个前端埋点统计 SDK
前端·javascript·监控
Cache技术分享4 小时前
270. Java Stream API - 从“怎么做”转向“要什么结果”:声明式编程的优势
前端·后端
king王一帅4 小时前
AI 时代真正流式解析+渲染双重优化的 Incremark
前端·ai编程·markdown
aesthetician5 小时前
用铜钟听歌,发 SCI !
前端·人工智能·音频
Mike_jia5 小时前
LogWhisperer 全解析:打造你的Linux服务器AI日志分析中枢
前端