动态加载 样式渲染_让骨架屏更快渲染 知乎

作者:潘与其 - 蚂蚁金服前端工程师 - 喜欢图形学、可视化 在之前「为vue项目添加骨架屏」一文中,介绍了骨架屏的概念以及在 Vue 项目中的应用。本文将介绍如何加快浏览器对骨架屏的渲染。

骨架屏的渲染时机

让我们先来看一下时间线,打开 Chrome Devtool 中性能面板:

0671e95a398f1bef5c75d880f6f24ac1.png

不难发现,在 HTML 下载完毕之后,浏览器仍然需要等待样式(index.css)下载完毕才开始渲染骨架屏。

这是由于浏览器构建渲染树需要 DOM 和 CSSOM,因此 HTML 和 CSS 都是会阻塞渲染的资源。这在大部分场景下都是合情合理的,毕竟让用户看到内容在样式加载前后闪烁(FOUC)是需要避免的。

但是骨架屏所需的样式已经内联在 HTML 中,供前端渲染内容使用的 CSS 显然不应该阻塞骨架屏的渲染。有没有办法改变这个特性呢?

将 link 挪到 body 中

首先想到的办法是,将  从  中挪到  中,HTML 规范允许这样做:

 tag can occur either in the head element or in the body element (or both), depending on whether it has a link type that is body-ok. For example, the stylesheet link type is body-ok, and therefore a   is permitted in the body.

这样 CSS 只会阻塞后续内容,骨架屏可以不受影响地被渲染。





Skeleton DOM


...

但是在 Chrome 中测试后会发现 CSS 依然阻塞渲染,在 Chrome 的关于这个问题的一个讨论中,能看到由于针对这种情况的渲染策略并没有严格的规范,不同浏览器下出现了不同的表现:

  • Chrome 依旧阻塞渲染。Webpagetest

  • IE 符合预期,仅仅阻塞后续内容。Webpagetest

  • Firefox 完全不阻塞渲染,除非  中已经出现了阻塞的 。这样后续内容就会出现 FOUC。Webpagetest。需要在  之后加上空的  达到阻塞后续内容渲染的效果。

在这个长长的讨论中,开发人员试图达到如下效果:

  • 任何出现在  之后的 DOM 内容在样式表加载完成之前都不会被添加到渲染树中,也就是阻塞后续渲染。

  • 为  增加 async 属性,类似  的 defer/async,不阻塞渲染,加载完毕立即应用。

  • 由 JS 插入的  将被异步加载。

通过这种方式,开发者就能让浏览器按照声明顺序,尽快渲染页面内容。开发者 Jake 提出了一个配合 HTTP2 使用的场景:











但是这个功能目前仍然没有在 Chrome 实装,不得不转向其他方法。

异步加载样式表

loadCSS 为异步加载样式表提供了两种方式:

  1. 提供全局 loadCSS 方法,动态加载指定样式表

我们将使用第一种方法,也是 loadCSS 推荐的方式。

 让浏览器仅仅请求下载样式表,但完成后并不会应用样式,也就不会阻塞浏览器渲染了。如果想在下载完成后应用样式,可以在 onload 回调函数中修改 rel 的值为 stylesheet,像正常阻塞样式表一样应用。

另外,由于浏览器支持度问题,loadCSS 提供了 polyfill (使用 media 属性),以及在不支持 JS 情况下降级。完整例子如下:



我们将在使用了骨架屏的 Vue 项目中应用这种方法。

在 Vue 项目中应用

虽然异步加载的样式表不会阻塞骨架屏的渲染,但是当前端渲染内容替换掉骨架屏内容时,必须保证此时样式表已经加载完毕,否则真正有意义的页面内容将出现 FOUC。由于样式表和 JS 加载顺序无法预知,我们必须考虑两者加载先后的情况。

大致思路

首先必须要保证 Vue 实例在异步样式表加载完毕后进行挂载,如果此时样式还没有完成,我们把挂载方法放到全局,等到样式加载完成后再调用:

app = new App();
window.mountApp = () => {
app.$mount('#app');
};
if (window.STYLE_READY) {
window.mountApp();
}

然后使用 ,当加载完成时,如果发现全局有 mountApp,就执行挂载:

有了具体思路,下面让我们看看在具体项目中应用时可能遇到的问题。如果不关心具体细节,可以跳到“最终效果”一节。

配合 HTMLWebpackPlugin 使用

在生成 SPA 时,通常会使用 HTMLWebpackPlugin,这个插件根据开发者传入的模板生成最终的 HTML,当我们开启了 inject 选项时,会自动插入  和 。在实现上述思路时,需要作出一些修改。

首先,在模板中我们需要加入针对 JS 和 CSS 的 










然后,由于不需要插件插入 ,我们可以编写一个简单的 Webpack 插件,监听 HTMLWebpackPlugin 的事件,过滤掉 CSS。这样插件就不会自动插入  了:

module.exports = class OmmitCSSPlugin {
constructor() {}
apply(compiler) {
compiler.plugin('compilation', (compilation) => {
compilation.plugin(
'html-webpack-plugin-alter-asset-tags',
(args, cb) => {
args.head = args.head.filter((link) => link.attributes.rel !== 'stylesheet');
cb(null, args);
}
);
});
}
}

最终效果

使用这个方法后,再次观察时间线,发现骨架屏渲染时间被提前了:

00eefdcf0c2dcfb5fee7b08e3383cb1e.png

最后,可以参考 DEMO 地址 和 Github 地址。

另外,在 Lavas 中也内置了这个功能,欢迎使用 Lavas 快速搭建 PWA 应用。

https://zhuanlan.zhihu.com/p/34550387

参考资料

  • render-blocking-css

  • critical-rendering-path-css-fast-loading-website

  • Chrome ISSUE

  • link-in-body

  • Lavas 中的应用

714a58ac28274b6302a5e6a2bda10117.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值