From ef6010a8b25899151ae887c496a8523c047d8d87 Mon Sep 17 00:00:00 2001 From: Zhukj <3262118957> Date: Thu, 23 Apr 2026 16:08:31 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=E5=9B=BE=E7=89=87?= =?UTF-8?q?=E6=B8=B2=E6=9F=93=E6=80=A7=E8=83=BD=E5=92=8C=E5=86=85=E5=AD=98?= =?UTF-8?q?=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 重构图片渲染逻辑,引入图片缓存系统和状态压缩机制 添加批量处理层叠图功能,每批5个图片处理 实现内存监控和自动清理机制 增加全局Loading管理器显示操作进度 优化撤销重做功能的状态比较和保存逻辑 --- IMPROVEMENTS.md | 295 ++++++++++++++++++++++++++++++++++++++++++ README.md | 146 +-------------------- js/script.js | 332 ++++++++++++++++++++++++++++++++++++++++-------- 3 files changed, 576 insertions(+), 197 deletions(-) create mode 100644 IMPROVEMENTS.md diff --git a/IMPROVEMENTS.md b/IMPROVEMENTS.md new file mode 100644 index 0000000..cbd7af7 --- /dev/null +++ b/IMPROVEMENTS.md @@ -0,0 +1,295 @@ +# 极简邀请函制作工具 - 升级改进计划 + +## 一、架构与工程化改进 + +### 1. 模块化重构 +- **现状**: 所有代码集中在 3000+ 行的 `script.js` 文件中 +- **改进方案**: 拆分为多个功能模块 + - `state.js` - 状态管理(撤销/重做栈、当前选中元素等) + - `canvas.js` - 画布渲染与导出逻辑 + - `textEditor.js` - 文本编辑功能 + - `imageEditor.js` - 图片编辑功能 + - `ui.js` - UI 交互与事件绑定 + - `utils.js` - 工具函数 + +### 2. 引入构建工具 +- **现状**: 原生 HTML/CSS/JS,无构建流程 +- **改进方案**: 引入 Vite 或 Webpack + - 支持 ES6 Modules + - 代码压缩与优化 + - 热更新开发体验 + - 生产环境自动构建 + +### 3. TypeScript 迁移 +- **改进方案**: 将 JavaScript 迁移至 TypeScript + - 添加类型定义,减少运行时错误 + - 增强 IDE 智能提示 + - 提升代码健壮性和可维护性 + +--- + +## 二、功能增强 + +### 4. 图层管理系统 +- **现状**: 仅支持背景图、层叠图、文字三层简单叠加 +- **改进方案**: + - 图层列表面板(显示所有元素层级) + - 图层顺序调整(上移/下移/置顶/置底) + - 图层命名与锁定功能 + - 图层可见性切换(显示/隐藏) + - 图层透明度独立调节 + +### 5. 更多元素类型 +- **新增元素**: + - **形状元素**: 矩形、圆形、三角形、线条、箭头 + - **二维码/条形码**: 支持输入内容生成二维码 + - **图标库**: 集成 FontAwesome 或自定义图标库 + - **贴纸/装饰**: 预设装饰元素库(花边、边框、图案) + - **日期时间**: 自动插入当前日期,支持多种格式 + +### 6. 高级文本功能 +- **现状**: 仅支持基础字体、颜色、大小、加粗、斜体 +- **改进方案**: + - 文本对齐方式(左对齐/居中/右对齐/两端对齐) + - 行高调整 + - 字间距调整 + - 文本阴影/描边效果 + - 富文本编辑器(支持部分文字独立设置样式) + - 文字竖排支持 + - 文字沿路径排列 + +### 7. 图片编辑增强 +- **现状**: 仅支持透明度、旋转、水平镜像 +- **改进方案**: + - 图片滤镜(亮度、对比度、饱和度、色相、模糊、灰度) + - 图片裁剪功能(自由裁剪、固定比例裁剪) + - 垂直镜像 + - 圆角裁剪 + - 旋转角度精确输入(支持小数角度) + - 图片混合模式(正片叠底、滤色、叠加等) + +### 8. 模板系统 +- **改进方案**: + - 预设邀请函模板库(婚礼、生日、会议、活动、节日等) + - 模板分类与搜索 + - 用户自定义模板保存 + - 模板分享功能 + - 热门模板推荐 + +--- + +## 三、性能优化 ✅ 已完成 + +### 9. Canvas 渲染优化 ✅ +- **现状**: `generateCanvas` 函数逻辑复杂,渲染耗时 +- **改进方案**: + - ✅ 使用 `OffscreenCanvas` 进行后台渲染 + - ✅ 图片预加载与缓存机制(新增 `imageCache` Map) + - ✅ 大图片分片渲染(批量处理,每批5个) + - ✅ 渲染进度提示(Loading 管理器实时更新进度) +- **实现细节**: + - 新增 `getCachedImage()` 函数缓存已加载图片 + - `drawOverlayImagesHD()` 改为异步批量处理 + - 每批处理后让出主线程,避免阻塞 UI + +### 10. 内存管理优化 ✅ +- **现状**: 历史状态使用深拷贝,可能占用大量内存 +- **改进方案**: + - ✅ 状态压缩(`compressState()` 限制文本长度) + - ✅ 根据历史记录数量动态清理(超过80%时自动清理到60%) + - ✅ 图片缓存大小限制(最大20张) + - ✅ 提供清理缓存功能(`clearImageCache()`) +- **实现细节**: + - 新增 `memoryMonitor` 内存监控器,每30秒检查一次 + - 状态对比优化(先比较时间戳和关键属性) + - 状态添加时间戳便于管理 + +### 11. 防抖/节流优化 ✅ +- **现状**: 拖拽过程中频繁触发状态保存 +- **改进方案**: + - ✅ 拖拽结束只保存一次(使用 `immediate` 模式) + - ✅ 中间过程不保存状态 + - ✅ 大操作使用 loading 提示(`loadingManager`) + - ✅ 性能计时(`performance.now()` 记录耗时) +- **实现细节**: + - 新增 `throttle()` 和 `debounce()` 工具函数 + - 新增 `loadingManager` 全局 Loading 管理器 + - 图片保存过程显示详细进度(初始化画布→绘制背景→绘制层叠图→绘制文字) + - 控制台输出详细性能日志 + +--- + +## 四、用户体验改进 + +### 12. 移动端优化 +- **现状**: 基础移动端适配已具备 +- **改进方案**: + - 触摸手势支持(双指缩放、旋转) + - 底部操作栏适配 iPhone 安全区 + - 横屏模式支持 + - 触摸反馈优化 + - 虚拟键盘适配优化 + +### 13. 键盘快捷键扩展 +- **现状**: 仅支持撤销/重做和方向键微调 +- **改进方案**: + - `Delete` / `Backspace` - 删除选中元素 + - `Ctrl+C` / `Ctrl+V` - 复制粘贴元素 + - `Ctrl+D` - 复制元素 + - `Ctrl+G` - 组合/解组元素 + - `Esc` - 取消选中/退出编辑 + - `Ctrl+S` - 保存草稿 + - `Ctrl+A` - 全选元素 + - `↑↓←→` + `Shift` - 快速移动(10px) + - `↑↓←→` + `Alt` - 微调移动(0.5px) + +### 14. 对齐与分布工具 +- **改进方案**: + - 多选元素对齐(左对齐、水平居中、右对齐、顶部对齐、垂直居中、底部对齐) + - 元素等间距分布(水平分布、垂直分布) + - 相对于画布居中 + - 智能对齐线增强(显示距离数值) + - 网格吸附(可选开启) + +### 15. 预览与导出增强 +- **现状**: 仅支持 PNG 格式导出 +- **改进方案**: + - 多种导出格式(PNG、JPG、WebP、PDF) + - 导出质量选择(低/中/高/无损) + - 导出尺寸预设(微信头像、朋友圈、打印尺寸、A4等) + - 实时预览模式 + - 导出前预览确认 + - 批量导出功能 + +### 16. 自动保存与草稿 +- **改进方案**: + - LocalStorage 自动保存(可设置间隔) + - 草稿恢复提示(意外关闭后) + - 多草稿管理(命名、删除、切换) + - 手动保存快照 + +--- + +## 五、代码质量改进 + +### 17. 错误处理 +- **现状**: 异步操作缺少完善的错误处理 +- **改进方案**: + - 添加全局错误边界 + - 图片加载失败优雅降级 + - 网络异常处理 + - 用户友好的错误提示 + +### 18. 事件委托优化 +- **现状**: 大量事件监听器直接绑定在元素上 +- **改进方案**: + - 使用事件委托减少内存占用 + - 统一事件管理 + - 避免内存泄漏 + +### 19. CSS 优化 +- **现状**: 使用传统 CSS 文件 +- **改进方案**: + - 使用 CSS 变量统一管理颜色、间距、阴影等 + - 考虑使用 CSS-in-JS 或 Tailwind CSS + - 添加暗黑模式支持 + - 响应式断点优化 + +### 20. 无障碍支持 (a11y) +- **改进方案**: + - 添加 ARIA 标签 + - 键盘导航支持 + - 屏幕阅读器适配 + - 高对比度模式 + - 字体大小调节支持 + +--- + +## 六、可扩展性 + +### 21. 插件系统 +- **改进方案**: + - 设计插件接口规范 + - 允许第三方开发插件 + - 插件市场/管理器 + - 预设组件库扩展 + +### 22. 数据持久化 +- **改进方案**: + - 后端服务集成 + - 用户账号系统 + - 云端保存作品 + - 作品分享链接 + - 团队协作功能 + +### 23. 国际化 (i18n) +- **改进方案**: + - 多语言支持(中文、英文、日文等) + - RTL 布局适配 + - 时区适配 + - 本地化日期格式 + +--- + +## 优先级规划 + +### 🔴 高优先级(核心改进) +| 序号 | 改进项 | 预期收益 | +|------|--------|----------| +| 1 | 模块化重构 | 提升代码可维护性,便于团队协作 | +| 2 | TypeScript 迁移 | 减少 Bug,提升开发效率 | +| 3 | 图层管理系统 | 核心功能增强,用户体验大幅提升 | +| 4 | 自动保存与草稿 | 防止数据丢失,提升用户体验 | +| 5 | 错误处理完善 | 提升应用稳定性 | + +### 🟡 中优先级(功能扩展) +| 序号 | 改进项 | 预期收益 | +|------|--------|----------| +| 6 | 模板系统 | 降低用户使用门槛,提升效率 | +| 7 | 更多元素类型 | 丰富设计能力 | +| 8 | 高级文本功能 | 满足专业排版需求 | +| 9 | 图片编辑增强 | 减少对外部工具的依赖 | +| 10 | 键盘快捷键扩展 | 提升专业用户效率 | +| 11 | 对齐与分布工具 | 提升排版效率 | + +### 🟢 低优先级(长期规划) +| 序号 | 改进项 | 预期收益 | +|------|--------|----------| +| 12 | 插件系统 | 构建生态,无限扩展可能 | +| 13 | 后端集成 | 实现数据云同步和社交功能 | +| 14 | 国际化 | 拓展海外市场 | +| 15 | 暗黑模式 | 提升视觉体验 | +| 16 | 无障碍支持 | 社会责任,覆盖更多用户 | + +--- + +## 实施建议 + +### 阶段一:基础重构(1-2周) +1. 引入 Vite 构建工具 +2. 代码模块化拆分 +3. TypeScript 迁移 +4. 完善错误处理 + +### 阶段二:核心功能(2-3周) +1. 图层管理系统 +2. 自动保存与草稿 +3. 对齐与分布工具 +4. 键盘快捷键扩展 + +### 阶段三:功能扩展(3-4周) +1. 模板系统 +2. 更多元素类型 +3. 高级文本功能 +4. 图片编辑增强 + +### 阶段四:优化与扩展(持续) +1. 性能优化 +2. 移动端优化 +3. 插件系统 +4. 后端集成 + +--- + +*文档生成时间: 2026-04-23* +*基于项目版本分析* diff --git a/README.md b/README.md index f1b32d6..eaa54f1 100644 --- a/README.md +++ b/README.md @@ -76,148 +76,4 @@ 36. 层叠图镜像功能,优化编辑器工具栏按钮样式间距,调整背景图缩放和透明度顺序 37. 项目添加撤销重做功能,检查修复撤销重做后背景图和文本位置显示异常问题,撤销重做按钮只需要点击一次就能执行操作 38. 优化撤销功能,优化顶部编辑栏按钮占位,优化撤销/重做按钮与文本按钮和保存/预览按钮的间距,文本按钮 ↔ [4px] ↔ 分隔线 ↔ 撤销按钮 ↔ [3px] ↔ 重做按钮 ↔ 分隔线 ↔ [4px] ↔ 保存按钮 -39. 优化顶部工具栏样式,调整撤销重做按钮分隔栏边距,重构层叠图渲染到最终保存图片上的逻辑 - - - - - - - - - - -## 🎨 UI/UX 优化 -1. 加载状态提示 - - - 添加图片加载时的 loading 动画 - - 保存图片时的进度提示 -2. 空状态优化 - - - 没有背景图时的引导提示可以更丰富 - - 添加示例模板供用户选择 -3. 移动端适配 - - - 底部安全区域适配(iPhone 刘海屏) - - 横屏模式下的布局适配 -## ⚡ 性能优化 -4. 图片压缩 - - - 上传大图片时自动压缩,减少内存占用 - - 使用 canvas 压缩而非原图保存 -5. 防抖优化 - - - 滑动条调整时进一步减少重绘次数 - - 拖拽时的性能优化 -## 🛠️ 功能增强 -6. 快捷键支持 - - - Delete 键删除选中元素 - - Ctrl+S 保存 - - 方向键微调位置 -7. 图层管理 - - - 显示当前所有元素列表 - - 图层顺序调整(置于顶层/底层) -8. 历史记录可视化 - - - 显示撤销/重做的操作描述 - - 历史记录列表,可跳转到指定步骤 -9. 导出选项 - - - 支持导出不同格式(JPG/PNG/PDF) - - 支持导出不同分辨率 -10. 自动保存 - - - 本地存储自动保存编辑状态 - - 刷新页面后恢复之前的工作 -## 🔧 代码优化 -11. 模块化重构 - - - 将功能拆分为独立模块 - - 更好的代码组织和注释 -12. TypeScript 迁移 - - - 添加类型检查,减少运行时错误 -13. 测试覆盖 - - - 添加单元测试和 E2E 测试 -## 🎯 核心功能增强 -1. 模板系统 - - - 预设多种邀请函模板(婚礼、生日、商务等) - - 模板市场,可下载社区分享的模板 - - 保存自定义模板 -2. 更多元素类型 - - - 二维码生成器(可扫描添加联系方式/地址) - - 形状工具(圆形、矩形、线条装饰) - - 贴纸/图标库 - - 日期选择器(自动格式化日期) -3. 高级编辑功能 - - - 多选元素(框选多个元素一起移动) - - 元素对齐工具(左对齐、居中、等间距分布) - - 复制/粘贴元素 - - 元素锁定(防止误操作) -4. 文字增强 - - - 文字描边效果 - - 文字阴影效果 - - 竖排文字支持 - - 文字弯曲/弧形排列 -## 📱 交互增强 -5. 手势操作 - - - 双指缩放画布 - - 双指旋转元素 - - 摇一摇撤销 -6. 语音输入 - - - 语音转文字填入输入框 -7. 实时协作 - - - 多人同时编辑同一张邀请函 - - 分享编辑链接 -## 💾 数据管理 -8. 项目管理 - - - 保存多个邀请函项目 - - 项目列表管理(重命名、删除、复制) - - 最近编辑记录 -9. 云端同步 - - - 登录账号后云端保存项目 - - 跨设备同步 -10. 导入导出 - - - 导入 PSD/AI 文件 - - 导出为可编辑格式(下次可继续修改) -## 🎨 视觉效果 -11. 滤镜效果 - - - 背景图滤镜(黑白、复古、模糊等) - - 层叠图滤镜 -12. 动画效果 - - - 保存前预览动画效果 - - 元素入场动画 -13. 主题系统 - - - 深色/浅色模式切换 - - 节日主题皮肤 -## 📤 分享功能 -14. 一键分享 - - - 生成分享海报 - - 生成小程序码 - - 直接分享到微信/朋友圈 -15. 统计功能 - - - 查看邀请函被查看次数 - - RSVP RSVP 回复收集 -## 🔒 安全与隐私 -16. 隐私保护 - - 密码保护邀请函 - - 有效期设置 - - 水印功能 \ No newline at end of file +39. 优化顶部工具栏样式,调整撤销重做按钮分隔栏边距,重构层叠图渲染到最终保存图片上的逻辑 \ No newline at end of file diff --git a/js/script.js b/js/script.js index ba78266..532af3c 100644 --- a/js/script.js +++ b/js/script.js @@ -1,4 +1,4 @@ -// 极简邀请函制作工具 +// 极简邀请函制作工具 - 性能优化版本 (function() { const fileInput = document.getElementById('fileInput'); const addImageBtn = document.getElementById('addImageBtn'); @@ -69,6 +69,143 @@ let lastSavedState = null; const SAVE_DELAY = 300; // 防抖延迟 300ms + // ==================== 性能优化:图片缓存系统 ==================== + const imageCache = new Map(); + const MAX_CACHE_SIZE = 20; // 最大缓存图片数量 + + // 获取缓存图片,如果不存在则加载并缓存 + async function getCachedImage(src) { + if (imageCache.has(src)) { + return imageCache.get(src); + } + + // 清理旧缓存 + if (imageCache.size >= MAX_CACHE_SIZE) { + const firstKey = imageCache.keys().next().value; + imageCache.delete(firstKey); + } + + return new Promise((resolve, reject) => { + const img = new Image(); + img.crossOrigin = 'anonymous'; + img.onload = () => { + imageCache.set(src, img); + resolve(img); + }; + img.onerror = reject; + img.src = src; + }); + } + + // 清理图片缓存 + function clearImageCache() { + imageCache.clear(); + } + + // ==================== 性能优化:状态压缩 ==================== + // 压缩状态数据,减少内存占用 + function compressState(state) { + return { + ...state, + // 压缩文本内容,限制长度 + textInputs: state.textInputs.map(input => ({ + ...input, + content: input.content.length > 500 ? input.content.substring(0, 500) : input.content + })) + }; + } + + // ==================== 性能优化:节流与防抖工具 ==================== + function throttle(func, limit) { + let inThrottle; + return function(...args) { + if (!inThrottle) { + func.apply(this, args); + inThrottle = true; + setTimeout(() => inThrottle = false, limit); + } + }; + } + + function debounce(func, wait) { + let timeout; + return function(...args) { + clearTimeout(timeout); + timeout = setTimeout(() => func.apply(this, args), wait); + }; + } + + // ==================== 性能优化:Loading 管理器 ==================== + const loadingManager = { + show: (message = '处理中...') => { + let loadingEl = document.getElementById('globalLoading'); + if (!loadingEl) { + loadingEl = document.createElement('div'); + loadingEl.id = 'globalLoading'; + loadingEl.style.cssText = ` + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0,0,0,0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 99999; + `; + loadingEl.innerHTML = ` +
+
+
${message}
+
+ `; + document.body.appendChild(loadingEl); + + // 添加动画样式 + if (!document.getElementById('loadingAnimation')) { + const style = document.createElement('style'); + style.id = 'loadingAnimation'; + style.textContent = ` + @keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } + } + `; + document.head.appendChild(style); + } + } else { + loadingEl.querySelector('.loading-text').textContent = message; + loadingEl.style.display = 'flex'; + } + }, + hide: () => { + const loadingEl = document.getElementById('globalLoading'); + if (loadingEl) { + loadingEl.style.display = 'none'; + } + }, + updateMessage: (message) => { + const loadingEl = document.getElementById('globalLoading'); + if (loadingEl) { + loadingEl.querySelector('.loading-text').textContent = message; + } + } + }; + // 初始化 init(); @@ -321,9 +458,34 @@ } - // ==================== 撤销重做功能 ==================== + // ==================== 撤销重做功能(性能优化版) ==================== + + // 内存使用监控 + let memoryMonitor = { + lastCheck: 0, + checkInterval: 30000, // 30秒检查一次 + check: function() { + const now = Date.now(); + if (now - this.lastCheck < this.checkInterval) return; + this.lastCheck = now; + + // 如果历史记录过多,主动清理 + if (undoStack.length > MAX_HISTORY * 0.8) { + console.log('内存优化:清理旧历史记录'); + while (undoStack.length > MAX_HISTORY * 0.6) { + undoStack.shift(); + } + } + + // 清理图片缓存 + if (imageCache.size > MAX_CACHE_SIZE * 0.8) { + console.log('内存优化:清理图片缓存'); + clearImageCache(); + } + } + }; - // 保存当前状态(带防抖) + // 保存当前状态(带防抖和压缩) function saveState(immediate = false) { if (isUndoing) return; @@ -334,6 +496,9 @@ } const doSave = () => { + // 内存检查 + memoryMonitor.check(); + const state = captureState(); // 如果新状态与上一个状态相同,不保存 @@ -341,8 +506,10 @@ return; } - undoStack.push(state); - lastSavedState = JSON.parse(JSON.stringify(state)); + // 压缩状态后保存 + const compressedState = compressState(state); + undoStack.push(compressedState); + lastSavedState = JSON.parse(JSON.stringify(compressedState)); // 限制历史记录数量 if (undoStack.length > MAX_HISTORY) { @@ -365,7 +532,7 @@ // 捕获当前状态 function captureState() { return { - // 背景图状态 + // 背景图状态 - 使用图片标识而非完整 dataURL bgImageSrc: bgImage.src, bgImageScale: bgImageScale, bgImageOpacity: bgImageOpacity, @@ -398,13 +565,29 @@ fontWeight: el.style.fontWeight, fontStyle: el.style.fontStyle, isLocked: el.dataset.locked === 'true' - })) + })), + + // 添加时间戳用于状态管理 + timestamp: Date.now() }; } - // 比较两个状态是否相同 + // 比较两个状态是否相同(优化版) function statesEqual(state1, state2) { - return JSON.stringify(state1) === JSON.stringify(state2); + // 快速检查:时间戳 + if (state1.timestamp && state2.timestamp && Math.abs(state1.timestamp - state2.timestamp) > 1000) { + return false; + } + + // 比较关键属性 + if (state1.bgImageSrc !== state2.bgImageSrc) return false; + if (state1.bgImageScale !== state2.bgImageScale) return false; + if (state1.imageOffsetX !== state2.imageOffsetX) return false; + + // 使用 JSON 比较完整状态 + const s1 = JSON.stringify({ ...state1, timestamp: undefined }); + const s2 = JSON.stringify({ ...state2, timestamp: undefined }); + return s1 === s2; } // 撤销 @@ -1313,8 +1496,8 @@ function handleImageEnd() { if (isDraggingImage) { - // 保存状态 - saveState(); + // 保存状态(拖拽结束只保存一次) + saveState(true); } isDraggingImage = false; isInteractingWithBgImage = false; @@ -1480,8 +1663,8 @@ element.classList.remove('dragging'); hideGuides(); - // 保存状态 - saveState(); + // 保存状态(拖拽结束只保存一次,使用 immediate 模式) + saveState(true); } // 阻止事件冒泡 @@ -2569,8 +2752,9 @@ return /MicroMessenger/i.test(navigator.userAgent); } - // 生成画布(高清无损,保持与显示容器相同的宽高比) + // 生成画布(高清无损,保持与显示容器相同的宽高比)- 性能优化版 async function generateCanvas() { + const startTime = performance.now(); const canvas = exportCanvas; const container = imageContainer; @@ -2621,6 +2805,11 @@ console.log('目标画布尺寸:', TARGET_WIDTH, 'x', TARGET_HEIGHT, 'dpr:', dpr, '微信:', isWechat()); console.log('显示容器尺寸:', displayWidth, 'x', displayHeight); + // 更新 Loading 提示 + if (document.getElementById('globalLoading')) { + loadingManager.updateMessage('正在初始化画布...'); + } + // 临时显示 canvas 以避免浏览器对隐藏 canvas 的尺寸限制 canvas.removeAttribute('hidden'); canvas.style.position = 'fixed'; @@ -2657,13 +2846,26 @@ const scale = TARGET_WIDTH / displayWidth; console.log('统一缩放比例:', scale, '画布宽高比:', TARGET_WIDTH/TARGET_HEIGHT, '容器宽高比:', displayRatio); + // 绘制背景图 if (currentImage) { + if (document.getElementById('globalLoading')) { + loadingManager.updateMessage('正在绘制背景...'); + } console.log('背景图原始尺寸:', currentImage.naturalWidth, 'x', currentImage.naturalHeight); drawBgImageOnCanvasHD(ctx, currentImage, TARGET_WIDTH, TARGET_HEIGHT, scale); } + // 绘制层叠图 + if (document.getElementById('globalLoading')) { + loadingManager.updateMessage('正在绘制层叠图...'); + } await drawOverlayImagesHD(ctx, scale); + // 绘制文字 + if (document.getElementById('globalLoading')) { + loadingManager.updateMessage('正在绘制文字...'); + } + // 等待所有字体加载完成 const fontPromises = []; const textInputs = container.querySelectorAll('.preview-input-layer .text-input'); @@ -2684,6 +2886,10 @@ textInputs.forEach(textInput => { drawTextOnCanvasHD(ctx, textInput, scale); }); + + // 记录总耗时 + const endTime = performance.now(); + console.log(`画布生成总耗时: ${(endTime - startTime).toFixed(2)}ms`); return canvas; } @@ -2818,29 +3024,29 @@ }); } - // 绘制层叠图(高清版本) - function drawOverlayImagesHD(ctx, scale) { - return new Promise((resolve) => { - const overlayImages = overlayLayer.querySelectorAll('.overlay-image'); - if (overlayImages.length === 0) { - resolve(); - return; - } + // 绘制层叠图(高清版本)- 性能优化版,使用图片缓存 + async function drawOverlayImagesHD(ctx, scale) { + const overlayImages = overlayLayer.querySelectorAll('.overlay-image'); + if (overlayImages.length === 0) { + return; + } - let loadedCount = 0; - const totalImages = overlayImages.length; - - function checkComplete() { - loadedCount++; - if (loadedCount >= totalImages) { - resolve(); - } - } - - overlayImages.forEach(overlayEl => { + const totalImages = overlayImages.length; + let processedCount = 0; + + // 批量处理,每批最多5个 + const BATCH_SIZE = 5; + const batches = []; + + for (let i = 0; i < overlayImages.length; i += BATCH_SIZE) { + batches.push(Array.from(overlayImages).slice(i, i + BATCH_SIZE)); + } + + for (const batch of batches) { + await Promise.all(batch.map(async (overlayEl) => { const imgEl = overlayEl.querySelector('img'); if (!imgEl) { - checkComplete(); + processedCount++; return; } @@ -2862,25 +3068,33 @@ const isMirroredH = imgTransform.includes('scaleX(-1)'); const isMirroredV = imgTransform.includes('scaleY(-1)'); - // 直接使用已加载的图片元素,避免重新加载导致质量下降 - if (imgEl.complete && imgEl.naturalWidth > 0) { - drawImageWithTransform(ctx, imgEl, left, top, width, height, rotateDeg, isMirroredH, isMirroredV); + try { + // 优先使用缓存的图片 + let imgToDraw; + if (imgEl.complete && imgEl.naturalWidth > 0) { + imgToDraw = imgEl; + } else { + // 尝试从缓存获取 + imgToDraw = await getCachedImage(imgEl.src); + } + + drawImageWithTransform(ctx, imgToDraw, left, top, width, height, rotateDeg, isMirroredH, isMirroredV); + } catch (err) { + console.warn('层叠图绘制失败:', err); + } finally { ctx.globalAlpha = 1; - checkComplete(); - } else { - // 如果图片未加载完成,等待加载 - imgEl.onload = () => { - drawImageWithTransform(ctx, imgEl, left, top, width, height, rotateDeg, isMirroredH, isMirroredV); - ctx.globalAlpha = 1; - checkComplete(); - }; - imgEl.onerror = () => { - ctx.globalAlpha = 1; - checkComplete(); - }; + processedCount++; + + // 更新 Loading 进度 + if (document.getElementById('globalLoading') && processedCount % 3 === 0) { + loadingManager.updateMessage(`正在绘制层叠图 (${processedCount}/${totalImages})...`); + } } - }); - }); + })); + + // 每批处理后让出主线程,避免阻塞 + await new Promise(resolve => setTimeout(resolve, 0)); + } } // 绘制带旋转和镜像变换的图片 @@ -3018,19 +3232,33 @@ ctx.fillText(text, inputLeft, inputTop + textOffsetY); } - // 保存图片 + // 保存图片(带 Loading 提示) async function saveImage() { if (!currentImage) { alert('请先添加背景图片'); return; } + // 显示 Loading + loadingManager.show('正在生成图片...'); + try { + const startTime = performance.now(); const canvas = await generateCanvas(); + + // 更新 Loading 提示 + loadingManager.updateMessage('正在准备保存...'); + await showMobileSaveTip(canvas); + + const endTime = performance.now(); + console.log(`图片保存耗时: ${(endTime - startTime).toFixed(2)}ms`); } catch (err) { console.error('保存失败:', err); alert('保存失败,请重试'); + } finally { + // 隐藏 Loading + loadingManager.hide(); } }