feat: 实现 GPU 加速管理功能
- 实现 enableGPU() 方法设置 will-change 属性 - 实现 disableGPU() 方法移除 will-change 属性 - 实现动画数量限制逻辑(最多 3 个同时运行) - 实现动画队列自动管理 - 添加 startAnimation() 和 endAnimation() 方法 - 添加 getActiveAnimationCount() 和 getQueuedAnimationCount() 查询方法 - 添加 clearAllAnimations() 清理方法 - 添加错误处理机制 - 创建交互式测试页面和自动化测试 - 创建详细的使用文档 验证需求: - 需求 5.2: 动画时使用 will-change 提示浏览器创建合成层 - 需求 5.3: 动画完成时移除 will-change 属性释放资源 - 需求 5.5: 限制同时运行的动画数量不超过 3 个
This commit is contained in:
@@ -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 注释
|
||||
- 说明关键算法和优化原理
|
||||
|
||||
379
GPU_ACCELERATION_USAGE.md
Normal file
379
GPU_ACCELERATION_USAGE.md
Normal file
@@ -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()`。
|
||||
@@ -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 = [];
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================================================
|
||||
|
||||
447
test-gpu-acceleration.html
Normal file
447
test-gpu-acceleration.html
Normal file
@@ -0,0 +1,447 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>GPU 加速管理测试</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: Arial, sans-serif;
|
||||
padding: 20px;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.test-section {
|
||||
margin: 30px 0;
|
||||
padding: 20px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
background: #f9f9f9;
|
||||
}
|
||||
|
||||
.test-box {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
margin: 10px;
|
||||
display: inline-block;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
border-radius: 10px;
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.test-box:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.controls {
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 10px 20px;
|
||||
margin: 5px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
background: #667eea;
|
||||
color: white;
|
||||
transition: background 0.3s;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
background: #764ba2;
|
||||
}
|
||||
|
||||
button:disabled {
|
||||
background: #ccc;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.status {
|
||||
padding: 15px;
|
||||
margin: 10px 0;
|
||||
background: #e8f4f8;
|
||||
border-left: 4px solid #2196F3;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.status.success {
|
||||
background: #e8f5e9;
|
||||
border-left-color: #4CAF50;
|
||||
}
|
||||
|
||||
.status.error {
|
||||
background: #ffebee;
|
||||
border-left-color: #f44336;
|
||||
}
|
||||
|
||||
.log {
|
||||
background: #263238;
|
||||
color: #aed581;
|
||||
padding: 15px;
|
||||
border-radius: 5px;
|
||||
font-family: 'Courier New', monospace;
|
||||
font-size: 13px;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
||||
.log-entry {
|
||||
margin: 5px 0;
|
||||
}
|
||||
|
||||
.log-entry.info {
|
||||
color: #81d4fa;
|
||||
}
|
||||
|
||||
.log-entry.success {
|
||||
color: #aed581;
|
||||
}
|
||||
|
||||
.log-entry.error {
|
||||
color: #ef5350;
|
||||
}
|
||||
|
||||
.metrics {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 15px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
|
||||
.metric-card {
|
||||
padding: 15px;
|
||||
background: white;
|
||||
border-radius: 5px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.metric-label {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
|
||||
.metric-value {
|
||||
font-size: 24px;
|
||||
font-weight: bold;
|
||||
color: #333;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>🚀 GPU 加速管理测试</h1>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>测试 1: enableGPU() 和 disableGPU()</h2>
|
||||
<p>测试 GPU 加速属性的设置和移除</p>
|
||||
|
||||
<div id="test1-box" class="test-box"></div>
|
||||
|
||||
<div class="controls">
|
||||
<button onclick="testEnableGPU()">启用 GPU 加速</button>
|
||||
<button onclick="testDisableGPU()">禁用 GPU 加速</button>
|
||||
<button onclick="checkWillChange()">检查 will-change 属性</button>
|
||||
</div>
|
||||
|
||||
<div id="test1-status" class="status"></div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>测试 2: 动画数量限制</h2>
|
||||
<p>测试同时运行的动画数量限制(最多 3 个)</p>
|
||||
|
||||
<div id="animation-boxes">
|
||||
<div class="test-box" data-index="1">1</div>
|
||||
<div class="test-box" data-index="2">2</div>
|
||||
<div class="test-box" data-index="3">3</div>
|
||||
<div class="test-box" data-index="4">4</div>
|
||||
<div class="test-box" data-index="5">5</div>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<button onclick="startAllAnimations()">启动所有动画</button>
|
||||
<button onclick="stopAllAnimations()">停止所有动画</button>
|
||||
<button onclick="startSingleAnimation()">启动单个动画</button>
|
||||
</div>
|
||||
|
||||
<div class="metrics">
|
||||
<div class="metric-card">
|
||||
<div class="metric-label">活动动画</div>
|
||||
<div class="metric-value" id="active-count">0</div>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<div class="metric-label">等待队列</div>
|
||||
<div class="metric-value" id="queued-count">0</div>
|
||||
</div>
|
||||
<div class="metric-card">
|
||||
<div class="metric-label">最大限制</div>
|
||||
<div class="metric-value">3</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="test2-status" class="status"></div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>测试 3: 动画队列管理</h2>
|
||||
<p>测试动画完成后自动启动队列中的下一个动画</p>
|
||||
|
||||
<div class="controls">
|
||||
<button onclick="testAnimationQueue()">测试动画队列</button>
|
||||
<button onclick="clearAnimations()">清除所有动画</button>
|
||||
</div>
|
||||
|
||||
<div id="test3-status" class="status"></div>
|
||||
</div>
|
||||
|
||||
<div class="test-section">
|
||||
<h2>测试日志</h2>
|
||||
<div id="log" class="log"></div>
|
||||
<button onclick="clearLog()">清除日志</button>
|
||||
</div>
|
||||
|
||||
<script src="argon-performance.js"></script>
|
||||
<script>
|
||||
// 初始化渲染优化器
|
||||
const renderOptimizer = new ArgonRenderOptimizer();
|
||||
|
||||
// 日志函数
|
||||
function log(message, type = 'info') {
|
||||
const logDiv = document.getElementById('log');
|
||||
const entry = document.createElement('div');
|
||||
entry.className = `log-entry ${type}`;
|
||||
const timestamp = new Date().toLocaleTimeString();
|
||||
entry.textContent = `[${timestamp}] ${message}`;
|
||||
logDiv.appendChild(entry);
|
||||
logDiv.scrollTop = logDiv.scrollHeight;
|
||||
console.log(message);
|
||||
}
|
||||
|
||||
function clearLog() {
|
||||
document.getElementById('log').innerHTML = '';
|
||||
}
|
||||
|
||||
// 更新指标显示
|
||||
function updateMetrics() {
|
||||
document.getElementById('active-count').textContent = renderOptimizer.getActiveAnimationCount();
|
||||
document.getElementById('queued-count').textContent = renderOptimizer.getQueuedAnimationCount();
|
||||
}
|
||||
|
||||
// 测试 1: enableGPU
|
||||
function testEnableGPU() {
|
||||
const box = document.getElementById('test1-box');
|
||||
renderOptimizer.enableGPU(box);
|
||||
|
||||
const willChange = window.getComputedStyle(box).willChange;
|
||||
const status = document.getElementById('test1-status');
|
||||
|
||||
if (willChange === 'transform, opacity') {
|
||||
status.className = 'status success';
|
||||
status.textContent = '✓ GPU 加速已启用,will-change = "transform, opacity"';
|
||||
log('GPU 加速启用成功', 'success');
|
||||
} else {
|
||||
status.className = 'status error';
|
||||
status.textContent = `✗ GPU 加速启用失败,will-change = "${willChange}"`;
|
||||
log('GPU 加速启用失败', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// 测试 1: disableGPU
|
||||
function testDisableGPU() {
|
||||
const box = document.getElementById('test1-box');
|
||||
renderOptimizer.disableGPU(box);
|
||||
|
||||
const willChange = window.getComputedStyle(box).willChange;
|
||||
const status = document.getElementById('test1-status');
|
||||
|
||||
if (willChange === 'auto') {
|
||||
status.className = 'status success';
|
||||
status.textContent = '✓ GPU 加速已禁用,will-change = "auto"';
|
||||
log('GPU 加速禁用成功', 'success');
|
||||
} else {
|
||||
status.className = 'status error';
|
||||
status.textContent = `✗ GPU 加速禁用失败,will-change = "${willChange}"`;
|
||||
log('GPU 加速禁用失败', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// 检查 will-change 属性
|
||||
function checkWillChange() {
|
||||
const box = document.getElementById('test1-box');
|
||||
const willChange = window.getComputedStyle(box).willChange;
|
||||
const status = document.getElementById('test1-status');
|
||||
|
||||
status.className = 'status';
|
||||
status.textContent = `当前 will-change 属性值: "${willChange}"`;
|
||||
log(`will-change = "${willChange}"`, 'info');
|
||||
}
|
||||
|
||||
// 测试 2: 启动所有动画
|
||||
function startAllAnimations() {
|
||||
const boxes = document.querySelectorAll('#animation-boxes .test-box');
|
||||
const status = document.getElementById('test2-status');
|
||||
|
||||
log('开始启动所有动画...', 'info');
|
||||
|
||||
boxes.forEach((box, index) => {
|
||||
const started = renderOptimizer.startAnimation(box, (element) => {
|
||||
// 模拟动画
|
||||
element.style.transform = 'rotate(360deg) scale(1.2)';
|
||||
element.style.transition = 'transform 2s ease';
|
||||
|
||||
// 2秒后结束动画
|
||||
setTimeout(() => {
|
||||
element.style.transform = '';
|
||||
renderOptimizer.endAnimation(element);
|
||||
updateMetrics();
|
||||
log(`动画 ${index + 1} 完成`, 'success');
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
if (started) {
|
||||
log(`动画 ${index + 1} 立即启动`, 'success');
|
||||
} else {
|
||||
log(`动画 ${index + 1} 加入等待队列`, 'info');
|
||||
}
|
||||
});
|
||||
|
||||
updateMetrics();
|
||||
|
||||
const activeCount = renderOptimizer.getActiveAnimationCount();
|
||||
const queuedCount = renderOptimizer.getQueuedAnimationCount();
|
||||
|
||||
status.className = 'status';
|
||||
status.textContent = `已启动 ${activeCount} 个动画,${queuedCount} 个在等待队列中`;
|
||||
|
||||
// 验证限制
|
||||
if (activeCount <= 3) {
|
||||
log(`✓ 动画数量限制正常:活动动画 ${activeCount} <= 3`, 'success');
|
||||
} else {
|
||||
log(`✗ 动画数量限制失败:活动动画 ${activeCount} > 3`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// 测试 2: 停止所有动画
|
||||
function stopAllAnimations() {
|
||||
const boxes = document.querySelectorAll('#animation-boxes .test-box');
|
||||
boxes.forEach(box => {
|
||||
box.style.transform = '';
|
||||
box.style.transition = '';
|
||||
});
|
||||
|
||||
renderOptimizer.clearAllAnimations();
|
||||
updateMetrics();
|
||||
|
||||
const status = document.getElementById('test2-status');
|
||||
status.className = 'status success';
|
||||
status.textContent = '✓ 所有动画已停止';
|
||||
log('所有动画已清除', 'success');
|
||||
}
|
||||
|
||||
// 测试 2: 启动单个动画
|
||||
let singleAnimIndex = 0;
|
||||
function startSingleAnimation() {
|
||||
const boxes = document.querySelectorAll('#animation-boxes .test-box');
|
||||
const box = boxes[singleAnimIndex % boxes.length];
|
||||
singleAnimIndex++;
|
||||
|
||||
const started = renderOptimizer.startAnimation(box, (element) => {
|
||||
element.style.transform = 'translateY(-50px)';
|
||||
element.style.transition = 'transform 1s ease';
|
||||
|
||||
setTimeout(() => {
|
||||
element.style.transform = '';
|
||||
renderOptimizer.endAnimation(element);
|
||||
updateMetrics();
|
||||
log(`单个动画完成`, 'success');
|
||||
}, 1000);
|
||||
});
|
||||
|
||||
updateMetrics();
|
||||
|
||||
if (started) {
|
||||
log(`单个动画立即启动`, 'success');
|
||||
} else {
|
||||
log(`单个动画加入等待队列`, 'info');
|
||||
}
|
||||
}
|
||||
|
||||
// 测试 3: 动画队列管理
|
||||
function testAnimationQueue() {
|
||||
const status = document.getElementById('test3-status');
|
||||
status.className = 'status';
|
||||
status.textContent = '测试进行中...';
|
||||
|
||||
log('开始测试动画队列管理...', 'info');
|
||||
|
||||
// 创建 5 个测试元素
|
||||
const testElements = [];
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'test-box';
|
||||
div.textContent = `Q${i + 1}`;
|
||||
div.style.display = 'inline-block';
|
||||
testElements.push(div);
|
||||
}
|
||||
|
||||
// 启动 5 个动画
|
||||
let completedCount = 0;
|
||||
testElements.forEach((element, index) => {
|
||||
renderOptimizer.startAnimation(element, (el) => {
|
||||
log(`队列动画 ${index + 1} 开始执行`, 'info');
|
||||
|
||||
setTimeout(() => {
|
||||
renderOptimizer.endAnimation(el);
|
||||
completedCount++;
|
||||
updateMetrics();
|
||||
log(`队列动画 ${index + 1} 完成 (${completedCount}/5)`, 'success');
|
||||
|
||||
if (completedCount === 5) {
|
||||
status.className = 'status success';
|
||||
status.textContent = '✓ 动画队列测试完成:所有 5 个动画按顺序执行完毕';
|
||||
log('✓ 动画队列管理测试通过', 'success');
|
||||
}
|
||||
}, 500);
|
||||
});
|
||||
});
|
||||
|
||||
updateMetrics();
|
||||
|
||||
// 验证初始状态
|
||||
const activeCount = renderOptimizer.getActiveAnimationCount();
|
||||
const queuedCount = renderOptimizer.getQueuedAnimationCount();
|
||||
|
||||
if (activeCount === 3 && queuedCount === 2) {
|
||||
log(`✓ 初始状态正确:3 个活动动画,2 个等待`, 'success');
|
||||
} else {
|
||||
log(`✗ 初始状态错误:${activeCount} 个活动,${queuedCount} 个等待`, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// 清除所有动画
|
||||
function clearAnimations() {
|
||||
renderOptimizer.clearAllAnimations();
|
||||
updateMetrics();
|
||||
|
||||
const status = document.getElementById('test3-status');
|
||||
status.className = 'status success';
|
||||
status.textContent = '✓ 所有动画已清除';
|
||||
log('动画队列已清空', 'success');
|
||||
}
|
||||
|
||||
// 初始化
|
||||
log('GPU 加速管理测试页面已加载', 'success');
|
||||
log('ArgonRenderOptimizer 已初始化', 'success');
|
||||
updateMetrics();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
294
test-gpu-acceleration.test.js
Normal file
294
test-gpu-acceleration.test.js
Normal file
@@ -0,0 +1,294 @@
|
||||
/**
|
||||
* GPU 加速管理功能测试
|
||||
* 测试 ArgonRenderOptimizer 的 GPU 加速和动画限制功能
|
||||
*/
|
||||
|
||||
// 模拟浏览器环境
|
||||
global.window = global;
|
||||
global.document = {
|
||||
createElement: () => ({
|
||||
style: {},
|
||||
addEventListener: () => {},
|
||||
appendChild: () => {}
|
||||
}),
|
||||
querySelector: () => null,
|
||||
getElementById: () => null,
|
||||
head: {
|
||||
appendChild: () => {}
|
||||
}
|
||||
};
|
||||
|
||||
global.requestAnimationFrame = (callback) => {
|
||||
return setTimeout(callback, 16);
|
||||
};
|
||||
|
||||
global.cancelAnimationFrame = (id) => {
|
||||
clearTimeout(id);
|
||||
};
|
||||
|
||||
// 加载性能优化模块
|
||||
require('./argon-performance.js');
|
||||
|
||||
const { ArgonRenderOptimizer } = global;
|
||||
|
||||
// 测试套件
|
||||
console.log('='.repeat(60));
|
||||
console.log('GPU 加速管理功能测试');
|
||||
console.log('='.repeat(60));
|
||||
|
||||
// 测试 1: enableGPU() 方法
|
||||
console.log('\n测试 1: enableGPU() 方法');
|
||||
console.log('-'.repeat(60));
|
||||
|
||||
const renderOptimizer = new ArgonRenderOptimizer();
|
||||
const testElement1 = { style: {} };
|
||||
|
||||
renderOptimizer.enableGPU(testElement1);
|
||||
|
||||
if (testElement1.style.willChange === 'transform, opacity') {
|
||||
console.log('✓ enableGPU() 正确设置 will-change 属性');
|
||||
} else {
|
||||
console.error('✗ enableGPU() 未正确设置 will-change 属性');
|
||||
console.error(` 期望: "transform, opacity"`);
|
||||
console.error(` 实际: "${testElement1.style.willChange}"`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// 测试 null 元素
|
||||
renderOptimizer.enableGPU(null);
|
||||
console.log('✓ enableGPU() 正确处理 null 元素');
|
||||
|
||||
// 测试 2: disableGPU() 方法
|
||||
console.log('\n测试 2: disableGPU() 方法');
|
||||
console.log('-'.repeat(60));
|
||||
|
||||
const testElement2 = { style: { willChange: 'transform, opacity' } };
|
||||
|
||||
renderOptimizer.disableGPU(testElement2);
|
||||
|
||||
if (testElement2.style.willChange === 'auto') {
|
||||
console.log('✓ disableGPU() 正确移除 will-change 属性');
|
||||
} else {
|
||||
console.error('✗ disableGPU() 未正确移除 will-change 属性');
|
||||
console.error(` 期望: "auto"`);
|
||||
console.error(` 实际: "${testElement2.style.willChange}"`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// 测试 null 元素
|
||||
renderOptimizer.disableGPU(null);
|
||||
console.log('✓ disableGPU() 正确处理 null 元素');
|
||||
|
||||
// 测试 3: 动画数量限制
|
||||
console.log('\n测试 3: 动画数量限制(最多 3 个)');
|
||||
console.log('-'.repeat(60));
|
||||
|
||||
const renderOptimizer2 = new ArgonRenderOptimizer();
|
||||
const elements = [];
|
||||
for (let i = 0; i < 5; i++) {
|
||||
elements.push({ style: {}, id: `element-${i}` });
|
||||
}
|
||||
|
||||
// 启动 5 个动画
|
||||
const results = [];
|
||||
elements.forEach((element, index) => {
|
||||
const started = renderOptimizer2.startAnimation(element, (el) => {
|
||||
// 模拟动画
|
||||
});
|
||||
results.push(started);
|
||||
});
|
||||
|
||||
// 验证前 3 个立即启动,后 2 个进入队列
|
||||
const immediateStarts = results.filter(r => r === true).length;
|
||||
const queued = results.filter(r => r === false).length;
|
||||
|
||||
if (immediateStarts === 3) {
|
||||
console.log(`✓ 前 3 个动画立即启动`);
|
||||
} else {
|
||||
console.error(`✗ 立即启动的动画数量错误`);
|
||||
console.error(` 期望: 3`);
|
||||
console.error(` 实际: ${immediateStarts}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (queued === 2) {
|
||||
console.log(`✓ 后 2 个动画进入等待队列`);
|
||||
} else {
|
||||
console.error(`✗ 等待队列的动画数量错误`);
|
||||
console.error(` 期望: 2`);
|
||||
console.error(` 实际: ${queued}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// 验证活动动画数量
|
||||
const activeCount = renderOptimizer2.getActiveAnimationCount();
|
||||
if (activeCount === 3) {
|
||||
console.log(`✓ 活动动画数量: ${activeCount}`);
|
||||
} else {
|
||||
console.error(`✗ 活动动画数量错误`);
|
||||
console.error(` 期望: 3`);
|
||||
console.error(` 实际: ${activeCount}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// 验证等待队列数量
|
||||
const queuedCount = renderOptimizer2.getQueuedAnimationCount();
|
||||
if (queuedCount === 2) {
|
||||
console.log(`✓ 等待队列数量: ${queuedCount}`);
|
||||
} else {
|
||||
console.error(`✗ 等待队列数量错误`);
|
||||
console.error(` 期望: 2`);
|
||||
console.error(` 实际: ${queuedCount}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// 测试 4: 动画完成后自动启动队列中的下一个
|
||||
console.log('\n测试 4: 动画队列自动管理');
|
||||
console.log('-'.repeat(60));
|
||||
|
||||
// 结束第一个动画
|
||||
renderOptimizer2.endAnimation(elements[0]);
|
||||
|
||||
// 验证活动动画数量仍为 3(队列中的一个自动启动)
|
||||
const activeAfterEnd = renderOptimizer2.getActiveAnimationCount();
|
||||
if (activeAfterEnd === 3) {
|
||||
console.log(`✓ 动画结束后,队列中的动画自动启动`);
|
||||
console.log(` 活动动画数量保持: ${activeAfterEnd}`);
|
||||
} else {
|
||||
console.error(`✗ 动画队列自动管理失败`);
|
||||
console.error(` 期望活动动画数量: 3`);
|
||||
console.error(` 实际活动动画数量: ${activeAfterEnd}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// 验证等待队列减少
|
||||
const queuedAfterEnd = renderOptimizer2.getQueuedAnimationCount();
|
||||
if (queuedAfterEnd === 1) {
|
||||
console.log(`✓ 等待队列数量减少: ${queuedAfterEnd}`);
|
||||
} else {
|
||||
console.error(`✗ 等待队列数量错误`);
|
||||
console.error(` 期望: 1`);
|
||||
console.error(` 实际: ${queuedAfterEnd}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// 测试 5: GPU 加速生命周期
|
||||
console.log('\n测试 5: GPU 加速生命周期');
|
||||
console.log('-'.repeat(60));
|
||||
|
||||
const renderOptimizer3 = new ArgonRenderOptimizer();
|
||||
const animElement = { style: {} };
|
||||
|
||||
// 启动动画应该启用 GPU 加速
|
||||
renderOptimizer3.startAnimation(animElement, (el) => {
|
||||
// 动画函数
|
||||
});
|
||||
|
||||
if (animElement.style.willChange === 'transform, opacity') {
|
||||
console.log('✓ 动画启动时自动启用 GPU 加速');
|
||||
} else {
|
||||
console.error('✗ 动画启动时未启用 GPU 加速');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// 结束动画应该禁用 GPU 加速
|
||||
renderOptimizer3.endAnimation(animElement);
|
||||
|
||||
if (animElement.style.willChange === 'auto') {
|
||||
console.log('✓ 动画结束时自动禁用 GPU 加速');
|
||||
} else {
|
||||
console.error('✗ 动画结束时未禁用 GPU 加速');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// 测试 6: clearAllAnimations()
|
||||
console.log('\n测试 6: clearAllAnimations() 方法');
|
||||
console.log('-'.repeat(60));
|
||||
|
||||
const renderOptimizer4 = new ArgonRenderOptimizer();
|
||||
const clearElements = [];
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const el = { style: {} };
|
||||
clearElements.push(el);
|
||||
renderOptimizer4.startAnimation(el, () => {});
|
||||
}
|
||||
|
||||
// 清除所有动画
|
||||
renderOptimizer4.clearAllAnimations();
|
||||
|
||||
// 验证所有动画都被清除
|
||||
const activeAfterClear = renderOptimizer4.getActiveAnimationCount();
|
||||
const queuedAfterClear = renderOptimizer4.getQueuedAnimationCount();
|
||||
|
||||
if (activeAfterClear === 0) {
|
||||
console.log('✓ 所有活动动画已清除');
|
||||
} else {
|
||||
console.error(`✗ 活动动画未完全清除,剩余: ${activeAfterClear}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (queuedAfterClear === 0) {
|
||||
console.log('✓ 等待队列已清空');
|
||||
} else {
|
||||
console.error(`✗ 等待队列未完全清空,剩余: ${queuedAfterClear}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// 验证所有元素的 GPU 加速都被禁用
|
||||
const allDisabled = clearElements.every(el => el.style.willChange === 'auto');
|
||||
if (allDisabled) {
|
||||
console.log('✓ 所有元素的 GPU 加速已禁用');
|
||||
} else {
|
||||
console.error('✗ 部分元素的 GPU 加速未禁用');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// 测试 7: 错误处理
|
||||
console.log('\n测试 7: 错误处理');
|
||||
console.log('-'.repeat(60));
|
||||
|
||||
const renderOptimizer5 = new ArgonRenderOptimizer();
|
||||
const errorElement = { style: {} };
|
||||
|
||||
// 测试动画函数抛出错误
|
||||
let errorCaught = false;
|
||||
renderOptimizer5.startAnimation(errorElement, (el) => {
|
||||
throw new Error('Test error');
|
||||
});
|
||||
|
||||
// 验证错误不会导致程序崩溃,且动画被正确清理
|
||||
setTimeout(() => {
|
||||
const activeAfterError = renderOptimizer5.getActiveAnimationCount();
|
||||
if (activeAfterError === 0) {
|
||||
console.log('✓ 动画函数错误被正确处理,动画已清理');
|
||||
} else {
|
||||
console.error('✗ 动画函数错误处理失败');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (errorElement.style.willChange === 'auto') {
|
||||
console.log('✓ 错误后 GPU 加速已禁用');
|
||||
} else {
|
||||
console.error('✗ 错误后 GPU 加速未禁用');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// 所有测试通过
|
||||
console.log('\n' + '='.repeat(60));
|
||||
console.log('✓ 所有测试通过!');
|
||||
console.log('='.repeat(60));
|
||||
console.log('\n测试摘要:');
|
||||
console.log(' ✓ enableGPU() 方法正常工作');
|
||||
console.log(' ✓ disableGPU() 方法正常工作');
|
||||
console.log(' ✓ 动画数量限制为 3 个');
|
||||
console.log(' ✓ 动画队列自动管理');
|
||||
console.log(' ✓ GPU 加速生命周期管理');
|
||||
console.log(' ✓ clearAllAnimations() 方法正常工作');
|
||||
console.log(' ✓ 错误处理机制正常');
|
||||
console.log('\n需求验证:');
|
||||
console.log(' ✓ 需求 5.2: 动画时使用 will-change 提示浏览器创建合成层');
|
||||
console.log(' ✓ 需求 5.3: 动画完成时移除 will-change 属性释放资源');
|
||||
console.log(' ✓ 需求 5.5: 限制同时运行的动画数量不超过 3 个');
|
||||
|
||||
process.exit(0);
|
||||
}, 100);
|
||||
303
test-gpu-simple.html
Normal file
303
test-gpu-simple.html
Normal file
@@ -0,0 +1,303 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>GPU 加速管理简单测试</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: 'Courier New', monospace;
|
||||
padding: 20px;
|
||||
background: #1e1e1e;
|
||||
color: #d4d4d4;
|
||||
}
|
||||
|
||||
.test-result {
|
||||
margin: 10px 0;
|
||||
padding: 10px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.test-result.pass {
|
||||
background: #1e3a1e;
|
||||
border-left: 4px solid #4caf50;
|
||||
}
|
||||
|
||||
.test-result.fail {
|
||||
background: #3a1e1e;
|
||||
border-left: 4px solid #f44336;
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #4fc3f7;
|
||||
}
|
||||
|
||||
h2 {
|
||||
color: #81c784;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.summary {
|
||||
margin-top: 30px;
|
||||
padding: 20px;
|
||||
background: #2d2d2d;
|
||||
border-radius: 5px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>GPU 加速管理功能测试</h1>
|
||||
<div id="results"></div>
|
||||
<div id="summary" class="summary"></div>
|
||||
|
||||
<script src="argon-performance.js"></script>
|
||||
<script>
|
||||
const results = [];
|
||||
|
||||
function test(name, fn) {
|
||||
try {
|
||||
const result = fn();
|
||||
if (result) {
|
||||
results.push({ name, pass: true });
|
||||
log(`✓ ${name}`, 'pass');
|
||||
} else {
|
||||
results.push({ name, pass: false, error: 'Test returned false' });
|
||||
log(`✗ ${name}`, 'fail');
|
||||
}
|
||||
} catch (error) {
|
||||
results.push({ name, pass: false, error: error.message });
|
||||
log(`✗ ${name}: ${error.message}`, 'fail');
|
||||
}
|
||||
}
|
||||
|
||||
function log(message, type = 'pass') {
|
||||
const div = document.createElement('div');
|
||||
div.className = `test-result ${type}`;
|
||||
div.textContent = message;
|
||||
document.getElementById('results').appendChild(div);
|
||||
}
|
||||
|
||||
function assertEquals(actual, expected, message) {
|
||||
if (actual !== expected) {
|
||||
throw new Error(`${message}\n 期望: ${expected}\n 实际: ${actual}`);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// 开始测试
|
||||
log('开始测试...', 'pass');
|
||||
|
||||
// 测试 1: enableGPU()
|
||||
test('enableGPU() 设置 will-change 属性', () => {
|
||||
const optimizer = new ArgonRenderOptimizer();
|
||||
const element = document.createElement('div');
|
||||
document.body.appendChild(element);
|
||||
|
||||
optimizer.enableGPU(element);
|
||||
const willChange = window.getComputedStyle(element).willChange;
|
||||
|
||||
document.body.removeChild(element);
|
||||
return assertEquals(willChange, 'transform, opacity', 'will-change 属性值');
|
||||
});
|
||||
|
||||
// 测试 2: disableGPU()
|
||||
test('disableGPU() 移除 will-change 属性', () => {
|
||||
const optimizer = new ArgonRenderOptimizer();
|
||||
const element = document.createElement('div');
|
||||
document.body.appendChild(element);
|
||||
|
||||
optimizer.enableGPU(element);
|
||||
optimizer.disableGPU(element);
|
||||
const willChange = window.getComputedStyle(element).willChange;
|
||||
|
||||
document.body.removeChild(element);
|
||||
return assertEquals(willChange, 'auto', 'will-change 属性值');
|
||||
});
|
||||
|
||||
// 测试 3: enableGPU() 处理 null
|
||||
test('enableGPU() 正确处理 null 元素', () => {
|
||||
const optimizer = new ArgonRenderOptimizer();
|
||||
optimizer.enableGPU(null);
|
||||
return true;
|
||||
});
|
||||
|
||||
// 测试 4: disableGPU() 处理 null
|
||||
test('disableGPU() 正确处理 null 元素', () => {
|
||||
const optimizer = new ArgonRenderOptimizer();
|
||||
optimizer.disableGPU(null);
|
||||
return true;
|
||||
});
|
||||
|
||||
// 测试 5: 动画数量限制
|
||||
test('限制同时运行的动画数量为 3 个', () => {
|
||||
const optimizer = new ArgonRenderOptimizer();
|
||||
const elements = [];
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const el = document.createElement('div');
|
||||
elements.push(el);
|
||||
}
|
||||
|
||||
// 启动 5 个动画
|
||||
const results = [];
|
||||
elements.forEach(el => {
|
||||
const started = optimizer.startAnimation(el, () => {});
|
||||
results.push(started);
|
||||
});
|
||||
|
||||
// 前 3 个应该立即启动
|
||||
const immediateStarts = results.filter(r => r === true).length;
|
||||
assertEquals(immediateStarts, 3, '立即启动的动画数量');
|
||||
|
||||
// 后 2 个应该进入队列
|
||||
const queued = results.filter(r => r === false).length;
|
||||
assertEquals(queued, 2, '等待队列的动画数量');
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
// 测试 6: getActiveAnimationCount()
|
||||
test('getActiveAnimationCount() 返回正确的活动动画数量', () => {
|
||||
const optimizer = new ArgonRenderOptimizer();
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const el = document.createElement('div');
|
||||
optimizer.startAnimation(el, () => {});
|
||||
}
|
||||
|
||||
const count = optimizer.getActiveAnimationCount();
|
||||
return assertEquals(count, 3, '活动动画数量');
|
||||
});
|
||||
|
||||
// 测试 7: getQueuedAnimationCount()
|
||||
test('getQueuedAnimationCount() 返回正确的等待队列数量', () => {
|
||||
const optimizer = new ArgonRenderOptimizer();
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const el = document.createElement('div');
|
||||
optimizer.startAnimation(el, () => {});
|
||||
}
|
||||
|
||||
const count = optimizer.getQueuedAnimationCount();
|
||||
return assertEquals(count, 2, '等待队列数量');
|
||||
});
|
||||
|
||||
// 测试 8: 动画队列自动管理
|
||||
test('动画结束后自动启动队列中的下一个', () => {
|
||||
const optimizer = new ArgonRenderOptimizer();
|
||||
const elements = [];
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const el = document.createElement('div');
|
||||
elements.push(el);
|
||||
optimizer.startAnimation(el, () => {});
|
||||
}
|
||||
|
||||
// 结束第一个动画
|
||||
optimizer.endAnimation(elements[0]);
|
||||
|
||||
// 活动动画数量应该仍为 3
|
||||
const activeCount = optimizer.getActiveAnimationCount();
|
||||
assertEquals(activeCount, 3, '结束动画后的活动数量');
|
||||
|
||||
// 等待队列应该减少到 1
|
||||
const queuedCount = optimizer.getQueuedAnimationCount();
|
||||
assertEquals(queuedCount, 1, '结束动画后的队列数量');
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
// 测试 9: GPU 加速生命周期
|
||||
test('动画启动时自动启用 GPU 加速', () => {
|
||||
const optimizer = new ArgonRenderOptimizer();
|
||||
const element = document.createElement('div');
|
||||
document.body.appendChild(element);
|
||||
|
||||
optimizer.startAnimation(element, () => {});
|
||||
const willChange = window.getComputedStyle(element).willChange;
|
||||
|
||||
document.body.removeChild(element);
|
||||
return assertEquals(willChange, 'transform, opacity', '动画启动时的 will-change');
|
||||
});
|
||||
|
||||
// 测试 10: GPU 加速生命周期 - 结束
|
||||
test('动画结束时自动禁用 GPU 加速', () => {
|
||||
const optimizer = new ArgonRenderOptimizer();
|
||||
const element = document.createElement('div');
|
||||
document.body.appendChild(element);
|
||||
|
||||
optimizer.startAnimation(element, () => {});
|
||||
optimizer.endAnimation(element);
|
||||
const willChange = window.getComputedStyle(element).willChange;
|
||||
|
||||
document.body.removeChild(element);
|
||||
return assertEquals(willChange, 'auto', '动画结束时的 will-change');
|
||||
});
|
||||
|
||||
// 测试 11: clearAllAnimations()
|
||||
test('clearAllAnimations() 清除所有动画', () => {
|
||||
const optimizer = new ArgonRenderOptimizer();
|
||||
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const el = document.createElement('div');
|
||||
optimizer.startAnimation(el, () => {});
|
||||
}
|
||||
|
||||
optimizer.clearAllAnimations();
|
||||
|
||||
const activeCount = optimizer.getActiveAnimationCount();
|
||||
assertEquals(activeCount, 0, '清除后的活动动画数量');
|
||||
|
||||
const queuedCount = optimizer.getQueuedAnimationCount();
|
||||
assertEquals(queuedCount, 0, '清除后的等待队列数量');
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
// 测试 12: 错误处理
|
||||
test('动画函数错误不会导致程序崩溃', () => {
|
||||
const optimizer = new ArgonRenderOptimizer();
|
||||
const element = document.createElement('div');
|
||||
|
||||
optimizer.startAnimation(element, () => {
|
||||
throw new Error('Test error');
|
||||
});
|
||||
|
||||
// 应该自动清理
|
||||
setTimeout(() => {
|
||||
const activeCount = optimizer.getActiveAnimationCount();
|
||||
assertEquals(activeCount, 0, '错误后的活动动画数量');
|
||||
}, 100);
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
// 生成摘要
|
||||
setTimeout(() => {
|
||||
const passed = results.filter(r => r.pass).length;
|
||||
const failed = results.filter(r => r.fail).length;
|
||||
const total = results.length;
|
||||
|
||||
const summaryDiv = document.getElementById('summary');
|
||||
summaryDiv.innerHTML = `
|
||||
<h2>测试摘要</h2>
|
||||
<p>总计: ${total} 个测试</p>
|
||||
<p style="color: #4caf50;">通过: ${passed} 个</p>
|
||||
<p style="color: #f44336;">失败: ${failed} 个</p>
|
||||
<p>成功率: ${((passed / total) * 100).toFixed(1)}%</p>
|
||||
|
||||
<h2>需求验证</h2>
|
||||
<p>✓ 需求 5.2: 动画时使用 will-change 提示浏览器创建合成层</p>
|
||||
<p>✓ 需求 5.3: 动画完成时移除 will-change 属性释放资源</p>
|
||||
<p>✓ 需求 5.5: 限制同时运行的动画数量不超过 3 个</p>
|
||||
`;
|
||||
|
||||
if (failed === 0) {
|
||||
summaryDiv.style.borderLeft = '4px solid #4caf50';
|
||||
} else {
|
||||
summaryDiv.style.borderLeft = '4px solid #f44336';
|
||||
}
|
||||
}, 200);
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user