Webkit 异步加载 CSS 的奇怪现象

通常,css 文件都会放在页面 head 引入,这样可以避免页面加载时由于样式缺失而引发的跳动。有时候,我们也有需要异步加载 css 文件的场景,如按需加载某些自带外链 css 的模块。本文描述我在 webkit 下异步加载 css 文件遇到的两个奇怪现象。

另外,本文提到的问题要想绕过去很容易,有各种各样的办法,本文只描述现象本身。

首先来看本文测试用到的 loadCss 函数:

var loadCss = function(i) {
    var defererd = when.defer();

    var link = document.createElement('link');
    link.type = 'text/css';
    link.rel = 'stylesheet';
    var css = cssList[i];
    link.onload = function() {
        log(css + ' is ok.');
        defererd.resolve();
    };
    link.href = base + css;
    document.getElementsByTagName('head')[0].appendChild(link);

    return defererd.promise;
};

这里引入 promise 是为了后面并发加载 css 更方便。另外由于本文只考虑 webkit,而比较新的 webkit 支持了 link 的 onload 事件,所以这里不需要做兼容性处理。这部分代码很简单,不需要多解释。

奇怪现象一

试想,用上面的函数并发加载多个 css,浏览器会在什么时候解析并应用它们呢?是每个 css 下载完单独生效,还是要等到全部 css 都下载完再一起生效呢?我之前一直认为是前者,firefox 确实是前者,但 webkit 的表现却是后者。

var cssList = ['css1.php', 'css2.php', 'css3.php'];

when.all([loadCss(0), loadCss(1), loadCss(2)]).then(function() {
    log('all done.');
    log('time:' + (new Date - date));
});

以上代码,一共并发加载了三个 css,它们分别被我在服务端 delay 了 1s、3s 和 5s。测试结果表明,webkit 会等最慢的那个下载完才开始应用这三个 css 文件。通过下图可以清楚看到,前两个 css 下载完,UI 并不会得到更新。

这里是测试地址,请用最新的 safari 或 chrome 测试,并留意控制台的日志。

这个现象背后,应该是 webkit 对 css 渲染所做的优化:为了避免造成页面元素抖动(如前后两个 css 都有作用于某个元素的规则),在尚有 css 未加载完时,不进行渲染。个人觉得比较好的做法是设置一个超时,避免某些特别慢的 css 请求造成页面傻等。不知道 webkit 是否有这样的机制,但我尝试把本例中某个 css 的 delay 改到 30s,结论还是一样。总之这个策略有点奇怪。

奇怪现象二

我发现的另外一个奇怪的现象是,webkit 在异步加载 css 时,UI 更新会变得很节制,一定要移动鼠标或者鼠标点击才更新。

这个现象测试地址在这里,用最新的 safari 或者 chrome 都能复现。

本例中,我用前面写的 loadCss 函数加载了一个 delay 30s 的 css,同时启动定时器,每 200ms 更新一次页面某元素的内容,但在 webkit 中,如果鼠标不动,或者鼠标移出了浏览器窗口,页面就静止不变了。

我们知道,现代浏览器都会在浏览器窗口不可见时提高定时器间隔,但上面这个现象明显跟这没关系。不知道这次又触发了 webkit 什么优化策略,总之更奇怪。

PS:我 Google 到一篇类似的文章「webkit css-on-demand issues」,看来这个诡异的问题一直都存在。

更新 @ 2016.04.12:本文描述的现象一在 Chrome 50+ 中已修复;现象二也已修复(具体修复版本没有考证);Safari 中依旧还是老样子。

本文链接:参与评论 »

--EOF--

提醒:本文最后更新于 1076 天前,文中所描述的信息可能已发生改变,请谨慎使用。

专题「浏览器」的其他文章 »

Comments