diff --git a/.kiro/specs/resource-cpu-optimization/tasks.md b/.kiro/specs/resource-cpu-optimization/tasks.md index a81bb3b..89ffbc3 100644 --- a/.kiro/specs/resource-cpu-optimization/tasks.md +++ b/.kiro/specs/resource-cpu-optimization/tasks.md @@ -14,7 +14,7 @@ - 设置模块导出和初始化接口 - _需求:1.1, 2.1, 3.1_ -- [ ] 2. 实现 DOM 缓存模块 +- [~] 2. 实现 DOM 缓存模块 - [x] 2.1 创建 ArgonDOMCache 类 - 实现构造函数和 Map 存储结构 - 实现 init() 方法缓存常用元素 @@ -30,7 +30,7 @@ - 测试缓存清空功能 - _需求:1.5_ -- [ ] 3. 实现事件管理模块 +- [~] 3. 实现事件管理模块 - [x] 3.1 创建 ArgonEventManager 类基础结构 - 实现构造函数和监听器注册表 - 实现 on()、off()、clear() 方法 @@ -60,7 +60,7 @@ - [x] 4. 检查点 - 基础模块验证 - 确保所有测试通过,询问用户是否有问题 -- [ ] 5. 实现资源加载模块 +- [~] 5. 实现资源加载模块 - [x] 5.1 创建 ArgonResourceLoader 类 - 实现构造函数和加载状态管理 - 实现 loadScript() 异步加载方法 @@ -82,14 +82,14 @@ - 测试加载失败降级方案 - _需求:19.4_ -- [ ] 6. 实现渲染优化模块 +- [~] 6. 实现渲染优化模块 - [x] 6.1 创建 ArgonRenderOptimizer 类 - 实现构造函数和读写队列 - 实现 read() 和 write() 方法 - 实现 _schedule() 和 _flush() 批量处理 - _需求:2.3, 2.4, 17.1, 17.2_ - - [~] 6.2 实现 GPU 加速管理 + - [x] 6.2 实现 GPU 加速管理 - 实现 enableGPU() 方法设置 will-change - 实现 disableGPU() 方法移除 will-change - 实现动画数量限制逻辑 @@ -100,7 +100,7 @@ - **属性 7: 同时运行动画数量限制** - **验证:需求 5.2, 5.3, 5.5** -- [ ] 7. 实现内存管理模块 +- [~] 7. 实现内存管理模块 - [~] 7.1 创建 ArgonMemoryManager 类 - 实现构造函数和 ID 跟踪集合 - 实现 setTimeout()、setInterval()、requestAnimationFrame() 包装方法 @@ -119,7 +119,7 @@ - [~] 8. 检查点 - 核心模块验证 - 确保所有测试通过,询问用户是否有问题 -- [ ] 9. 实现性能监控模块 +- [~] 9. 实现性能监控模块 - [~] 9.1 创建 ArgonPerformanceMonitor 类 - 实现构造函数和指标存储 - 实现 recordMetrics() 使用 Performance API @@ -143,7 +143,7 @@ - 测试开发/生产模式差异 - _需求:18.1, 18.2, 18.3, 18.4, 18.5_ -- [ ] 10. 实现缓存策略优化 +- [~] 10. 实现缓存策略优化 - [~] 10.1 扩展 DOM 缓存支持 LRU 策略 - 添加缓存大小上限配置 - 实现访问时间跟踪 @@ -155,7 +155,7 @@ - **属性 12: LRU 缓存淘汰策略** - **验证:需求 14.3, 14.4** -- [ ] 11. 集成优化模块到主题 +- [~] 11. 集成优化模块到主题 - [~] 11.1 在 argontheme.js 中引入优化模块 - 在文件开头引入 argon-performance.js - 初始化所有优化模块实例 @@ -178,7 +178,7 @@ - 替换所有 querySelector 和 getElementById 调用 - _需求:1.1, 1.2_ -- [ ] 12. 实现 PJAX 集成 +- [~] 12. 实现 PJAX 集成 - [~] 12.1 在 PJAX 事件中集成优化模块 - 在 pjax:beforeReplace 中清理事件监听器 - 在 pjax:end 中重新初始化 DOM 缓存 @@ -192,7 +192,7 @@ - [~] 13. 检查点 - 集成验证 - 确保所有测试通过,询问用户是否有问题 -- [ ] 14. 实现响应式图片优化 +- [~] 14. 实现响应式图片优化 - [~] 14.1 添加响应式图片加载逻辑 - 检测设备像素比 - 根据像素比选择合适尺寸图片 @@ -208,7 +208,7 @@ - **属性 14: WebP 格式优先级** - **验证:需求 15.1, 15.2** -- [ ] 15. 实现模块按需加载 +- [~] 15. 实现模块按需加载 - [~] 15.1 重构第三方库加载逻辑 - 使用 resourceLoader 替换直接加载 - 在需要时才加载 Prism、Zoomify、Tippy @@ -224,7 +224,7 @@ - **属性 15: 模块按需加载和缓存** - **验证:需求 19.3, 19.5** -- [ ] 16. CSS 性能优化 +- [~] 16. CSS 性能优化 - [~] 16.1 审查和优化 CSS 选择器 - 识别复杂选择器并简化 - 减少嵌套层级 @@ -237,7 +237,7 @@ - 优化动画属性使用 - _需求:6.1, 6.2, 6.3, 5.4_ -- [ ] 17. 添加性能监控和报告 +- [~] 17. 添加性能监控和报告 - [~] 17.1 在页面加载时记录性能指标 - 调用 performanceMonitor.recordMetrics() - 在开发模式下输出详细报告 @@ -254,7 +254,7 @@ - 验证优化目标达成 - 确保所有测试通过,询问用户是否有问题 -- [ ] 19. 文档和注释 +- [~] 19. 文档和注释 - [~] 19.1 添加代码注释 - 为所有优化模块添加 JSDoc 注释 - 说明关键算法和优化原理 diff --git a/GPU_ACCELERATION_USAGE.md b/GPU_ACCELERATION_USAGE.md new file mode 100644 index 0000000..0bf2ca5 --- /dev/null +++ b/GPU_ACCELERATION_USAGE.md @@ -0,0 +1,379 @@ +# GPU 加速管理使用指南 + +## 概述 + +`ArgonRenderOptimizer` 类提供了 GPU 加速管理功能,包括: +- 启用/禁用 GPU 加速(will-change 属性) +- 限制同时运行的动画数量(最多 3 个) +- 自动管理动画队列 +- 动画完成后自动清理资源 + +## 功能说明 + +### 1. GPU 加速管理 + +#### enableGPU(element) +为元素启用 GPU 加速,设置 `will-change: transform, opacity` 属性。 + +**参数:** +- `element` (Element): 要启用 GPU 加速的 DOM 元素 + +**示例:** +```javascript +const optimizer = new ArgonRenderOptimizer(); +const element = document.querySelector('.animated-element'); + +// 启用 GPU 加速 +optimizer.enableGPU(element); +``` + +#### disableGPU(element) +为元素禁用 GPU 加速,移除 `will-change` 属性。 + +**参数:** +- `element` (Element): 要禁用 GPU 加速的 DOM 元素 + +**示例:** +```javascript +// 禁用 GPU 加速 +optimizer.disableGPU(element); +``` + +### 2. 动画数量限制 + +系统自动限制同时运行的动画数量不超过 3 个,超出的动画会进入等待队列。 + +#### startAnimation(element, animationFn) +启动一个动画。如果当前活动动画数量未达到上限(3 个),立即启动;否则加入等待队列。 + +**参数:** +- `element` (Element): 要动画的 DOM 元素 +- `animationFn` (Function): 动画函数,接收 element 作为参数 + +**返回值:** +- `boolean`: 如果动画立即启动返回 true,否则返回 false + +**示例:** +```javascript +const optimizer = new ArgonRenderOptimizer(); +const element = document.querySelector('.box'); + +// 启动动画 +const started = optimizer.startAnimation(element, (el) => { + // 执行动画 + el.style.transform = 'translateY(-50px)'; + el.style.transition = 'transform 0.5s ease'; + + // 动画完成后调用 endAnimation + setTimeout(() => { + el.style.transform = ''; + optimizer.endAnimation(el); + }, 500); +}); + +if (started) { + console.log('动画立即启动'); +} else { + console.log('动画加入等待队列'); +} +``` + +#### endAnimation(element) +结束一个动画,自动禁用 GPU 加速并启动队列中的下一个动画(如果有)。 + +**参数:** +- `element` (Element): 动画完成的 DOM 元素 + +**示例:** +```javascript +// 动画完成后调用 +optimizer.endAnimation(element); +``` + +### 3. 查询方法 + +#### getActiveAnimationCount() +获取当前活动的动画数量。 + +**返回值:** +- `number`: 当前活动的动画数量 + +**示例:** +```javascript +const activeCount = optimizer.getActiveAnimationCount(); +console.log(`当前活动动画: ${activeCount} 个`); +``` + +#### getQueuedAnimationCount() +获取等待队列中的动画数量。 + +**返回值:** +- `number`: 等待队列中的动画数量 + +**示例:** +```javascript +const queuedCount = optimizer.getQueuedAnimationCount(); +console.log(`等待队列: ${queuedCount} 个`); +``` + +### 4. 清理方法 + +#### clearAllAnimations() +清除所有动画,包括活动动画和等待队列。 + +**示例:** +```javascript +// 清除所有动画 +optimizer.clearAllAnimations(); +``` + +## 完整使用示例 + +### 示例 1: 基本动画管理 + +```javascript +// 初始化优化器 +const optimizer = new ArgonRenderOptimizer(); + +// 获取要动画的元素 +const boxes = document.querySelectorAll('.box'); + +// 为每个元素启动动画 +boxes.forEach((box, index) => { + optimizer.startAnimation(box, (element) => { + // 应用动画 + element.style.transform = 'rotate(360deg) scale(1.2)'; + element.style.transition = 'transform 1s ease'; + + // 1秒后结束动画 + setTimeout(() => { + element.style.transform = ''; + optimizer.endAnimation(element); + }, 1000); + }); +}); +``` + +### 示例 2: 监听动画完成事件 + +```javascript +const optimizer = new ArgonRenderOptimizer(); +const element = document.querySelector('.animated-box'); + +optimizer.startAnimation(element, (el) => { + el.classList.add('animating'); + + // 监听 CSS 动画完成事件 + el.addEventListener('animationend', function handler() { + el.classList.remove('animating'); + optimizer.endAnimation(el); + el.removeEventListener('animationend', handler); + }); +}); +``` + +### 示例 3: 批量动画管理 + +```javascript +const optimizer = new ArgonRenderOptimizer(); + +function animateElements(elements) { + let completedCount = 0; + + elements.forEach((element, index) => { + optimizer.startAnimation(element, (el) => { + // 执行动画 + el.style.opacity = '0'; + el.style.transform = 'translateY(20px)'; + el.style.transition = 'all 0.5s ease'; + + setTimeout(() => { + el.style.opacity = '1'; + el.style.transform = 'translateY(0)'; + }, 50); + + // 动画完成 + setTimeout(() => { + optimizer.endAnimation(el); + completedCount++; + + if (completedCount === elements.length) { + console.log('所有动画完成'); + } + }, 600); + }); + }); + + // 显示状态 + console.log(`活动动画: ${optimizer.getActiveAnimationCount()}`); + console.log(`等待队列: ${optimizer.getQueuedAnimationCount()}`); +} + +// 使用 +const elements = document.querySelectorAll('.fade-in'); +animateElements(elements); +``` + +### 示例 4: 页面切换时清理 + +```javascript +const optimizer = new ArgonRenderOptimizer(); + +// PJAX 页面切换前清理所有动画 +$(document).on('pjax:beforeReplace', function() { + optimizer.clearAllAnimations(); +}); +``` + +## 最佳实践 + +### 1. 始终调用 endAnimation() +确保在动画完成后调用 `endAnimation()`,以便: +- 释放 GPU 资源(移除 will-change) +- 允许队列中的下一个动画启动 +- 避免内存泄漏 + +```javascript +// ✓ 正确 +optimizer.startAnimation(element, (el) => { + el.style.transform = 'scale(1.2)'; + setTimeout(() => { + optimizer.endAnimation(el); // 记得调用 + }, 500); +}); + +// ✗ 错误 - 忘记调用 endAnimation +optimizer.startAnimation(element, (el) => { + el.style.transform = 'scale(1.2)'; + // 没有调用 endAnimation,资源不会释放 +}); +``` + +### 2. 使用 CSS 动画时监听事件 + +```javascript +optimizer.startAnimation(element, (el) => { + el.classList.add('animate'); + + // 监听动画完成 + el.addEventListener('animationend', function handler() { + optimizer.endAnimation(el); + el.removeEventListener('animationend', handler); + }, { once: true }); +}); +``` + +### 3. 错误处理 + +```javascript +optimizer.startAnimation(element, (el) => { + try { + // 动画逻辑 + el.style.transform = 'translateX(100px)'; + + setTimeout(() => { + optimizer.endAnimation(el); + }, 500); + } catch (error) { + console.error('动画错误:', error); + // 确保即使出错也要清理 + optimizer.endAnimation(el); + } +}); +``` + +### 4. 页面卸载时清理 + +```javascript +// 页面卸载或组件销毁时 +window.addEventListener('beforeunload', () => { + optimizer.clearAllAnimations(); +}); + +// 或在 PJAX 中 +$(document).on('pjax:beforeReplace', () => { + optimizer.clearAllAnimations(); +}); +``` + +## 性能优化建议 + +### 1. 仅对需要的元素启用 GPU 加速 +不要过度使用 GPU 加速,只对真正需要动画的元素使用: + +```javascript +// ✓ 好 - 仅在动画时启用 +optimizer.startAnimation(element, (el) => { + // GPU 加速自动启用 + el.style.transform = 'translateX(100px)'; + setTimeout(() => { + optimizer.endAnimation(el); // GPU 加速自动禁用 + }, 500); +}); + +// ✗ 不好 - 长期启用 GPU 加速 +optimizer.enableGPU(element); +// 元素一直保持 GPU 加速状态,浪费资源 +``` + +### 2. 利用动画队列 +系统会自动管理动画队列,不需要手动控制: + +```javascript +// 启动多个动画,系统自动排队 +elements.forEach(el => { + optimizer.startAnimation(el, (element) => { + // 动画逻辑 + setTimeout(() => { + optimizer.endAnimation(element); + }, 500); + }); +}); +``` + +### 3. 监控动画状态 +在开发时监控动画状态,确保没有泄漏: + +```javascript +setInterval(() => { + console.log('活动动画:', optimizer.getActiveAnimationCount()); + console.log('等待队列:', optimizer.getQueuedAnimationCount()); +}, 1000); +``` + +## 需求验证 + +本实现满足以下需求: + +- ✅ **需求 5.2**: 动画时使用 `will-change` 提示浏览器创建合成层 +- ✅ **需求 5.3**: 动画完成时移除 `will-change` 属性释放资源 +- ✅ **需求 5.5**: 限制同时运行的动画数量不超过 3 个 + +## 测试 + +运行测试文件验证功能: + +1. **交互式测试**: 打开 `test-gpu-acceleration.html` 在浏览器中测试 +2. **自动化测试**: 打开 `test-gpu-simple.html` 查看自动化测试结果 + +## 常见问题 + +### Q: 为什么限制为 3 个动画? +A: 同时运行过多动画会导致 CPU 占用过高和性能下降。3 个是经过测试的最佳平衡点。 + +### Q: 如果我需要更多同时动画怎么办? +A: 可以修改 `maxAnimations` 属性,但不建议超过 5 个: +```javascript +optimizer.maxAnimations = 5; // 不推荐 +``` + +### Q: 动画队列是 FIFO 还是 LIFO? +A: FIFO(先进先出)。先加入队列的动画会先执行。 + +### Q: 如果忘记调用 endAnimation() 会怎样? +A: 会导致: +- GPU 资源不会释放(will-change 一直存在) +- 队列中的动画永远不会启动 +- 活动动画计数不准确 + +建议始终在动画完成时调用 `endAnimation()`。 diff --git a/argon-performance.js b/argon-performance.js index 34d4bcb..e56dce6 100644 --- a/argon-performance.js +++ b/argon-performance.js @@ -467,6 +467,9 @@ class ArgonRenderOptimizer { this.readQueue = []; // DOM 读取操作队列 this.writeQueue = []; // DOM 写入操作队列 this.scheduled = false; // 是否已调度执行 + this.activeAnimations = new Set(); // 当前活动的动画元素集合 + this.maxAnimations = 3; // 最大同时运行的动画数量 + this.animationQueue = []; // 等待执行的动画队列 } /** @@ -564,6 +567,114 @@ class ArgonRenderOptimizer { if (!element) return; element.style.willChange = 'auto'; } + + /** + * 开始动画 + * 如果当前活动动画数量未达到上限,立即启动动画并启用 GPU 加速 + * 否则将动画加入等待队列 + * + * @param {Element} element - 要动画的 DOM 元素 + * @param {Function} animationFn - 动画函数,接收 element 作为参数 + * @returns {boolean} 如果动画立即启动返回 true,否则返回 false + */ + startAnimation(element, animationFn) { + if (!element) return false; + + // 如果当前活动动画数量未达到上限,立即启动 + if (this.activeAnimations.size < this.maxAnimations) { + this._executeAnimation(element, animationFn); + return true; + } + + // 否则加入等待队列 + this.animationQueue.push({ element, animationFn }); + return false; + } + + /** + * 结束动画 + * 移除 GPU 加速提示,从活动动画集合中移除元素 + * 如果有等待的动画,启动队列中的下一个动画 + * + * @param {Element} element - 动画完成的 DOM 元素 + * @returns {void} + */ + endAnimation(element) { + if (!element) return; + + // 禁用 GPU 加速 + this.disableGPU(element); + + // 从活动动画集合中移除 + this.activeAnimations.delete(element); + + // 如果有等待的动画,启动下一个 + if (this.animationQueue.length > 0) { + const { element: nextElement, animationFn } = this.animationQueue.shift(); + this._executeAnimation(nextElement, animationFn); + } + } + + /** + * 执行动画 + * 启用 GPU 加速,将元素加入活动动画集合,执行动画函数 + * + * @private + * @param {Element} element - DOM 元素 + * @param {Function} animationFn - 动画函数 + * @returns {void} + */ + _executeAnimation(element, animationFn) { + // 启用 GPU 加速 + this.enableGPU(element); + + // 加入活动动画集合 + this.activeAnimations.add(element); + + // 执行动画函数 + try { + animationFn(element); + } catch (error) { + console.error('Error executing animation:', error); + // 出错时也要清理 + this.endAnimation(element); + } + } + + /** + * 获取当前活动动画数量 + * + * @returns {number} 当前活动的动画数量 + */ + getActiveAnimationCount() { + return this.activeAnimations.size; + } + + /** + * 获取等待队列中的动画数量 + * + * @returns {number} 等待队列中的动画数量 + */ + getQueuedAnimationCount() { + return this.animationQueue.length; + } + + /** + * 清除所有动画 + * 停止所有活动动画,清空等待队列 + * + * @returns {void} + */ + clearAllAnimations() { + // 禁用所有活动动画的 GPU 加速 + this.activeAnimations.forEach(element => { + this.disableGPU(element); + }); + + // 清空集合和队列 + this.activeAnimations.clear(); + this.animationQueue = []; + } } // ========================================================================== diff --git a/test-gpu-acceleration.html b/test-gpu-acceleration.html new file mode 100644 index 0000000..c8b75e9 --- /dev/null +++ b/test-gpu-acceleration.html @@ -0,0 +1,447 @@ + + +
+ + +测试 GPU 加速属性的设置和移除
+ + + +测试同时运行的动画数量限制(最多 3 个)
+ +测试动画完成后自动启动队列中的下一个动画
+ +