前言

 对于问题多多的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:阻塞解析

  1. <html>
  2. <body>
  3. <script>
  4. // 打印出 null
  5. console.log(document.getElementById('hi'))
  6. </script>
  7. <script src="./longtime.js"></script>
  8. <div id="hi">Hi</div>
  9. </body>
  10. </html>

示例2:阻塞渲染

  1. <html>
  2. <body>
  3. <script>
  4. // 打印出 <div id="hi">Hi</div>
  5. console.log(document.getElementById('hi'))
  6. </script>
  7. <link rel="stylesheet" href="./longtime.css">
  8. <div id="hi">Hi</div>
  9. </body>
  10. </html>

示例3:阻塞渲染

  1. <html>
  2. <head>
  3. <script>
  4. // 打印出 hinull
  5. console.log('hi' + document.getElementById('hi'))
  6. // 打印出 hiscript#s
  7. console.log('s' + document.getElementById('s'))
  8. </script>
  9. <link rel="stylesheet" href="./longtime.css">
  10. <script id="s"></script>
  11. </head>
  12. <body>
  13. <div id="hi">Hi</div>
  14. </body>
  15. </html>

示例4:阻塞渲染

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

示例2说明,如果阻塞渲染发生在body标签内,那么body及其子元素会继续解析并追加到DOM Tree中;

示例3说明,如果阻塞渲染发生在head标签内,那么body及其子元素不会被追加到DOM Tree中。

示例4说明,不管external stylesheet在哪里引入,在页面的所有external stylesheets下载完成前(DOMContentLoaded后才渲染),整个页面将不会被渲染。(估计Chrome会预先统计external stylesheet的数量)

对于FireFox

示例1:阻塞渲染

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

示例2:阻塞渲染

  1. <html>
  2. <head>
  3. <!-- div#hi不显示,直到./longtime.css下载完后 -->
  4. <style>#hi{color:red;}</style>
  5. <link rel="stylesheet" href="./longtime.css">
  6. </head>
  7. <body>
  8. <div id="hi">Hi</div>
  9. </body>
  10. </html>

对于IE9

示例1:

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

示例2:

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

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

解决方法

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

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

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

  1. 前端魔法堂:可能是你见过最详细的WebWorker实用指南

    前言 JavaScript从使用开初就一直基于事件循环的单线程运行模型,即使是成功进军后端开发的Nodejs也没有改变这一模型.那么对于计算密集型的应用,我们必须创建新进程来执行运算,然后执行进程间通 ...

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

    前言  最近在用Polymer增强form,使其支持表单的异步提交,但发现明明订阅了onsubmit和submit事件,却怎么也触发不了.下面我们将一一道来. 提交表单的方式 表单仅含一个以下的元素时 ...

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

    前言  前几天用户反映在录入资料时一不小心错按Backspace键,就会直接回退到是一个页面,导致之前辛辛苦苦录入的资料全部丢失了.哦?居然还有这种情况.下面我们来一起探讨一下吧! Windows系统 ...

  4. CSS魔法堂:重拾Border之——解构Border

    前言  当CSS3推出border-radius属性时我们是那么欣喜若狂啊,一想到终于不用再添加额外元素来模拟圆角了,但发现border-radius还分水平半径和垂直半径,然后又发现border-t ...

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

    一.前言 我们一般使用方式为 <link type="text/css" rel="stylesheet" href="text.css&quo ...

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

    一.前言 .net工程的Properties文件夹下自动生成一个名为AssemblyInfo.cs的文件,一般情况下我们很少直接改动该文件.但我们实际上通过另一个形式操作该文件.那就是通过在鼠标右键点 ...

  7. 【转】Java魔法堂:String.format详解

    Java魔法堂:String.format详解     目录     一.前言    二.重载方法     三.占位符     四.对字符.字符串进行格式化     五.对整数进行格式化     六. ...

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

    一.前言 在<JS魔法堂:jsDeferred源码剖析>中我们了解到img元素加载失败可以作为函数异步执行的优化方案,本文打算对img元素的加载行为进行更深入的探讨. 二.资源加载的相关属 ...

  9. MyBatis魔法堂:ResultMap详解

    一.前言   MyBatis是基于“数据库结构不可控”的思想建立的,也就是我们希望数据库遵循第三范式或BCNF,但实际事与愿违,那么结果集映射就是MyBatis为我们提供这种理想与现实间转换的手段了, ...

随机推荐

  1. 获取手机 IP

    /** *  获取用户ip * *  @return 获取用户ip */ + (NSString *)getIPAddress { NSString *address = @"an erro ...

  2. 百度地图API-覆盖物

    这两天一直在研究百度地图开放平台的各种好玩的东西,闲暇之余自己动手体验了一番,果然是妙趣横生,而且还可以自定义理想中的地图,不得不说,百度地图的开放平台为我们的应用提供了很多的便利,之前我们已经学习了 ...

  3. HTML <form> 标签的 method 属性(20161028)

    HTML <form> 标签的 method 属性 HTML <form> 标签 实例 在下面的例子中,表单数据将通过 method 属性附加到 URL 上: <form ...

  4. #MainTest

    '界面设计' --- <TextView android:layout_width="wrap_content" android:layout_height="wr ...

  5. angularjs fileUpload

    文件上传一直是我不熟悉的地方,<a href='https://github.com/nervgh/angular-file-upload/wiki/Module-API'>官网解释的例子 ...

  6. 【记录】解析具有合并单元格的Excel

    最近公司让做各种数据表格的导入导出,就涉及到电子表格的解析,做了这么多天总结一下心得. 工具:NOPI 语言:C# 目的:因为涉及到导入到数据库,具有合并单元格的多行必然要拆分,而NPOI自动解析的时 ...

  7. windows 下编译php扩展库pecl里的扩展memcache

    Memcache是一个高性能的分布式的内存对象缓存系统,通过在内存里维护一个统一的巨大的hash表,它能够用来存储各种格式的数据,包括图像.视频.文件以及数据库检索的结果等.简单的说就是将数据调用到内 ...

  8. Linux中的grep命令

    grep - print lines matching a pattern 参数: -a 将binary文件以text文件的方式查找 -i 忽略大小写 --color=zuto 加颜色匹配字符串 -v ...

  9. Android学习笔记---前传

    在正式的撰写个人的学习笔记前,先对个人的学习经历做一个简要的介绍.座右铭:诚不欺我 1. 前言 本人非软件工程出身,属于半路出家,误打误撞进入这个行业,初心是软件开发的门槛低,自以为学习过C语言,轻度 ...

  10. iOS 10 语音识别Speech Framework详解

    最近做了一个项目,涉及到语音识别,使用的是iOS的speech Framework框架,在网上搜了很多资料,也看了很多博客,但介绍的不是很详细,正好项目做完,在这里给大家详解一下speech Fram ...