我曾经也做过类似的游戏,这类网页的一个特点就是有很多非常炫酷的动效,这类动效体积很大。
当遇到大文件时,我们必须进行一些优化。对于这些资源,我希望在需要展示的时候可以不需要等待直接展示。最理想的方法是进行预加载,让浏览器在闲暇时提前去下载这些资源。
因此,今天我们来谈谈前端几种“推测性加载”机制。
推测加载有多种机制:
预取
涉及在需要渲染文档(或文档的一部分)之前获取渲染文档(或文档的一部分)所需的部分或全部资源,以便在渲染时可以更快地实现渲染。
预渲染
更进一步,实际上渲染了准备在需要时显示的内容。根据具体操作方式,这可能会导致从旧页面即时导航到新页面。
预连接
涉及通过抢先执行部分或全部连接握手(即DNS+TCP+TLS)来加速来自给定源的未来加载。
当浏览器请求某个跨域资源时,需要先将域名解析成IP地址,然后才可以发出请求,这个过程就是DNS解析。
preload通常用于指示浏览器在渲染页面之前提前加载某些重要资源,一般用于加载当前页面必要的关键资源。不太常用,因为有可能导致首屏渲染变慢。
大多数现代浏览器会在解析HTML文档时开始预加载指定的资源。这意味着浏览器会在主HTML文档解析过程中检测到preload标签,并尽早开始下载相应的资源,以便在当前页面渲染过程中使用。
预加载的资源通常与主文档的下载并行进行。这意味着浏览器会尽可能地利用可用的网络连接来同时下载主文档和预加载的资源,以加快整体页面加载速度。
我们也可以对preload的优先级进行控制,preload标签支持使用as属性来指定资源的类型(例如,as="style"、as="script"等)。浏览器可能会根据资源的类型和网页的渲染情况来调整加载的优先级,确保关键资源能够尽快加载。预加载的触发条件可能因浏览器而异。有些浏览器可能会考虑页面的加载状态和用户行为来触发预加载,以避免在用户可能不需要的情况下浪费带宽和资源。例如,当用户即将点击链接时,浏览器可能会预加载链接指向的页面。
向浏览器提供有关当前页面上哪些JavaScript模块具有高优先级的提示,以便浏览器可以尽早开始下载它们。它是专门用于预加载js模块的“preload”。与rel=preload的工作方式基本相同,区别是浏览器不仅会预加载缓存,还会将它直接编译倒内存中的模块映射中。
prefetch是预获取的意思,主要用于提前加载未来可能用到的资源,但不一定会在当前页面加载完成后立即使用。
和preload预加载的主要区别是加载的时机不同,preload是在浏览器解析到该标签时即触发加载,而prefetch则是在浏览器空闲时再触发加载。
如果你使用vue的异步组件,你会发现它也是用prefetch实现的。
这是一个已经弃用的、非标准的功能,请不要使用。
上面对几种推测性加载方法进行了简要介绍。
不过很遗憾的是,这个连IE11都兼容的特性Safari并不兼容,这就使得在Safari浏览器上这个标签不会起任何作用,资源不会被预加载。
对于Safari我们只能另外想其他的办法了,最简单的就是通过请求去加载了。
因为在页面onload之后渲染主线程可能并不会空闲,没了浏览器的支持我们怎么才能判断当前的渲染主线程是否空闲?
有一个requestIdleCallback函数,当主线程空闲时会调用回调方法,从而可以在空闲时做一些操作。不过更加遗憾的是Safari目前只在预览版本中支持这一函数,正式版本还不支持。
既然没有办法得知主线程的空闲状态,又不想占用主线程,那么只有一个办法了,通过Worker去新起一个函数来专门做加载就可以了:
worker.js
constloadStatic=async(urls)=>{constpromiseList=[];for(consturlofurls){constreqPromise=newPromise(async(resolve,reject)=>{try{constresponse=awaitfetch(url);if(response.ok){constblob=awaitresponse.blob();constblobUrl=URL.createObjectURL(blob);resolve(blobUrl);}else{console.error("[loadStatic]请求失败,状态:",response.status);reject({url,error:response.statusText});}}catch(error){console.error("[loadStatic]网络错误:",error);reject({url,error});}});promiseList.push(reqPromise);}returnPromise.allSettled(promiseList);};self.onmessage=(e)=>{consturls=e.data;loadStatic(urls).then((data)=>{postMessage({status:'completed',data});}).catch((error)=>{console.error(error);postMessage({status:'error',data:error});});};main.js
constworker=newWorker('./worker.js');worker.onmessage=function(event){console.log("收到worker返回的内容",event.data);event.data.forEach(info=>{ console.log(info); //加载成功:{status:'fulfilled',value:'blob:xxxxx'} //加载失败:{status:'rejected',reason:{url:'xxxxx',error:'NotFound'}}})};worker.onerror=function(e){worker.terminate.();console.log("[usePrefetch]error:"+e.message);};consturls=[xxx,xxx,xxx];worker.postMessage(urls);然后加一个判断,当支持prefetch时使用prefetch,不支持时使用Worker:
constsupportsPrefetch=document.createElement("link").relList.supports("prefetch");这样就通过不同方式实现了Chrome和Safari的大文件预加载,结合CDN的缓存策略,能让用户享受到更流畅的使用体验。