news 2026/4/16 21:45:02

前端动画的多种实现方式

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
前端动画的多种实现方式

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

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/16 11:56:37

微信支付集成_JSAPI

微信支付集成_JSAPI 0.背景 产品接入微信支付,需要实现PC端扫码支付,移动端公众号支付,以及小程序支付.经过调研统一采用微信的JSAPI实现.主要过程分两个大步骤: 下单接口(/v3/pay/transactions/jsapi),获取预付单号切换到微信环境(公众号,小程序)并结合预付单号,唤起支付界…

作者头像 李华
网站建设 2026/4/16 13:33:02

32、深入理解 Bash 脚本中的输入读取、循环控制与数据处理

深入理解 Bash 脚本中的输入读取、循环控制与数据处理 1. 读取键盘输入 在 Bash 脚本中, read 命令是读取用户输入的常用工具。运行以下脚本示例: [me@linuxbox ~]$ read-single Enter one or more values > a b c d REPLY = a b c dread 支持多种选项,以下是一…

作者头像 李华
网站建设 2026/4/16 12:00:54

37、Bash编程高级技巧:数组、特殊命令与异步操作全解析

Bash编程高级技巧:数组、特殊命令与异步操作全解析 1. 数组操作 在Bash中,数组是一种强大的数据结构,它允许我们存储和操作多个值。下面将详细介绍数组的一些常见操作。 1.1 查找数组使用的下标 Bash允许数组在赋值时存在“间隙”,因此有时需要确定哪些元素实际存在。可…

作者头像 李华
网站建设 2026/4/16 13:33:06

18、Perl 循环结构与控制详解

Perl 循环结构与控制详解 1. 需求与目标程序 编写一个程序,它接收命令行中指定的一系列数字,然后完成以下两个任务: 1. 将这些数字按升序排序。 2. 遍历排序后的数字,并打印每个数字及其累加总和。 2. 多种循环类型介绍 2.1 foreach 循环 当列表中元素的具体(数字)…

作者头像 李华
网站建设 2026/4/16 11:54:53

spark的Kryo 序列化介绍

好的,我们来详细介绍一下 Spark 中的 Kryo 序列化。 默认情况下,Spark 使用 Java 的序列化机制。Java 的序列化机制使用方便,不需要额外的配置,在算子中使用的变量实现 Serializable 接口即可,但是,Java 序列化机制的效率不高,序列化速度慢并且序列化后的数据所占用的空…

作者头像 李华