前端魔法堂:解秘FOUC

前言

 对于问题多多的IE678,FOUC(flash of unstyled content)——浏览器样式闪烁是一个不可忽视的话题,但对于ever green的浏览器就不用理会了吗?下面尝试较全面地解密FOUC。

到底什么是FOUC?

 页面加载解析时,页面以样式A渲染;当页面加载解析完成后,页面突然以样式B渲染,导致出现页面样式闪烁。
 样式A,浏览器默认样式 或 浏览器默认样式 层叠 部分已加载的页面样式;
 样式B,浏览器默认样式 叠加 全部页面样式。

为什么会出现FOUC

 我们了解当输入网址按回车后浏览器会向服务器发送请求,然后服务器返回页面给浏览器,浏览器边下载页面边解析边渲染。
下面我们解剖一下边下载页面边解析边渲染的过程:
1. 边下载边解析就是边下载html边构建DOM Tree;
2. 浏览器以user agent stylesheet(浏览器内置样式)为原料构建CSSOM Tree;
3. DOM Tree+CSSOM Tree构建出Render Tree,然后页面内容渲染出来;
4. 当解析到inline stylesheet 或 internal stylesheet时,马上刷新CSSOM Tree,CSSOM Tree或DOM Tree发生变化时会引起Render Tree变化;
5. 当解析到external stylesheet时就先加载,然后如internal stylesheet那样解析和刷新CSSOM Tree和Render Tree了。
 上述步骤5中由于样式文件存在下载这个延时不确定的阶段,因此网络环境不好或样式资源体积大的情况下我们可以看到样式闪烁明显。
 这就是为什么我们将external stylesheet的引入放在head标签中的原因,在body渲染前先把相对完整的CSSOM Tree构建好。但大家都听说过script会阻塞html页面解析(block parsing),而link不会,那假如网络环境不好或样式资源体积大时,body已经解析并加入到DOM Tree后,external stylesheet才加载完成,不是也会造成FOUC吗?

style,link等样式资源的下载、解析确实不会阻塞页面的解析,但它们会阻塞页面的渲染(block rendering)。

Block Parsing 和 Block Rendering的区别

Block Parsing: 阻塞HTML页面解析,HTML页面会被继续下载,但阻塞点后面的标签不会被解析,img,link等不会发请求获取外部资源。
Block Rendering:阻塞HTML页面渲染,HTML页面会被继续下载,阻塞点后面的标签会继续被解析,img,link等会继续发送请求获取外部资源,但不会合成Rendering Tree或不会触发页面渲染,也不会执行JavaScript代码。
 各浏览器这方面还有一点差异:

对于Chrome

<link rel="stylesheet">,<link rel="import"> and @import url("<url>")会阻塞渲染。
示例1:阻塞解析

<html>
  <body>
    <script>
            // 打印出 null
      console.log(document.getElementById('hi'))
    </script>
    <script src="./longtime.js"></script>
    <div id="hi">Hi</div>
  </body>
</html>

示例2:阻塞渲染

<html>
  <body>
    <script>
            // 打印出 <div id="hi">Hi</div>
      console.log(document.getElementById('hi'))
    </script>
    <link rel="stylesheet" href="./longtime.css">
    <div id="hi">Hi</div>
  </body>
</html>

示例3:阻塞渲染

<html>
  <head>
    <script>
            // 打印出 hinull
      console.log('hi' + document.getElementById('hi'))
            // 打印出 hiscript#s
      console.log('s' + document.getElementById('s'))
    </script>
    <link rel="stylesheet" href="./longtime.css">
    <script id="s"></script>
  </head>
  <body>
    <div id="hi">Hi</div>
  </body>
</html>

示例4:阻塞渲染

<html>
  <body>
        <!-- div#hi在 ./longtime.css下载完前不会被渲染 -->
    <style>#hi{color:red;}</style>
    <link rel="stylesheet" href="./longtime.css">
    <div id="hi">Hi</div>
  </body>
</html>

示例2说明,如果阻塞渲染发生在body标签内,那么body及其子元素会继续解析并追加到DOM Tree中;
示例3说明,如果阻塞渲染发生在head标签内,那么body及其子元素不会被追加到DOM Tree中。
示例4说明,不管external stylesheet在哪里引入,在页面的所有external stylesheets下载完成前,整个页面将不会被渲染。(估计Chrome会预先统计external stylesheet的数量)

对于FireFox

示例1:阻塞渲染

<html>
  <body>
        <!-- div#hi的文字显示为红色,待./longtime.css下载完后又渲染为其他颜色 -->
    <style>#hi{color:red;}</style>
    <link rel="stylesheet" href="./longtime.css">
    <div id="hi">Hi</div>
  </body>
</html>

示例2:阻塞渲染

<html>
  <head>
        <!-- div#hi不显示,直到./longtime.css下载完后 -->
    <style>#hi{color:red;}</style>
    <link rel="stylesheet" href="./longtime.css">
  </head>
  <body>
    <div id="hi">Hi</div>
  </body>
</html>

对于IE9

示例1:

<html>
  <body>
        <!-- div#hi没有渲染,也没有加入到DOM Tree中 -->
    <style>#hi{color:red;}</style>
    <link rel="stylesheet" href="./longtime.css">
    <div id="hi">Hi</div>
  </body>
</html>

示例2:

<html>
  <body>
        <!-- div#hi渲染了,加入到DOM Tree中 -->
    <style>#hi{color:red;}</style>
    <div id="hi">Hi</div>
    <link rel="stylesheet" href="./longtime.css">
  </body>
</html>

上面的示例表明,IE下block rendering等价于block parsing,因为连img,script,link,@import url()资源请求都会被阻塞。

解决方法

 现在我们知道FOUC时由于页面采用临时样式来渲染页面而导致的,其中仅有chrome能好的屏蔽了这一点,而其他浏览器就呵呵了。那有什么方案可以解决呢?其实我们的目的就是不要让用户看到临时样式,那么我们可以隐藏body,当样式资源加载完成后再显示body

<html class="no-js">
    <style>
        /modernizr会将html的no-js替换为js,并将modernizr代码在最后时加载,那么就能保证所有样式文件已经加载完成/
        .no-js body{display: none!important;}
    </style>
    <body>
        <script src="modernizr.js"></script>
    </body>
</html>

(编译modernizr时记得勾setClasses哦,否则不会替换no-js的!)

总结

 上述方案虽然解决了FOUC的问题,但很明显地延长了首屏白屏时间,当前较流行的App Shell(可以理解为先显示页面布局的骨架或一幅图片)也会失效,所以对于2C的应用仅仅采用上述的方案效果并不理想。后续待我研究好后再追加一篇吧^_^
 尊重原创,转载请注明来自:http://www.cnblogs.com/fsjohnhuang/p/6739064.html ^_^肥仔John

感谢

Flash of unstyled content
The FOUC Problem
Critical rendering path

时间: 2024-12-11 09:33:08

前端魔法堂:解秘FOUC的相关文章

前端魔法堂:屏蔽Backspace导致页面回退

前言  前几天用户反映在录入资料时一不小心错按Backspace键,就会直接回退到是一个页面,导致之前辛辛苦苦录入的资料全部丢失了.哦?居然还有这种情况.下面我们来一起探讨一下吧! Windows系统下独有的行为  Windows下的IE.FireFox和Chrome 52之前的浏览器,当焦点不在一个可编辑的元素上时,按Backspace键就会回退到上一个页面,按Shift+Backspace键则会前进到下一个页面.  而Chrome 52以后的浏览器则屏蔽了Backspace和Shift+Ba

前端魔法堂:onsubmit和submit事件处理函数怎么不生效呢?

前言  最近在用Polymer增强form,使其支持表单的异步提交,但发现明明订阅了onsubmit和submit事件,却怎么也触发不了.下面我们将一一道来. 提交表单的方式 表单仅含一个以下的元素时,该元素得到焦点,按回车键,即可发起表单提交. input[type=text] input[type=password] input[type=email] input[type=url] input[type=tel] input[type=number] input[type=search]

JS魔法堂:LINK元素深入详解

一.前言   我们一般使用方式为 <link type="text/css" rel="stylesheet" href="text.css"> 来引入外部层叠式样式文件,但LINK元素各属性的具体含义.资源加载行为等方面却了解不多,本文打算稍微深入一下.   由于内容较多,特设目录一坨:   二.到底有没有结束标签?  三.普通属性介绍  四.属性disabled详解    1. Attribute和Property的disable

.Net魔法堂:AssemblyInfo.cs文件详解

一.前言   .net工程的Properties文件夹下自动生成一个名为AssemblyInfo.cs的文件,一般情况下我们很少直接改动该文件.但我们实际上通 过另一个形式操作该文件.那就是通过在鼠标右键点击项目的属性进入"应用程序"->"程序集信息",然后修改信息.   二.作用    通过特性(Attribute)来设置程序集(dll文件)的常规信息,供查看或作为配置信息供程序内部使用.   三.详解 // 程序集标题 [assembly:Assembly

MyBatis魔法堂:ResultMap详解

一.前言     MyBatis是基于"数据库结构不可控"的思想建立的,也就是我们希望数据库遵循第三范式或BCNF,但实际事与愿违,那么结果集映射就是MyBatis为我们提供这种理想与现实间转换的手段了,而resultMap就是结果集映射的配置标签了.   二.从SQL查询结果到领域模型实体   在深入ResultMap标签前,我们需要了解从SQL查询结果集到JavaBean或POJO实体的过程.   1. 通过JDBC查询得到ResultSet对象   2. 遍历ResultSet对

JS魔法堂:IMG元素加载行为详解

一.前言   在<JS魔法堂:jsDeferred源码剖析>中我们了解到img元素加载失败可以作为函数异步执行的优化方案,本文打算对img元素的加载行为进行更深入的探讨.   二.资源加载的相关属性和事件   资源加载首先当然是确定资源位置的 src属性 .随之就是资源加载成功与否的 onload事件 和 onerror事件 ,对于IE5~10来说还多了一个 onreadystatechage事件 和与该事件相关联的 readyState属性 和 complete属性 .    onload事

CSS魔法堂:&quot;那不是bug,是你不懂我!&quot; by inline-block

前言  每当来个需要既要水平排版又要设置固定高宽时,我就会想起display:inline-block,还有为了支持IE5.5/6/7的hack*display:inline;*zoom:1;.然后发现盒子间无端端多了个不可选的空白符,于是想尽办法修复这个bug. 直到一天拜读了@一丝姐.@HAX等高人的秘笈后才顿悟,原来我错了.那不是bug,是我不懂而已. 先行者--IE5.5中的inline-block  当我们为支持IE5.5/6/7而添加这段hack时*display:inline;*z

HTML5魔法堂:全面理解Drag &amp; Drop API

一.前言      在HTML4的时代,各前端工程师为了实现拖拽功能可说是煞费苦心,初听HTML5的DnD API觉得那些痛苦的日子将一去不复返,但事实又是怎样的呢?下面我们一起来看看DnD API的真面目吧!   二.由于篇幅较长,特设目录一陀 三.HTML4下实现简单拖拽 四.HTML5下实现简单拖拽 五.如何启用DnD效果 六.draggable属性详解 七.DnD的生命周期 八.DnD中最重要的数据传递对象──DataTransfer对象 九.[object DataTransferIt

JS魔法堂:精确判断IE的文档模式by特征嗅探

一.前言   苦逼的前端攻城狮都深受浏览器兼容之苦,再完成每一项功能前都要左顾右盼,生怕浏览器不支持某个API,生怕原生API内含臭虫因此判断浏览器类型和版本号成了不可绕过的一道关卡,而特征嗅探是继浏览器探测后另一利器处理上述问题.   二.何为特征嗅探 从前我们都是通过对navigator.userAgent或navigator.appVersion两个属性值进行特定字符串匹配和萃取来区 分浏览器类型和获取版本号的.但随着IE8提供可选的文档兼容性模式设置和各种加壳浏览器的出现,导致无法通过n