最近在工作中遇到需要使用到资源预加载的场景,记录一下预加载的实现过程和预加载主要的几种实现方式。
方法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: 必须指定资源类型 (script,style,font,image,document,fetch等),这有助于浏览器设置正确的优先级、请求头和进行资源策略检查。 -
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. 核心区别总结
| 特性 | preload | prefetch |
|---|---|---|
| 目标 | 当前页面的关键、必需资源 | 未来页面或当前页面的非关键、可能需要的资源 |
| 优先级 | 非常高 (立即加载,阻塞渲染优先级) | 非常低 (空闲时加载) |
| 加载时机 | 立即 | 空闲时 |
| 缓存用途 | 当前页面后续使用 | 后续页面或当前页面后续可能操作使用 |
| 强制程度 | 浏览器必须遵循(视为高优先级请求) | 浏览器可以忽略(只是一个建议提示) |
as属性 | 必须 | 强烈推荐 |
| 典型用例 | 首屏字体、关键脚本/CSS、首屏大图 | 下一页面的主资源、非首屏图片、SPA其他路由代码块 |
4. 如何选择?
-
当前页面渲染或核心交互是否立刻需要这个资源?
-
是 ->
preload(例如:首屏字体、阻塞渲染的JS/CSS、首屏主图)。 -
否 -> 考虑
prefetch或其他策略。
-
-
这个资源是否主要用于用户可能访问的下一个页面?
-
是 ->
prefetch(例如:主导航链接指向页面的主JS/CSS)。
-
-
这个资源在当前页面稍后才需要,且不阻塞关键渲染?
-
是 -> 可以考虑
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'
);
支持的功能
-
多种资源类型支持:
-
JavaScript (script)
-
CSS (style)
-
图片 (image)
-
字体 (font)
-
API数据 (fetch)
-
文档 (document)
-
音频 (audio)
-
视频 (video)
-
-
跨域支持:
-
可选择设置anonymous或use-credentials
-
-
资源状态跟踪:
-
实时监控加载状态(pending/loaded/error)
-
-
资源管理:
-
添加资源
-
移除资源
-
避免重复添加相同资源
-
这个实现提供了一个灵活且强大的动态资源预加载解决方案,可以轻松集成到任何项目中。
2160

被折叠的 条评论
为什么被折叠?



