当前位置: 首页 > news >正文

前端动画的多种实现方式

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

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

http://www.cnnetsun.cn/news/50185.html

相关文章:

  • CANN Samples(二十):常见问题与版本迁移
  • JavaEE进阶——Spring事务与传播机制实战指南
  • 3、贝尔实验室与Unix操作系统的起源
  • 基于SpringBoot的网上订餐系统(11485)
  • hive让分区关联数据的三种方式
  • 突破性音频AI技术:Step-Audio 2系列重塑智能语音交互新范式
  • 基于vue的家政服务管理系统_37cw9ju0_springboot php python nodejs
  • 《零基础学 PHP:从入门到实战》·PHP编程精进之路:掌握高级特性与实战技巧-1
  • Step-Audio 2:重新定义人机语音交互的技术革命
  • AutoGPT与Stable Diffusion联用:图文内容协同生成新玩法
  • NetSonar:3分钟快速掌握的网络诊断终极方案
  • 46、PHP 基础函数与操作全解析
  • 52、Linux系统性能优化与命令行操作指南
  • 53、Linux 命令行与软件管理全攻略
  • 61、Ubuntu和Linux互联网资源指南
  • OpenPLC Editor开源工具在工业自动化领域的应用实践
  • ACL实验:ACL控制Telnet与Ping权限
  • 7、Linux 进程管理与操作详解
  • 学Simulink——移动机器人导航场景实例:基于Simulink的BLDC阿克曼转向Stanley算法路径跟踪仿真
  • Linux内存管理优化实战:系统性能提升完整指南
  • 如何在5分钟内用HandyControl搭建WPF视频播放器界面
  • 个人作品集网站终极指南:零基础打造专业简历展示平台
  • 小参数GPT训练数据预处理实战:从混乱数据到高质量语料
  • 终极无审查AI助手:Dolphin-Mistral-24B-Venice-Edition完全使用指南
  • 【C++入门必备】最详细入门教程(3)
  • iOS功能开关完整指南:从入门到精通的终极实践
  • Step-Audio 2 mini:开源语音大模型如何让中小企业AI部署成本锐减80%?
  • Flutter桌面交互优化:3个提升用户体验的关键技巧
  • 快速免费完整迁移:从动态博客到极速静态站点的终极指南
  • 58、Ubuntu系统工具、测试与Perl编程全解析