鸿蒙PC Electron框架实战:彩虹糖的梦技术实现详解
摘要: "彩虹糖的梦"是一款基于鸿蒙PC Electron框架开发的糖果收集游戏,采用HTML5+CSS3+JavaScript技术栈构建,具有梦幻风格设计和丰富交互体验。项目架构分为前端应用层、Electron通信层和鸿蒙原生层,核心功能包括: 糖果数据管理:使用数组存储糖果属性,通过Set对象记录收集状态 状态持久化:利用localStorage保存收集进度和用户愿望清单 动态渲染:根据收集状
欢迎加入开源鸿蒙PC社区:https://harmonypc.csdn.net/
atomgit仓库地址:https://atomgit.com/feng8403000/caihongtangdemeng


一、项目概述
1.1 项目背景
彩虹糖的梦是一款基于鸿蒙PC Electron框架开发的梦幻糖果收集游戏。项目采用现代Web技术栈构建,以其充满童趣的设计理念和精美的视觉效果,为用户带来一个五彩缤纷的糖果世界。玩家可以通过点击收集各种可爱的糖果,解锁完整的糖果图鉴,并记录自己的甜蜜愿望。
这个项目展示了如何利用HTML5、CSS3和JavaScript这些基础的Web技术,结合Electron运行时环境,开发出具有丰富交互体验的桌面应用程序。通过这个项目,我们可以学习到状态管理、数据持久化、动画效果实现等核心技术点。
1.2 项目特点
| 特点 | 说明 |
|---|---|
| 梦幻风格 | 彩虹渐变色系、浮动糖果装饰、流畅动画 |
| 交互丰富 | 点击收集、分类筛选、弹窗详情、魔法特效 |
| 数据持久化 | localStorage保存收集进度和愿望 |
| 响应式设计 | 适配多种屏幕尺寸 |
| 纯前端实现 | 无需后端服务器 |
1.3 技术栈
- 前端框架: 原生HTML5 + CSS3 + JavaScript ES6+
- 图标方案: Emoji表情符号
- 运行时: Electron (HarmonyOS定制版)
- 原生层: ArkTS + libadapter.so
- 数据存储: localStorage
- 构建工具: Hvigor
二、技术架构设计
2.1 整体架构
┌─────────────────────────────────────────────────────────────┐
│ 前端应用层 │
│ (HTML/CSS/JavaScript + 糖果收集游戏) │
├─────────────────────────────────────────────────────────────┤
│ Electron + Preload 层 │
│ (IPC 通信、API 暴露) │
├─────────────────────────────────────────────────────────────┤
│ HarmonyOS 原生层 │
│ (libadapter.so + ETS Adapters) │
└─────────────────────────────────────────────────────────────┘
2.2 模块划分
| 模块 | 职责 | 文件 |
|---|---|---|
| 视图层 | HTML结构定义 | index.html |
| 样式层 | 视觉样式和动画 | style.css |
| 业务层 | 应用逻辑和状态管理 | js/app.js |
| 数据层 | 糖果数据定义 | js/app.js (内置) |
2.3 目录结构
ohos_hap/
├── electron/ # HAP入口模块
├── web_engine/ # 核心引擎模块
│ └── src/main/
│ ├── ets/
│ │ ├── adapter/ # 鸿蒙原生适配器
│ │ └── jsbindings/ # JS绑定层
│ └── resources/resfile/resources/app/
│ ├── index.html # 主页面入口
│ ├── style.css # 样式文件
│ └── js/
│ └── app.js # 应用逻辑
├── AppScope/
│ └── resources/base/element/
│ └── string.json # 应用名称配置
└── docs/ # 文档目录
三、核心功能实现详解
3.1 糖果数据结构设计
糖果数据采用集中式管理,所有糖果信息定义在一个数组中:
const candyData = [
{
id: 1,
name: '彩虹糖',
emoji: '🍬',
type: 'colored',
description: '五彩缤纷的彩虹糖,每一颗都有不同的味道和颜色,象征着生活的多彩与美好。'
},
{
id: 2,
name: '棒棒糖',
emoji: '🍭',
type: 'colored',
description: '甜蜜的棒棒糖,旋转的色彩像彩虹一样美丽,是童年最美好的回忆。'
},
// ... 更多糖果数据
];
数据结构字段说明:
| 字段 | 类型 | 说明 |
|---|---|---|
| id | number | 唯一标识符 |
| name | string | 糖果名称 |
| emoji | string | 表情符号作为图标 |
| type | string | 分类:colored/chocolate/cake |
| description | string | 详细描述 |
分类类型映射:
const typeLabels = {
colored: '彩色糖',
chocolate: '巧克力',
cake: '糕点'
};
3.2 状态管理系统
应用采用简单的全局变量管理状态:
// 已收集的糖果ID集合
let collectedCandies = new Set();
// 用户愿望列表
let wishes = [];
状态初始化流程:
function initApp() {
// 1. 从本地存储加载已收集的糖果
loadCollectedCandies();
// 2. 从本地存储加载愿望列表
loadWishes();
// 3. 渲染糖果网格
renderCandyGrid();
// 4. 渲染图鉴
renderAlbum();
// 5. 更新统计数据
updateStats();
// 6. 设置事件监听
setupEventListeners();
}
数据持久化实现:
function loadCollectedCandies() {
const saved = localStorage.getItem('collectedCandies');
if (saved) {
// 从JSON字符串恢复为Set对象
collectedCandies = new Set(JSON.parse(saved));
}
}
function saveCollectedCandies() {
// Set对象转换为数组后序列化为JSON
localStorage.setItem('collectedCandies', JSON.stringify([...collectedCandies]));
}
function loadWishes() {
const saved = localStorage.getItem('candyWishes');
if (saved) {
wishes = JSON.parse(saved);
renderWishes();
}
}
function saveWishes() {
localStorage.setItem('candyWishes', JSON.stringify(wishes));
}
3.3 糖果网格渲染
糖果展示区使用动态渲染方式,根据收集状态显示不同的样式:
function renderCandyGrid() {
const grid = document.getElementById('candyGrid');
grid.innerHTML = ''; // 清空现有内容
candyData.forEach(candy => {
// 判断是否已收集
const isCollected = collectedCandies.has(candy.id);
// 创建卡片元素
const card = document.createElement('div');
card.className = `candy-card ${isCollected ? 'collected' : 'not-collected'}`;
card.innerHTML = `
<div class="candy-emoji">${candy.emoji}</div>
<div class="candy-name">${candy.name}</div>
<div class="candy-type">${getTypeLabel(candy.type)}</div>
`;
// 绑定点击事件
card.addEventListener('click', () => handleCandyClick(candy));
grid.appendChild(card);
});
}
CSS样式控制:
.candy-card.collected {
border-color: var(--rainbow-green);
background: linear-gradient(135deg, #e8f5e9, #ffffff);
}
.candy-card.not-collected {
opacity: 0.7;
filter: grayscale(30%);
}
.candy-card.not-collected:hover {
opacity: 1;
filter: grayscale(0%);
}
3.4 点击收集机制
当用户点击糖果卡片时的处理流程:
function handleCandyClick(candy) {
// 检查是否已收集
const isCollected = collectedCandies.has(candy.id);
// 如果未收集,则执行收集逻辑
if (!isCollected) {
collectedCandies.add(candy.id);
saveCollectedCandies();
renderCandyGrid();
renderAlbum();
updateStats();
showCollectEffect(candy);
}
// 显示详情弹窗
showCandyModal(candy);
}
收集动画效果:
function showCollectEffect(candy) {
const effect = document.createElement('div');
effect.className = 'collect-effect';
effect.innerHTML = `${candy.emoji} +1`;
effect.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 3rem;
animation: collectFloat 1s ease-out forwards;
z-index: 1000;
pointer-events: none;
`;
document.body.appendChild(effect);
// 1秒后移除动画元素
setTimeout(() => {
effect.remove();
}, 1000);
}
3.5 弹窗详情展示
使用模态框展示糖果详细信息:
function showCandyModal(candy) {
const modal = document.getElementById('candyModal');
const body = document.getElementById('modalBody');
const isCollected = collectedCandies.has(candy.id);
body.innerHTML = `
<div class="modal-emoji">${candy.emoji}</div>
<div class="modal-name">${candy.name}</div>
<div class="modal-type">${getTypeLabel(candy.type)}</div>
<p class="modal-description">${candy.description}</p>
<div style="margin-top: 16px;">
<span style="color: ${isCollected ? '#27ae60' : '#e74c3c'};">
${isCollected ? '✓ 已收集' : '尚未收集'}
</span>
</div>
`;
modal.classList.add('active');
}
弹窗动画样式:
.modal-overlay {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.5);
justify-content: center;
align-items: center;
z-index: 100;
backdrop-filter: blur(5px);
}
.modal-overlay.active {
display: flex;
animation: fadeIn 0.3s ease;
}
.modal-content {
background: var(--bg-secondary);
border-radius: var(--radius-lg);
max-width: 400px;
width: 90%;
animation: slideUp 0.3s ease;
}
3.6 统计进度系统
实时更新收集进度统计:
function updateStats() {
const collected = collectedCandies.size;
const total = candyData.length;
const percentage = Math.round((collected / total) * 100);
// 更新数值显示
document.getElementById('collectedCount').textContent = collected;
document.getElementById('totalCount').textContent = total;
document.getElementById('percentage').textContent = percentage + '%';
// 更新进度条宽度
document.getElementById('progressFill').style.width = percentage + '%';
}
3.7 图鉴分类筛选
支持按分类筛选图鉴:
function filterAlbum(tab) {
const items = document.querySelectorAll('.album-item');
items.forEach(item => {
const candyId = parseInt(item.dataset.id);
const candy = candyData.find(c => c.id === candyId);
if (!candy) return;
// 根据筛选条件显示/隐藏
if (tab === 'all' || candy.type === tab) {
item.style.display = 'block';
} else {
item.style.display = 'none';
}
});
// 更新标签按钮状态
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.classList.toggle('active', btn.dataset.tab === tab);
});
}
3.8 愿望系统实现
用户可以提交和查看愿望:
function submitWish() {
const input = document.getElementById('wishInput');
const wish = input.value.trim();
if (!wish) return;
// 添加新愿望
wishes.push({
id: Date.now(),
text: wish,
time: new Date().toLocaleString('zh-CN')
});
saveWishes();
renderWishes();
input.value = '';
}
function renderWishes() {
const list = document.getElementById('wishList');
list.innerHTML = '';
// 倒序显示最新愿望
wishes.slice().reverse().forEach(wish => {
const item = document.createElement('div');
item.className = 'wish-item';
item.innerHTML = `
<p style="font-weight: 600;">${wish.text}</p>
<p style="font-size: 0.8rem; opacity: 0.8;">${wish.time}</p>
`;
list.appendChild(item);
});
}
3.9 魔法特效系统
点击按钮触发粒子爆炸动画:
function triggerMagic() {
const effect = document.getElementById('magicEffect');
effect.innerHTML = '';
const emojis = ['✨', '🌈', '🍬', '🍭', '🌟', '💫', '⭐', '💖', '🎀', '🎊'];
// 创建20个粒子
for (let i = 0; i < 20; i++) {
const particle = document.createElement('span');
particle.className = 'magic-particle';
particle.textContent = emojis[Math.floor(Math.random() * emojis.length)];
// 计算粒子运动轨迹
const angle = (i / 20) * Math.PI * 2;
const distance = 100 + Math.random() * 100;
const tx = Math.cos(angle) * distance;
const ty = Math.sin(angle) * distance;
// 设置CSS自定义属性
particle.style.setProperty('--tx', tx + 'px');
particle.style.setProperty('--ty', ty + 'px');
particle.style.animationDelay = Math.random() * 0.3 + 's';
effect.appendChild(particle);
}
effect.style.display = 'block';
// 1秒后清除
setTimeout(() => {
effect.style.display = 'none';
effect.innerHTML = '';
}, 1000);
}
四、前端样式设计
4.1 彩虹色系设计
:root {
/* 彩虹色系 */
--rainbow-red: #ff6b6b;
--rainbow-orange: #ffa94d;
--rainbow-yellow: #ffd43b;
--rainbow-green: #69db7c;
--rainbow-blue: #74c0fc;
--rainbow-purple: #b197fc;
--rainbow-pink: #f783ac;
/* 背景色 */
--bg-primary: #fff5f5;
--bg-secondary: #ffffff;
--bg-gradient-start: #fff0f3;
--bg-gradient-end: #f0f9ff;
}
4.2 渐变文字效果
标题使用彩虹渐变效果:
.logo h1 {
font-size: 3rem;
background: linear-gradient(90deg,
var(--rainbow-red),
var(--rainbow-orange),
var(--rainbow-yellow),
var(--rainbow-green),
var(--rainbow-blue),
var(--rainbow-purple)
);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
font-weight: 800;
letter-spacing: 4px;
}
4.3 动画关键帧
/* 浮动动画 */
@keyframes float {
0%, 100% { transform: translateY(0) rotate(0deg); }
50% { transform: translateY(-20px) rotate(10deg); }
}
/* 弹入动画 */
@keyframes bounceIn {
0% { transform: scale(0); opacity: 0; }
50% { transform: scale(1.1); }
100% { transform: scale(1); opacity: 1; }
}
/* 脉冲动画 */
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
/* 粒子爆炸动画 */
@keyframes explode {
0% { transform: translate(0, 0) scale(1); opacity: 1; }
100% { transform: translate(var(--tx), var(--ty)) scale(0); opacity: 0; }
}
4.4 响应式布局
@media (max-width: 768px) {
.stats-container {
flex-direction: column;
align-items: center;
}
.candy-grid {
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
}
}
@media (max-width: 480px) {
.logo h1 {
font-size: 1.5rem;
}
}
五、事件监听系统
function setupEventListeners() {
// 关闭弹窗按钮
document.getElementById('closeModal').addEventListener('click', closeModal);
// 分类标签切换
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.addEventListener('click', () => filterAlbum(btn.dataset.tab));
});
// 提交愿望按钮
document.getElementById('submitWish').addEventListener('click', submitWish);
// 愿望输入回车提交
document.getElementById('wishInput').addEventListener('keypress', (e) => {
if (e.key === 'Enter') submitWish();
});
// 魔法按钮
document.getElementById('magicBtn').addEventListener('click', triggerMagic);
// 点击遮罩关闭弹窗
document.getElementById('candyModal').addEventListener('click', (e) => {
if (e.target === document.getElementById('candyModal')) {
closeModal();
}
});
}
六、数据管理
6.1 糖果数据完整列表
| ID | 名称 | Emoji | 分类 |
|---|---|---|---|
| 1 | 彩虹糖 | 🍬 | 彩色糖 |
| 2 | 棒棒糖 | 🍭 | 彩色糖 |
| 3 | 棉花糖 | 🍡 | 彩色糖 |
| 4 | 水果糖 | 🍇 | 彩色糖 |
| 5 | 星星糖 | ⭐ | 彩色糖 |
| 6 | 巧克力 | 🍫 | 巧克力 |
| 7 | 黑巧克力 | 🍫 | 巧克力 |
| 8 | 牛奶巧克力 | 🥛 | 巧克力 |
| 9 | 曲奇饼干 | 🍪 | 糕点 |
| 10 | 纸杯蛋糕 | 🧁 | 糕点 |
| 11 | 甜甜圈 | 🍩 | 糕点 |
| 12 | 生日蛋糕 | 🎂 | 糕点 |
| 13 | 奶油蛋糕 | 🍰 | 糕点 |
| 14 | 爆米花 | 🍿 | 彩色糖 |
| 15 | 口香糖 | 🫘 | 彩色糖 |
| 16 | 果冻 | 🍮 | 彩色糖 |
| 17 | 布丁 | 🍮 | 糕点 |
| 18 | 马卡龙 | 🥠 | 糕点 |
| 19 | 冰淇淋 | 🍦 | 彩色糖 |
| 20 | 雪糕 | 🍧 | 彩色糖 |
七、代码优化建议
7.1 潜在改进点
| 改进项 | 现状 | 建议 |
|---|---|---|
| 数据结构 | 内置JSON | 可引入IndexedDB |
| 状态管理 | 全局变量 | 可使用观察者模式 |
| 渲染性能 | 直接innerHTML | 可使用虚拟DOM |
| 错误处理 | 基本缺失 | 增加异常捕获 |
| 单元测试 | 无 | 添加Jest测试 |
7.2 性能优化示例
使用DocumentFragment减少DOM操作:
function renderCandyGrid() {
const grid = document.getElementById('candyGrid');
const fragment = document.createDocumentFragment();
candyData.forEach(candy => {
const card = document.createElement('div');
card.className = `candy-card ${collectedCandies.has(candy.id) ? 'collected' : 'not-collected'}`;
card.innerHTML = `
<div class="candy-emoji">${candy.emoji}</div>
<div class="candy-name">${candy.name}</div>
<div class="candy-type">${getTypeLabel(candy.type)}</div>
`;
card.addEventListener('click', () => handleCandyClick(candy));
fragment.appendChild(card);
});
grid.innerHTML = '';
grid.appendChild(fragment);
}
八、总结与展望
8.1 项目成果
彩虹糖的梦项目成功实现了以下功能:
- 糖果收集系统:点击收集20种不同的糖果,带有精美的动画效果
- 进度统计:实时显示收集数量和百分比,进度条可视化
- 图鉴系统:支持分类筛选,区分已收集/未收集状态
- 愿望功能:用户可提交愿望,数据本地持久化
- 魔法特效:粒子爆炸动画,增强互动体验
8.2 技术收获
通过这个项目,我们掌握了以下技术要点:
- 状态管理:使用Set和数组管理应用状态
- 数据持久化:localStorage的读写操作
- DOM操作:动态创建元素、事件委托
- 动画实现:CSS关键帧、JavaScript动态样式
- 响应式设计:媒体查询、弹性布局
- 用户体验:收集反馈、弹窗交互
8.3 未来规划
| 优先级 | 功能 | 说明 |
|---|---|---|
| 高 | 音效系统 | 添加背景音乐和音效 |
| 高 | 动画升级 | 引入Lottie动画 |
| 中 | 社交分享 | 分享收集成果 |
| 中 | 成就系统 | 解锁特殊成就 |
| 低 | 主题商店 | 切换不同主题 |
8.4 技术拓展
未来可以考虑引入以下技术提升:
- Canvas/WebGL:实现更炫酷的粒子效果
- Service Worker:实现离线功能和推送通知
- Web Components:组件化开发
- PWA:支持添加到桌面
- TypeScript:类型安全提升代码质量
附录
核心文件列表
| 文件 | 行数 | 说明 |
|---|---|---|
| index.html | ~150 | 主页面结构 |
| style.css | ~710 | 样式和动画 |
| js/app.js | ~280 | 核心逻辑 |
| string.json | ~11 | 应用配置 |
浏览器兼容性
- Chrome 80+
- Firefox 75+
- Safari 13+
- Edge 80+
参考资料
- MDN Web Docs - CSS动画
- MDN Web Docs - localStorage
- Electron官方文档
- HarmonyOS开发者文档
更多推荐



所有评论(0)