【新年特辑】使用 React + TypeScript 开发新年祝福网页

🎉 新年将至,我决定开发一个独特的新年祝福网页,让每个人都能创建和分享自己的新年祝福。本文将详细介绍这个项目的开发过程,从技术选型到具体实现,希望能给大家一些启发。

在这里插入图片描述

在这里插入图片描述

一、项目概述

1.1 项目背景

在这个数字化的时代,传统的新年祝福方式似乎显得有些单调。作为开发者,我希望能够创造一个既保留传统节日氛围,又充满现代感的新年祝福平台。这个项目不仅是一次技术实践,更是对传统文化的一种新诠释。

1.2 技术选型

在开始开发之前,我仔细考虑了技术栈的选择:

  • React 18:选择 React 的原因是其强大的组件化能力和丰富的生态系统
  • TypeScript:使用 TypeScript 可以提供更好的类型安全和开发体验
  • Tailwind CSS:这个原子化 CSS 框架能够快速构建美观的界面
  • Framer Motion:用于实现流畅的动画效果
  • Vite:作为构建工具,提供极快的开发体验

1.3 功能规划

核心功能包括:

  1. 个性化祝福创建
  2. 动态烟花效果
  3. 音乐播放器
  4. 祝福图片生成与分享

二、项目初始化

2.1 环境搭建

首先创建一个新的 Vite 项目:

npm create vite@latest new-year-greetings -- --template react-ts
cd new-year-greetings
npm install

安装必要的依赖:

npm install tailwindcss postcss autoprefixer framer-motion
npm install -D @types/node

2.2 配置 Tailwind CSS

创建 Tailwind 配置文件:

// tailwind.config.js
module.exports = {content: ["./index.html","./src/**/*.{js,ts,jsx,tsx}",],theme: {extend: {animation: {'float': 'float 3s ease-in-out infinite',},keyframes: {float: {'0%, 100%': { transform: 'translateY(0)' },'50%': { transform: 'translateY(-10px)' },}}},},plugins: [],
}

三、界面开发

3.1 主页面布局

首先设计主页面的基本结构:

// App.tsx
function App() {return (<div className="min-h-screen bg-gradient-to-b from-red-800 to-yellow-600 text-white relative overflow-hidden"><div className="container mx-auto px-4 py-8 relative z-10">{/* 内容区域 */}</div></div>);
}

3.2 灯笼组件实现

灯笼是传统节日不可或缺的元素:

const Lantern = ({ index }: { index: number }) => {return (<motion.divinitial={{ y: -20, opacity: 0 }}animate={{ y: 0, opacity: 1 }}transition={{delay: index * 0.2,duration: 0.8,y: {duration: 2,repeat: Infinity,repeatType: "reverse",ease: "easeInOut"}}}className="absolute"style={{left: `${index * 25}%`,top: '20px'}}><div className="w-16 h-20 bg-red-600 rounded-full relative">{/* 灯笼装饰 */}<div className="absolute top-0 left-1/2 -translate-x-1/2 w-8 h-3 bg-yellow-500 rounded-full"></div><div className="absolute bottom-0 left-1/2 -translate-x-1/2 w-1 h-8 bg-red-800"></div></div></motion.div>);
};

3.3 烟花动画效果

使用 Framer Motion 实现绚丽的烟花效果:

const Firework = ({ x, y }: { x: number; y: number }) => {const colors = ['#FFD700', '#FF6B6B', '#4ECDC4', '#45B7D1'];const particles = Array.from({ length: 12 }).map((_, i) => ({angle: (i * Math.PI * 2) / 12,color: colors[Math.floor(Math.random() * colors.length)]}));return (<motion.divinitial={{ opacity: 1 }}animate={{ opacity: 0 }}transition={{ duration: 0.8 }}className="absolute"style={{ left: x, top: y }}>{particles.map((particle, i) => (<motion.divkey={i}initial={{ x: 0, y: 0, scale: 1 }}animate={{x: Math.cos(particle.angle) * 50,y: Math.sin(particle.angle) * 50,scale: 0}}transition={{ duration: 0.8, ease: "easeOut" }}style={{position: 'absolute',width: '4px',height: '4px',borderRadius: '50%',backgroundColor: particle.color}}/>))}</motion.div>);
};

3.4 祝福输入表单

创建一个优雅的输入界面:

const GreetingForm = () => {const [name, setName] = useState('');const [customGreeting, setCustomGreeting] = useState('');return (<motion.divinitial={{ opacity: 0, y: 20 }}animate={{ opacity: 1, y: 0 }}className="space-y-4"><inputtype="text"value={name}onChange={(e) => setName(e.target.value)}placeholder="请输入您的名字"className="w-full px-4 py-2 rounded-lg bg-white/10 border border-white/20"/><textareavalue={customGreeting}onChange={(e) => setCustomGreeting(e.target.value)}placeholder="添加自定义祝福语(选填)"className="w-full px-4 py-2 rounded-lg bg-white/10 border border-white/20"/></motion.div>);
};

四、动画优化

4.1 性能考虑

为了确保动画流畅,我们需要注意以下几点:

  1. 使用 transform 而不是改变位置属性
  2. 适当使用 will-change 属性
  3. 控制同时展示的动画元素数量

4.2 动画时机控制

useEffect(() => {const timer = setInterval(() => {if (fireworks.length < 5) {  // 限制同时存在的烟花数量const newFirework = {id: Date.now(),x: Math.random() * window.innerWidth,y: Math.random() * (window.innerHeight / 2)};setFireworks(prev => [...prev, newFirework]);}}, 1000);return () => clearInterval(timer);
}, [fireworks]);

五、响应式设计

5.1 移动端适配

使用 Tailwind CSS 的响应式类:

<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">{/* 内容 */}
</div>

5.2 触摸事件处理

const handleTouchStart = (e: React.TouchEvent) => {const touch = e.touches[0];addFirework(touch.clientX, touch.clientY);
};

六、音乐播放器开发

6.1 播放器设计思路

在设计音乐播放器时,我考虑了以下几个关键点:

  1. 用户体验:播放器应该易于使用且不影响主界面
  2. 功能完整:支持基本的播放控制和歌曲选择
  3. 视觉效果:与整体界面风格保持一致
  4. 响应式设计:在各种设备上都能良好展示

6.2 播放器组件实现

首先定义歌曲接口和歌曲列表:

interface Song {id: number;title: string;url: string;
}const songs: Song[] = [{ id: 1, title: '夏·烟火', url: '/songs/夏·烟火.mp3' },{ id: 2, title: '世间满樱花', url: '/songs/世间满樱花.mp3' },{ id: 3, title: '花火の绚', url: '/songs/花火の绚' },
];

播放器主体组件:

export const MusicPlayer: React.FC = () => {const [currentSong, setCurrentSong] = useState<Song | null>(null);const [isPlaying, setIsPlaying] = useState(false);const [progress, setProgress] = useState(0);const [isExpanded, setIsExpanded] = useState(false);const audioRef = useRef<HTMLAudioElement>(null);// 播放控制逻辑const handleSongSelect = (song: Song) => {if (currentSong?.id === song.id) {setIsPlaying(!isPlaying);} else {setCurrentSong(song);setIsPlaying(true);}};// 进度条控制const handleProgressClick = (e: React.MouseEvent<HTMLDivElement>) => {if (audioRef.current) {const bounds = e.currentTarget.getBoundingClientRect();const percent = (e.clientX - bounds.left) / bounds.width;audioRef.current.currentTime = percent * audioRef.current.duration;}};// 时间格式化const formatTime = (time: number) => {const minutes = Math.floor(time / 60);const seconds = Math.floor(time % 60);return `${minutes}:${seconds.toString().padStart(2, '0')}`;};return (<div className="fixed right-4 top-1/2 -translate-y-1/2 z-50">{/* 播放器界面实现 */}</div>);
};

6.3 播放器动画效果

使用 Framer Motion 实现展开/收起动画:

<motion.divinitial={{ x: 300 }}animate={{ x: 0 }}className="relative"
><motion.buttonwhileHover={{ scale: 1.1 }}whileTap={{ scale: 0.9 }}onClick={() => setIsExpanded(!isExpanded)}className="absolute -left-12 top-1/2 -translate-y-1/2 w-10 h-10 bg-red-800/90">{isExpanded ? '🎵' : '🎼'}</motion.button><motion.divanimate={{ width: isExpanded ? 'auto' : '0px',opacity: isExpanded ? 1 : 0 }}className="w-64 bg-red-800/90 backdrop-blur-md rounded-l-xl">{/* 播放器内容 */}</motion.div>
</motion.div>

七、祝福图片生成

7.1 Canvas 绘制实现

使用 Canvas API 生成分享图片:

interface ShareImageProps {name: string;greeting: string;background: string;
}const generateShareImage = async (props: ShareImageProps): Promise<string> => {const { name, greeting, background } = props;const canvas = document.createElement('canvas');const ctx = canvas.getContext('2d');if (!ctx) return '';// 设置画布尺寸canvas.width = 1080;canvas.height = 1920;// 加载背景图片const bgImage = new Image();bgImage.src = background;await new Promise(resolve => bgImage.onload = resolve);// 绘制背景ctx.drawImage(bgImage, 0, 0, canvas.width, canvas.height);// 添加渐变遮罩const gradient = ctx.createLinearGradient(0, 0, 0, canvas.height);gradient.addColorStop(0, 'rgba(0,0,0,0.6)');gradient.addColorStop(0.5, 'rgba(0,0,0,0.3)');gradient.addColorStop(1, 'rgba(0,0,0,0.6)');ctx.fillStyle = gradient;ctx.fillRect(0, 0, canvas.width, canvas.height);// 绘制文字ctx.textAlign = 'center';ctx.fillStyle = '#fff';// 绘制祝福语ctx.font = '48px Arial';ctx.fillText(greeting, canvas.width / 2, canvas.height / 2);// 绘制署名ctx.font = '36px Arial';ctx.fillText(`致:${name}`, canvas.width / 2, canvas.height / 2 + 100);return canvas.toDataURL('image/png');
};

7.2 图片保存功能

const handleSaveImage = async () => {try {setIsSaving(true);const imageUrl = await generateShareImage({name,greeting: selectedGreeting || customGreeting,background: '/images/bg.jpg'});// 创建下载链接const link = document.createElement('a');link.download = `新年祝福_${Date.now()}.png`;link.href = imageUrl;link.click();} catch (error) {console.error('保存图片失败:', error);} finally {setIsSaving(false);}
};

八、性能优化

8.1 资源加载优化

  1. 图片懒加载
const LazyImage = ({ src, alt }: { src: string; alt: string }) => {const [isLoaded, setIsLoaded] = useState(false);return (<motion.imgsrc={src}alt={alt}initial={{ opacity: 0 }}animate={{ opacity: isLoaded ? 1 : 0 }}onLoad={() => setIsLoaded(true)}/>);
};
  1. 音频预加载
const preloadAudio = (url: string) => {const audio = new Audio();audio.src = url;audio.preload = 'auto';
};useEffect(() => {songs.forEach(song => preloadAudio(song.url));
}, []);

8.2 渲染优化

  1. 使用 React.memo
const SongItem = React.memo(({ song, isPlaying, onSelect }: SongItemProps) => {return (<motion.divwhileHover={{ scale: 1.02 }}onClick={() => onSelect(song)}className="song-item">{/* 歌曲项内容 */}</motion.div>);
});
  1. 优化状态更新
const handleTimeUpdate = useCallback(() => {if (audioRef.current) {const progress = (audioRef.current.currentTime / audioRef.current.duration) * 100;setProgress(progress);}
}, []);

结语

通过这个项目,我们不仅创造了一个有趣的新年祝福应用,还实践了许多现代前端开发技术。希望这个项目能给大家带来一些启发,也祝愿大家新年快乐!

🎉 如果你觉得这个项目有帮助,欢迎点赞转发!如果你有任何问题或建议,也欢迎在评论区留言交流!


相关链接:

  • 项目源码

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/65812.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

jmeter分布式启动

https://www.cnblogs.com/qtclm/p/11082081.html 1、代理机&#xff1a;输入“ipconfig”&#xff0c;找到IP地址&#xff0c;在Jmeter/bin/jmeter.properties设置remote host 启动jmeter server 1、控制机&#xff1a;输入“ipconfig”&#xff0c;找到IP地址&#xff0c;在J…

sqoop将MySQL数据导入hive

使用脚本加载数据 MySQL有一张表 hive创建一张相同的表 编写脚本同步数据 [rootmaster sqoop]# vim stu.sh#!/bin/bash SQOOP/usr/local/soft/sqoop-1.4.6/bin/sqoop $SQOOP import --connect jdbc:mysql://192.168.67.100:3306/sqoop \--username root \--password 123456 \-…

【从零开始入门unity游戏开发之——C#篇39】C#反射使用——Type 类、Assembly 类、Activator 类操作程序集

文章目录 前言一、前置知识1、编译器2、程序集&#xff08;Assembly&#xff09;3、元数据&#xff08;Metadata&#xff09; 二、反射1、反射的概念2、反射的作用3、反射的核心Type 类3.1 Type 类介绍3.2 不同方法获取 Type3.3 获取type类型所在的程序集的相关信息 4、反射的常…

【MyBatis源码分析】Spring与MyBatis整合深入解析

&#x1f3ae; 作者主页&#xff1a;点击 &#x1f381; 完整专栏和代码&#xff1a;点击 &#x1f3e1; 博客主页&#xff1a;点击 文章目录 概述SqlSessionFactoryBean详解配置原理 MapperScannerConfigurer 源码分析介绍postProcessBeanDefinitionRegistry 方法 概述 MyBat…

微信小程序几种数据通信方式记录

在微信小程序开发中&#xff0c;组件间的数据传递是一个常见的需求。以下是不同组件间数据传递的方式&#xff0c;根据传递的方向&#xff08;父子、兄弟、跨层级等&#xff09;提供了多种方法。 1. 父组件向子组件传递数据 通过 properties&#xff08;组件属性&#xff09;&…

使用JMeter对Linux生产服务器进行压力测试

安装 JMeter wget https://downloads.apache.org/jmeter/binaries/apache-jmeter-5.4.1.tgz tar -xzf apache-jmeter-5.4.1.tgz cd apache-jmeter-5.4.1创建 JMeter 脚本 设置中文 选择Options—>Choose Language—>选择其他语言&#xff08;例如&#xff1a;Chinese&am…

STM32-笔记20-测量按键按下时间

1、按键按下的时间-思路 我们先检测下降沿信号&#xff0c;检测到以后&#xff0c;在回调函数里切换成检测上升沿信号&#xff0c;当两个信号都检测到的时候&#xff0c;这段时间就是按键按下的时间&#xff0c;如图所示&#xff1a;>N*(ARR1)CCRx的值 N是在这段时间内&…

2024年12月CCF-GESP编程能力等级认证Scratch图形化编程四级真题解析

本文收录于《Scratch等级认证CCF-GESP图形化真题解析》专栏,专栏总目录:点这里,订阅后可阅读专栏内所有文章。 一、单选题(共 10 题,每题 2 分,共 30 分) 第 1 题 2024 年 10 月 8 日,诺贝尔物理学奖“意外地”颁给了两位计算机科学家约翰霍普菲尔德(John J. Hopfie…

常见协议的高危软件漏洞信息

HTTP 协议 协议 | 软件 | 漏洞编号 | 漏洞描述 Apache Log4j CVE-2021-45105 | Apache Log4j拒绝服务攻击漏洞 XWiki Platform CVE-2023-26477 | XWiki Platform存在安全漏洞&#xff0c;该漏洞源于可以通过URL请求参数结合其他参数注入任意脚本宏 Microsoft Windows CVE-20…

CPT203 Software Engineering 软件工程 Pt.2 敏捷方法和需求工程(中英双语)

文章目录 3. Aglie methods&#xff08;敏捷方法&#xff09;3.1 Aglie methods&#xff08;敏捷方法&#xff09;3.1.1 特点3.1.2 优点3.1.3 缺点3.1.4 原则3.1.5 计划驱动与敏捷方法的对比 3.2 Scrum3.2.1 Scrum roles3.2.2 Scrum Activities and Artifacts3.2.2.1 Product B…

攻防靶场(29):目录权限和文件权限 ICMP

目录 1. 侦查 1.1 收集目标网络信息&#xff1a;IP地址 1.2 主动扫描&#xff1a;扫描IP地址段 1.3 搜索目标网站 2. 初始访问 2.1 利用面向公众的应用 3. 权限提升 3.1 有效账户&#xff1a;本地账户 3.2 滥用特权控制机制&#xff1a;Sudo和Sudo缓存 靶场下载地址&#xff1a…

libmodbus源码中重要的两个结构体讲解

文章目录 一、libmodbus重要数据结构讲解**1. 结构体 `_modbus`**定义成员解析小结**2. 结构体 `_modbus_backend`**定义成员解析小结**3. 两者关系和工作流程****关系****工作流程**一、libmodbus重要数据结构讲解 这两个结构体是 libmodbus 的核心,定义了 Modbus 通信上下文…

Spring自动化创建脚本-解放繁琐的初始化配置!!!(自动化SSM整合)

一、实现功能(原创&#xff0c;转载请告知) 1.自动配置pom配置文件 2.自动识别数据库及数据表&#xff0c;创建Entity、Dao、Service、Controller等 3.自动创建database.properties、mybatis-config.xml等数据库文件 4.自动创建spring-dao.xml spring-mvc.xml …

【详解】AndroidWebView的加载超时处理

Android WebView的加载超时处理 在Android开发中&#xff0c;WebView是一个常用的组件&#xff0c;用于在应用中嵌入网页。然而&#xff0c;当网络状况不佳或页面加载过慢时&#xff0c;用户可能会遇到加载超时的问题。为了提升用户体验&#xff0c;我们需要对WebView的加载超时…

java开发中注解汇总​​

注解作用位置注意mybatis Data Getter Setter ToString EqualsAndHashCode AllArgsConstructor NoArgsConstructor Data 代替&#xff1a;无参构造&#xff0c;get&#xff0c;set&#xff0c;toString&#xff0c;hashCode&#xff0c;equals Getter Setter 可放在类和方法上&…

【二】arcgis JavaScript api 实现加载不同坐标系的底图和三维服务

提示&#xff1a;如果是天地图底图参考这篇文章 【一】arcgis JavaScript api 实现加载不同坐标系的底图和三维服务_arcgis js api 调用三维地图服务-CSDN博客 需求&#xff1a; 前端开发实现底图&#xff08;wkid&#xff1a;3857&#xff0c;web墨卡托&#xff09;&#x…

c#接口和抽象方法

目录 抽象方法 1&#xff0c;抽象方法的定义, 2&#xff0c;抽象方法的特性 3&#xff0c;实例 接口 1&#xff0c;接口的定义 2&#xff0c;实现接口 3&#xff0c;接口实例 4&#xff0c;接口的特点 5&#xff0c;多接口实现 接口和抽象类的比较 抽象方法 抽象方法…

HTML——46.制作课程表

<!DOCTYPE html> <html><head><meta charset"UTF-8"><title>课程表</title></head><body><h3>课程表</h3><table border"1" cellspacing"0"><tr><th colspan"…

ARM64 Windows 10 IoT工控主板运行x86程序效率测试

ARM上的 Windows 10 IoT 企业版支持仿真 x86 应用程序&#xff0c;而 ARM上的 Windows 11 IoT 企业版则支持仿真 x86 和 x64 应用程序。英创推出的名片尺寸ARM64工控主板ESM8400&#xff0c;可预装正版Windows 10 IoT企业版操作系统&#xff0c;x86程序可无需修改而直接在ESM84…

【信息系统项目管理师】第14章:项目沟通管理过程详解

更多内容请见: 备考信息系统项目管理师-专栏介绍和目录 文章目录 一、规划沟通管理1、输入2、工具与技术3、输出二、管理沟通1、输入2、工具与技术3、输出三、监督沟通1、输入2、工具与技术3、输出一、规划沟通管理 定义:规划沟通管理是基于每个干系人或干系人群体的信息需求…