预加载的实现方式

 最近在工作中遇到需要使用到资源预加载的场景,记录一下预加载的实现过程和预加载主要的几种实现方式。

方法1:纯 JavaScript 动态加载

这种方式是平时使用的最频繁的方式,通过创建Image对象,将图片路径赋值给src属性,浏览器就会开始加载图片。

function preloadImage(url) {
  const img = new Image();
  img.src = url;
}

遇到需要预加载多张图片时:

function preloadImage(url) {
  const img = new Image();
  img.src = url;
}
const imageUrls = [
  'image1.jpg',
  'image2.jpg',
  // ... 更多图片
];
imageUrls.forEach(url => preloadImage(url));

这种方式最为,简单易用,而且Image对象提供了一种简单的方式来创建图像元素并操作图像,支持 onload(加载成功)、onerror(加载失败)等事件。例如,可以设置src属性来加载图像,并监听load事件来处理加载完成后的操作。

   const img = new Image();
   img.src = 'path/to/image.jpg';
   img.onload = function() {
     console.log('Image loaded');
   };

而且兼容性很好:Image对象在几乎所有浏览器中都得到支持,包括旧版浏览器。

方法2. Promise 批量预加载

如果你需要知道图片何时全部加载完成,可以使用Promise,结合 Promise实现批量加载和回调通知:

const preloadImages = (urls) => {
  const promises = urls.map(url => 
    new Promise((resolve, reject) => {
      const img = new Image();
      img.onload = () => resolve(img);
      img.onerror = reject;
      img.src = url;
    })
  );
  return Promise.all(promises);
};

// 使用示例
preloadImages(['img1.jpg', 'img2.jpg'])
  .then(imgs => console.log('所有图片加载完毕'))
  .catch(err => console.error('加载失败', err));

方法3. Link 标签预加载 (采纳的方法)

link标签实现预加载时有两种rel属性值,分别为preload 和 prefetch。

1. preload (预加载)

  • 目的: 告诉浏览器当前页面非常需要这个资源,它对于当前渲染(尤其是首屏内容或关键用户交互)是至关重要的,需要尽快高优先级地加载。

  • 加载时机: 浏览器会立即开始加载,优先级很高(通常与渲染阻塞的 CSS 或同步 JS 相当)。

  • 适用场景:

    • 关键路径资源: 首屏渲染必需的字体文件、首屏大图、关键的 CSS/JS(尤其是那些被后续发现的、会阻塞渲染的资源)。

    • 高优先级资源: 用户立即交互(如点击按钮)所需的脚本或样式。

  • 强制缓存: 浏览器会强制将 preload 的资源存储在 HTTP 缓存中,供当前页面后续使用。

语法:

<link rel="preload" href="critical-font.woff2" as="font" type="font/woff2" crossorigin>
<link rel="preload" href="main.js" as="script">
<link rel="preload" href="hero-image.webp" as="image" type="image/webp">
  • 关键属性:

    • as必须指定资源类型 (scriptstylefontimagedocumentfetch 等),这有助于浏览器设置正确的优先级、请求头和进行资源策略检查。

    • type (可选):资源的 MIME 类型,有助于浏览器判断是否支持该类型。

    • crossorigin (对于字体、fetch 等 CORS 资源):必需或建议设置,以确保请求带上正确的凭据。

2. prefetch (预获取)

  • 目的: 告诉浏览器未来的导航(用户可能访问的下一个页面)可能需要这个资源。它是对未来可能性的低优先级提示。

  • 加载时机: 浏览器会在空闲时间(当前页面主要资源加载完毕、关键渲染完成、主线程空闲时)以非常低的优先级加载。加载完成后存储在 HTTP 缓存中。

  • 适用场景:

    • 预测性导航: 用户很可能点击的链接所指向页面的主要资源(如该页面的框架 JS、CSS、主要图片)。

    • 非关键资源: 当前页面稍后可能需要,但非首屏必需的资源(如轮播图的后续图片、弹窗内容)。

    • SPA 路由: 预加载其他路由/视图的代码拆分块(chunk)。

  • 缓存: 加载完成后存储在 HTTP 缓存中,供后续页面后续操作使用。

<link rel="prefetch" href="next-page-bundle.js" as="script">
<link rel="prefetch" href="gallery-image-2.jpg" as="image">

3. 核心区别总结

特性preloadprefetch
目标当前页面的关键、必需资源未来页面或当前页面的非关键、可能需要的资源
优先级非常高 (立即加载,阻塞渲染优先级)非常低 (空闲时加载)
加载时机立即空闲时
缓存用途当前页面后续使用后续页面或当前页面后续可能操作使用
强制程度浏览器必须遵循(视为高优先级请求)浏览器可以忽略(只是一个建议提示)
as属性必须强烈推荐
典型用例首屏字体、关键脚本/CSS、首屏大图下一页面的主资源、非首屏图片、SPA其他路由代码块

4. 如何选择?

  1. 当前页面渲染或核心交互是否立刻需要这个资源?

    •  -> preload (例如:首屏字体、阻塞渲染的JS/CSS、首屏主图)。

    •  -> 考虑 prefetch 或其他策略。

  2. 这个资源是否主要用于用户可能访问的下一个页面?

    •  -> prefetch (例如:主导航链接指向页面的主JS/CSS)。

  3. 这个资源在当前页面稍后才需要,且不阻塞关键渲染?

    •  -> 可以考虑 prefetch (例如:轮播图后续图片),但也可以使用 preload 并配合较低的优先级提示(如 fetchpriority="low"),或者让资源按正常顺序加载。

  • `preload = "这个页面现在马上就要用!" - 高优先级,立即加载,用于当前页面核心资源。

  • `prefetch = "下个页面或待会儿可能会用到..." - 低优先级,空闲加载,用于预测未来需求。

5. 注意事项

  • 不要滥用 preload 过度使用 preload 会抢占真正关键资源的带宽,反而损害性能。只用于最关键的资源。

  • as 属性至关重要: 对于 preload 是强制的,对于 prefetch 也强烈推荐。它能确保正确的优先级、请求头和安全性策略。

  • 跨域资源 (crossorigin): 加载字体、通过 CORS 获取的脚本/样式等资源时,必须设置 crossorigin 属性(即使资源在同一个域名下,对于 font 通常也需要设置),否则预加载的资源可能被加载两次。

  • 浏览器支持: 现代浏览器普遍支持良好,但仍需考虑兼容性(尤其是较旧的浏览器)。可以使用特性检测。

  • 与 HTTP/2 Server Push 的关系: preload 可以看作是客户端驱动的、更精确的 Server Push 替代方案。Server Push 由服务器主动推送,可能造成浪费;preload 由浏览器根据页面指示发起请求,更可控。

  • modulepreload: 专门用于预加载 ES 模块,行为类似 preload 但针对模块依赖树进行了优化。

我使用的方法(link标签)

先看看静态加载。

    <!-- 预加载关键资源 -->
    <link rel="preload" href="https://picsum.photos/1200/600" as="image">
    <link rel="preload" href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap" as="style">
    <link rel="preload" href="https://cdn.jsdelivr.net/npm/lodash@4.17.21/lodash.min.js" as="script">
    <link rel="preload" href="https://cdn.jsdelivr.net/npm/chart.js" as="script">

再尝试动态资源预加载管理器:

核心实现:PreloadManager类

我实现了一个PreloadManager类,它封装了动态添加预加载资源的功能:

class PreloadManager {
    constructor() {
        this.resources = []; // 存储所有预加载资源
    }
    
    // 添加预加载资源
    addResource(url, type = 'script', crossOrigin = '') {
        // 检查是否已存在相同资源
        const existingResource = this.resources.find(r => r.url === url);
        if (existingResource) return existingResource;
        
        const resource = {
            id: Date.now() + Math.random().toString(36).substr(2, 5), // 唯一ID
            url,
            type,
            crossOrigin,
            status: 'pending', // 初始状态
            element: null // 存储创建的link元素
        };
        
        // 创建link元素
        const link = document.createElement('link');
        link.rel = 'preload';
        link.as = type; // 设置资源类型
        link.href = url;
        
        // 设置跨域属性
        if (crossOrigin) {
            link.crossOrigin = crossOrigin;
        }
        
        // 添加事件监听器
        link.addEventListener('load', () => {
            resource.status = 'loaded';
        });
        
        link.addEventListener('error', () => {
            resource.status = 'error';
        });
        
        // 添加到文档头部
        document.head.appendChild(link);
        resource.element = link;
        this.resources.push(resource);
        
        return resource;
    }
    
    // 获取所有资源
    getResources() {
        return this.resources;
    }
    
    // 移除资源
    removeResource(id) {
        const index = this.resources.findIndex(r => r.id === id);
        if (index !== -1) {
            const resource = this.resources[index];
            // 从DOM中移除link元素
            if (resource.element && resource.element.parentNode) {
                resource.element.parentNode.removeChild(resource.element);
            }
            this.resources.splice(index, 1);
            return true;
        }
        return false;
    }
}

使用方法

// 创建预加载管理器实例
const preloadManager = new PreloadManager();

// 添加一个JavaScript资源
const jsResource = preloadManager.addResource(
    'https://example.com/script.js',
    'script',
    'anonymous'
);

// 添加一个CSS资源
const cssResource = preloadManager.addResource(
    'https://example.com/styles.css',
    'style'
);

// 添加一个图片资源
const imageResource = preloadManager.addResource(
    'https://example.com/image.jpg',
    'image'
);

支持的功能

  1. 多种资源类型支持

    • JavaScript (script)

    • CSS (style)

    • 图片 (image)

    • 字体 (font)

    • API数据 (fetch)

    • 文档 (document)

    • 音频 (audio)

    • 视频 (video)

  2. 跨域支持

    • 可选择设置anonymous或use-credentials

  3. 资源状态跟踪

    • 实时监控加载状态(pending/loaded/error)

  4. 资源管理

    • 添加资源

    • 移除资源

    • 避免重复添加相同资源

这个实现提供了一个灵活且强大的动态资源预加载解决方案,可以轻松集成到任何项目中。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值