诸暨市中国转运服务网

前端JavaScript监听浏览器中所有资源的加载进度的方法与实践

2026-03-31 21:04:04 浏览次数:2
详细信息

一、主要实现方案

1. Performance API(推荐)

// 监听页面所有资源加载完成
window.addEventListener('load', function() {
    const resources = performance.getEntriesByType('resource');
    const totalResources = resources.length;

    resources.forEach(resource => {
        console.log(`资源: ${resource.name}`);
        console.log(`加载耗时: ${resource.duration}ms`);
        console.log(`类型: ${resource.initiatorType}`);
    });
});

// 实时监控资源加载
const observer = new PerformanceObserver((list) => {
    list.getEntries().forEach(entry => {
        console.log(`${entry.name} 加载耗时: ${entry.duration}ms`);
    });
});
observer.observe({ entryTypes: ['resource'] });

2. 自定义资源监控器

class ResourceProgressTracker {
    constructor() {
        this.resources = new Map();
        this.totalResources = 0;
        this.loadedResources = 0;
        this.totalSize = 0;
        this.loadedSize = 0;
        this.callbacks = {
            onProgress: null,
            onComplete: null
        };
    }

    start() {
        // 监听资源加载
        const originalFetch = window.fetch;
        window.fetch = function(...args) {
            const startTime = Date.now();
            const url = typeof args[0] === 'string' ? args[0] : args[0].url;

            return originalFetch.apply(this, args).then(response => {
                const clone = response.clone();
                clone.blob().then(blob => {
                    this._trackResource({
                        url,
                        size: blob.size,
                        type: 'fetch',
                        duration: Date.now() - startTime
                    });
                });
                return response;
            });
        }.bind(this);

        // 监控图片
        const originalImage = Image;
        window.Image = function() {
            const img = new originalImage();
            const _src = img.src;

            Object.defineProperty(img, 'src', {
                set(value) {
                    const startTime = Date.now();
                    _src.call(img, value);

                    img.onload = () => {
                        this._trackResource({
                            url: value,
                            type: 'image',
                            duration: Date.now() - startTime
                        });
                    }.bind(this);

                    img.onerror = () => {
                        this._trackResource({
                            url: value,
                            type: 'image',
                            error: true,
                            duration: Date.now() - startTime
                        });
                    }.bind(this);
                }
            });
            return img;
        }.bind(this);
    }

    _trackResource(info) {
        const resourceId = Date.now() + Math.random();
        this.resources.set(resourceId, info);
        this.totalResources++;

        if (info.size) {
            this.totalSize += info.size;
            this.loadedSize += info.size;
        }

        this.loadedResources++;
        this._updateProgress();
    }

    _updateProgress() {
        if (this.callbacks.onProgress) {
            const progress = {
                loaded: this.loadedResources,
                total: this.totalResources,
                percentage: (this.loadedResources / this.totalResources * 100).toFixed(2),
                loadedSize: this.loadedSize,
                totalSize: this.totalSize
            };
            this.callbacks.onProgress(progress);
        }
    }

    onProgress(callback) {
        this.callbacks.onProgress = callback;
    }

    onComplete(callback) {
        this.callbacks.onComplete = callback;
    }
}

// 使用示例
const tracker = new ResourceProgressTracker();
tracker.onProgress((progress) => {
    console.log(`加载进度: ${progress.percentage}%`);
    console.log(`已加载: ${progress.loaded}/${progress.total}`);
});
tracker.start();

3. MutationObserver + 资源事件监听

function monitorAllResources() {
    let total = 0;
    let loaded = 0;
    const resources = new Set();

    // 监听DOM变化,发现新资源
    const observer = new MutationObserver((mutations) => {
        mutations.forEach(mutation => {
            if (mutation.type === 'childList') {
                mutation.addedNodes.forEach(node => {
                    if (node.nodeType === Node.ELEMENT_NODE) {
                        findResources(node);
                    }
                });
            }
        });
    });

    // 查找元素中的资源
    function findResources(element) {
        const resourceAttrs = {
            img: 'src',
            script: 'src',
            link: 'href',
            iframe: 'src',
            audio: 'src',
            video: 'src',
            source: 'src',
            embed: 'src',
            object: 'data'
        };

        // 检查当前元素
        const tagName = element.tagName.toLowerCase();
        if (resourceAttrs[tagName]) {
            const src = element.getAttribute(resourceAttrs[tagName]);
            if (src) {
                addResource(src, tagName, element);
            }
        }

        // 检查背景图片
        const bg = window.getComputedStyle(element).backgroundImage;
        if (bg && bg !== 'none') {
            const url = bg.match(/url\(['"]?(.*?)['"]?\)/);
            if (url && url[1]) {
                addResource(url[1], 'background', element);
            }
        }

        // 递归检查子元素
        element.querySelectorAll('*').forEach(child => {
            findResources(child);
        });
    }

    function addResource(url, type, element) {
        if (!resources.has(url)) {
            resources.add(url);
            total++;

            const resource = document.createElement('img');
            resource.onload = () => {
                loaded++;
                updateProgress();
            };
            resource.onerror = () => {
                loaded++;
                updateProgress();
            };
            resource.src = url;
        }
    }

    function updateProgress() {
        const progress = {
            loaded,
            total,
            percentage: (loaded / total * 100).toFixed(2)
        };

        console.log('资源加载进度:', progress);

        // 派发自定义事件
        window.dispatchEvent(new CustomEvent('resourceProgress', {
            detail: progress
        }));

        if (loaded === total) {
            console.log('所有资源加载完成');
            window.dispatchEvent(new Event('allResourcesLoaded'));
        }
    }

    // 开始监听
    observer.observe(document.body, {
        childList: true,
        subtree: true
    });

    // 初始扫描
    findResources(document.body);

    return {
        getProgress: () => ({
            loaded,
            total,
            percentage: total > 0 ? (loaded / total * 100).toFixed(2) : 0
        })
    };
}

二、实践建议

1. 组合使用方案

class ComprehensiveResourceMonitor {
    constructor() {
        this.progress = {
            resources: 0,
            loaded: 0,
            size: { total: 0, loaded: 0 }
        };

        this.init();
    }

    init() {
        // 1. 使用Performance API获取已有资源
        this._initPerformanceAPI();

        // 2. 监听动态添加的资源
        this._initMutationObserver();

        // 3. 劫持资源加载方法
        this._interceptResourceLoading();
    }

    _initPerformanceAPI() {
        if (performance && performance.getEntriesByType) {
            const resources = performance.getEntriesByType('resource');
            this.progress.resources = resources.length;
            this.progress.loaded = resources.filter(r => r.duration > 0).length;
        }
    }

    _initMutationObserver() {
        this.observer = new MutationObserver((mutations) => {
            mutations.forEach(mutation => {
                mutation.addedNodes.forEach(node => {
                    this._scanForResources(node);
                });
            });
        });

        this.observer.observe(document.documentElement, {
            childList: true,
            subtree: true
        });
    }

    _interceptResourceLoading() {
        // 劫持fetch
        const originalFetch = window.fetch;
        window.fetch = (...args) => {
            const start = performance.now();
            return originalFetch(...args).then(response => {
                this._trackResource('fetch', args[0], performance.now() - start);
                return response;
            });
        };

        // 劫持Image
        const OriginalImage = window.Image;
        window.Image = class extends OriginalImage {
            constructor() {
                super();
                const start = performance.now();
                let srcValue = '';

                Object.defineProperty(this, 'src', {
                    get() { return srcValue; },
                    set(value) {
                        srcValue = value;
                        super.src = value;

                        this.onload = () => {
                            this._trackResource('image', value, performance.now() - start);
                        };
                    }
                });
            }
        };
    }
}

2. Vue/React组件集成示例

// React示例
import React, { useState, useEffect } from 'react';

const ResourceProgress = () => {
    const [progress, setProgress] = useState({ loaded: 0, total: 0, percentage: 0 });

    useEffect(() => {
        const tracker = new ResourceProgressTracker();

        tracker.onProgress(setProgress);
        tracker.start();

        return () => {
            // 清理
        };
    }, []);

    return (
        <div className="resource-progress">
            <div className="progress-bar">
                <div 
                    className="progress-fill" 
                    style={{ width: `${progress.percentage}%` }}
                />
            </div>
            <div className="progress-text">
                资源加载: {progress.loaded}/{progress.total} ({progress.percentage}%)
            </div>
        </div>
    );
};

三、注意事项

性能影响:过度监控可能影响页面性能 CORS限制:跨域资源可能无法获取准确大小 动态内容:SPA应用需要特殊处理动态加载的内容 内存泄漏:及时清理监听器和引用 浏览器兼容性:注意API兼容性,提供降级方案

四、优化建议

使用防抖或节流限制进度更新频率 对关键资源(首屏资源)进行优先监控 提供详细的错误处理机制 考虑使用Service Worker进行更底层的资源控制 集成到现有的性能监控系统中

这些方法可以根据实际需求组合使用,提供全面的资源加载监控能力。

相关推荐