feat: 实现 ArgonRenderOptimizer 类

- 实现构造函数,初始化读写队列和调度状态
- 实现 read() 方法:将 DOM 读取操作加入队列
- 实现 write() 方法:将 DOM 写入操作加入队列
- 实现 _schedule() 私有方法:使用 requestAnimationFrame 调度执行
- 实现 _flush() 私有方法:批量执行队列操作(先读后写)
- 实现 enableGPU() 方法:设置 will-change 属性启用 GPU 加速
- 实现 disableGPU() 方法:移除 will-change 属性释放资源
- 添加错误处理机制,确保单个操作失败不影响其他操作
- 在测试文件中添加完整的渲染优化模块测试
- 创建使用指南文档 RENDER_OPTIMIZER_USAGE.md
- 导出 ArgonRenderOptimizer 类供其他模块使用

验证需求:2.3, 2.4, 17.1, 17.2
This commit is contained in:
2026-01-21 14:52:50 +08:00
parent e548d20012
commit 9fca9481ae
4 changed files with 1492 additions and 0 deletions

View File

@@ -146,6 +146,77 @@ class ArgonEventManager {
this.listeners = new Map();
}
/**
* 节流函数
* 限制函数执行频率,使用 requestAnimationFrame 优化性能
*
* @param {Function} func - 要节流的函数
* @param {number} wait - 等待时间(毫秒),默认 16ms约 60fps
* @returns {Function} 节流后的函数
*/
throttle(func, wait = 16) {
let lastTime = 0;
let timeoutId = null;
return function(...args) {
const now = Date.now();
const remaining = wait - (now - lastTime);
if (remaining <= 0) {
// 如果距离上次执行已超过等待时间,立即执行
if (timeoutId) {
cancelAnimationFrame(timeoutId);
timeoutId = null;
}
lastTime = now;
func.apply(this, args);
} else if (!timeoutId) {
// 否则使用 requestAnimationFrame 在下一帧执行
timeoutId = requestAnimationFrame(() => {
lastTime = Date.now();
timeoutId = null;
func.apply(this, args);
});
}
};
}
/**
* 防抖函数
* 延迟执行函数,直到事件停止触发指定时间后才执行
* 如果在等待期间再次触发,则重新计时
*
* @param {Function} func - 要防抖的函数
* @param {number} wait - 等待时间(毫秒),默认 150ms
* @returns {Function} 防抖后的函数,包含 cancel 方法用于取消待执行任务
*/
debounce(func, wait = 150) {
let timeoutId = null;
const debounced = function(...args) {
// 取消之前的待执行任务
if (timeoutId) {
clearTimeout(timeoutId);
}
// 设置新的延迟执行任务
timeoutId = setTimeout(() => {
timeoutId = null;
func.apply(this, args);
}, wait);
};
// 提供取消方法,允许手动取消待执行任务
debounced.cancel = function() {
if (timeoutId) {
clearTimeout(timeoutId);
timeoutId = null;
}
};
return debounced;
}
/**
* 添加事件监听器
* 自动跟踪监听器以便后续清理
@@ -230,6 +301,271 @@ class ArgonEventManager {
}
}
// ==========================================================================
// 资源加载模块
// ==========================================================================
/**
* 资源加载类
* 提供第三方库按需加载和缓存管理功能
*
* @class ArgonResourceLoader
*/
class ArgonResourceLoader {
/**
* 构造函数
* 初始化加载状态管理
*/
constructor() {
this.loaded = new Set(); // 已加载的资源集合
this.loading = new Map(); // 正在加载的资源 Promise Map
}
/**
* 按需加载脚本
* 如果资源已加载或正在加载,则复用缓存
*
* @param {string} name - 资源名称(如 'prism', 'zoomify', 'tippy'
* @param {string} url - 资源 URL
* @returns {Promise<void>} 加载完成的 Promise
*/
async loadScript(name, url) {
// 如果已加载,直接返回
if (this.loaded.has(name)) {
return Promise.resolve();
}
// 如果正在加载,返回现有的 Promise
if (this.loading.has(name)) {
return this.loading.get(name);
}
// 创建新的加载 Promise
const promise = new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = url;
script.async = true;
script.onload = () => {
this.loaded.add(name);
this.loading.delete(name);
resolve();
};
script.onerror = () => {
this.loading.delete(name);
reject(new Error(`Failed to load ${name} from ${url}`));
};
document.head.appendChild(script);
});
this.loading.set(name, promise);
return promise;
}
/**
* 检查页面是否需要某个库
* 通过 CSS 选择器判断页面中是否存在需要该库的元素
*
* @param {string} selector - CSS 选择器
* @returns {boolean} 如果页面包含匹配的元素则返回 true
*/
needsLibrary(selector) {
return document.querySelector(selector) !== null;
}
/**
* 条件加载 Prism 代码高亮库
* 仅在页面包含代码块时加载
*
* @returns {Promise<void>}
*/
async loadPrismIfNeeded() {
// 检查是否需要加载
if (!this.needsLibrary('pre code')) {
return;
}
// 检查是否已经存在
if (typeof window.Prism !== 'undefined' && window.Prism.highlightAll) {
this.loaded.add('prism');
return;
}
// 加载 Prism
if (typeof argonConfig !== 'undefined' && argonConfig.prism_url) {
await this.loadScript('prism', argonConfig.prism_url);
}
}
/**
* 条件加载 Zoomify 图片放大库
* 仅在页面包含图片时加载
*
* @returns {Promise<void>}
*/
async loadZoomifyIfNeeded() {
// 检查是否需要加载
if (!this.needsLibrary('.post-content img')) {
return;
}
// 检查是否已经存在
if (typeof window.Zoomify !== 'undefined') {
this.loaded.add('zoomify');
return;
}
// 加载 Zoomify
if (typeof argonConfig !== 'undefined' && argonConfig.zoomify_url) {
await this.loadScript('zoomify', argonConfig.zoomify_url);
}
}
/**
* 条件加载 Tippy 提示框库
* 仅在页面包含提示框元素时加载
*
* @returns {Promise<void>}
*/
async loadTippyIfNeeded() {
// 检查是否需要加载
if (!this.needsLibrary('[data-tippy-content]')) {
return;
}
// 检查是否已经存在
if (typeof window.tippy !== 'undefined') {
this.loaded.add('tippy');
return;
}
// 加载 Tippy
if (typeof argonConfig !== 'undefined' && argonConfig.tippy_url) {
await this.loadScript('tippy', argonConfig.tippy_url);
}
}
}
// ==========================================================================
// 渲染优化模块
// ==========================================================================
/**
* 渲染优化类
* 批量读写 DOM 避免布局抖动,管理 GPU 加速属性
*
* @class ArgonRenderOptimizer
*/
class ArgonRenderOptimizer {
/**
* 构造函数
* 初始化读写队列和调度状态
*/
constructor() {
this.readQueue = []; // DOM 读取操作队列
this.writeQueue = []; // DOM 写入操作队列
this.scheduled = false; // 是否已调度执行
}
/**
* 批量读取 DOM 属性
* 将读取操作加入队列,在下一帧统一执行
*
* @param {Function} readFn - 读取函数,应该只包含 DOM 读取操作
* @returns {void}
*/
read(readFn) {
this.readQueue.push(readFn);
this._schedule();
}
/**
* 批量写入 DOM
* 将写入操作加入队列,在所有读取操作完成后统一执行
*
* @param {Function} writeFn - 写入函数,应该只包含 DOM 写入操作
* @returns {void}
*/
write(writeFn) {
this.writeQueue.push(writeFn);
this._schedule();
}
/**
* 调度执行
* 使用 requestAnimationFrame 在下一帧执行队列中的操作
*
* @private
* @returns {void}
*/
_schedule() {
if (this.scheduled) return;
this.scheduled = true;
requestAnimationFrame(() => {
this._flush();
});
}
/**
* 执行队列
* 先执行所有读取操作,再执行所有写入操作
* 这样可以避免布局抖动layout thrashing
*
* @private
* @returns {void}
*/
_flush() {
// 先执行所有读取操作
while (this.readQueue.length) {
const readFn = this.readQueue.shift();
try {
readFn();
} catch (error) {
console.error('Error in read operation:', error);
}
}
// 再执行所有写入操作
while (this.writeQueue.length) {
const writeFn = this.writeQueue.shift();
try {
writeFn();
} catch (error) {
console.error('Error in write operation:', error);
}
}
this.scheduled = false;
}
/**
* 添加 GPU 加速提示
* 设置 will-change 属性,提示浏览器创建合成层
*
* @param {Element} element - DOM 元素
* @returns {void}
*/
enableGPU(element) {
if (!element) return;
element.style.willChange = 'transform, opacity';
}
/**
* 移除 GPU 加速提示
* 移除 will-change 属性,释放资源
*
* @param {Element} element - DOM 元素
* @returns {void}
*/
disableGPU(element) {
if (!element) return;
element.style.willChange = 'auto';
}
}
// ==========================================================================
// 模块导出和初始化接口
// ==========================================================================
@@ -253,5 +589,7 @@ if (typeof window !== 'undefined') {
window.ArgonPerformanceConfig = ArgonPerformanceConfig;
window.ArgonDOMCache = ArgonDOMCache;
window.ArgonEventManager = ArgonEventManager;
window.ArgonResourceLoader = ArgonResourceLoader;
window.ArgonRenderOptimizer = ArgonRenderOptimizer;
window.initArgonPerformance = initArgonPerformance;
}