HTML5的性能优化

来源:互联网 发布:变性人有快感吗 知乎 编辑:程序博客网 时间:2024/06/06 00:55

1.更简洁的标签

接下来可能并不是一件很常见的事情,但是却是我比较推崇的,使用更简洁的标签方式。HTML5从这个名字大家可以听出,它是从HTML4继承过来的。HTML4里面有严格模式跟过渡模式,HTML5是支持这种过渡模式的,就是你可以不把一些标签闭合。但是,我并不推荐所有的标签,比方说BODY标签的不闭合,这种我们不推荐。但是像P标签最常用的,还有列表标签LI。为什么这样说?首先从视觉的角度来说,这样的方式更简洁一点。然后关键的是在文档传输过程中,内容会更少。

HTML5标签属性的声明支持三种方式:单括号、双括号和不加括号。为了减少文档大小,我是选择不加双引号的方式或单引号的方式。但是要注意,假设是类属性的声明,因属性可能包括多个类,多个类的时候则必须用括号括起来。在这方面,给大家看一下谷歌的一个实践。谷歌自己有一个页面完全实践了上面的东西,文档的大小减少了20%,使HTML文档的传输减少了20%。如果把整个都实践起来,可以达到5%—20%之间的减少。这是第一步,缩减HTML文档的大小。

扩展:

HTML5 中增加了大量的新标签。这些标签大体可以分为两类:1,功能性标签。2,语义性标签。

功能性标签:就是类似canvas、video 等标签,它是 HTML5 新创造出来的,用于实现一些新功能用的。这类新增标签,用于某些新功能的,这些新功能往往需要浏览器配合实现,所以在早期的浏览器中,功能不会得到支持。

语义性标签:就是类似 header、footer、nav 等等标签。它们类似 div,没有什么新增的功能,但是语义性增强了。例如,使用 header 括起来的内容,就会被认为是头部相关,页面中使用 nav 括起来的有序列表(ol)或者无序列表(ul),就会被认为是这个网站(或者网页)的导航。这类标签同样是新增的,在早期浏览器中不会被支持,但是这与功能性的标签不同,由于它的本质和 div 类似,所以可以通过一段 JavaScript 代码来进行 hack 从而让早期浏览器支持。

结构清晰合理的、使用 HTML5 重构过的网站有很多,这里只拿出几个比较好的比较典型的例子来分析。这里以“我爱水煮鱼” 博客作为例子(地址:http://blog.wpjam.com/)。你可以随时打开,然后右键“查看源代码”来观看详细的代码。此外,“潜行者m”博客也采用了比较规范的写法,也可以借鉴。

                    


2.图片优化

接下来是关于图片的优化,图片永远是又爱又恨的元素。因为当图片多的时候,会严重拖垮整个页面的加载速度。关于图片的优化方式,《高性能网站》书中已有很多介绍,总结起来主要有三点:使用精灵图、优化图片的大小,使用DATA URI,具体这里就不细说了。

图片优化的另一个思路是:no-image。抛弃图片,拥抱CSS3。原先需要设置一张圆角效果的图片,现在使用CSS3中的 border-radius;原先需要设置阴影效果的图片,现在使用CSS3中的box-shadow;原先需要设置渐变的背景图片,现在使用CSS3中的gradient。

扩展:

1,使用精灵图,优化图片大小这两点,详见我的博客文章:

“CSS Sprites 与图片压缩 ” http://blog.csdn.net/taotao6039/article/details/10914297

2,DATA URL

假设你有以下的图像:A

把它在网页上显示出来的标准方法是:<img src="http://sjolzy.cn/images/A.png"/>

这 种取得资料的方法称为 http URI scheme ,同样的效果使用 data URI scheme 可以写成:

<img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAIAAAA7ljmRAAAAGElEQVQIW2P4DwcMDAxAfBvMAhEQMYgcACEHG8ELxtbPAAAAAElFTkSuQmCC" />

换句话说我们把图像档案的内容内置在 HTML 档案中,节省了一个 HTTP 请求。

Data URI scheme 的语法:

我们来分析一下这句 img 标签的语法:<img src="data:image/png;base64,iVBOR....>

它包含一下部分:

data - 取得数据的协定名称

image/png - 数据类型名称

base64 - 数据的编码方法

iVBOR.... - 编码后的数据

: , ; - data URI scheme 指定的分隔符号

Base64 编码:

不知道什么是 base64 吗?简单地说它把一些 8-bit 数据翻译成标准 ASCII 字符,往上有很多免费的 base64 编码和解码的工具,你也可以用 PHP 的 base64_encode() 进行编码:

echo base64_encode(file_get_contents('check.png'));
在 CSS 中使用 data URL:

Data URI scheme 也可以在 CSS 中使用,例如:

body { background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAQAAAADCAIAAAA7ljmRAAAAGElEQVQIW2P4DwcMDAxAfBvMAhEQMYgcACEHG8ELxtbPAAAAAElFTkSuQmCC");}
浏 览器会缓存这种图像吗:

不会,Data URL 虽然节省 HTTP 请求,但是倘若这个图像要在网页多个地方显示的话,便会加大网页的内容,延长了下载的时间,其中一个解决办法是在一个 CSS class 中加入 data URL,在需要显示图像的区块调用这个 class,例如:

.logobg {  background: url(data:…)}
<div class=”navigation logobg”>helo, hello<div class=”footer logobg”>
又 是 IE 来搞破坏

任何精采的技巧都可能受到 IE 的排挤,这次也有这种情况,IE8 之前的版本都不支援 data URI scheme。

解决这个问题的办法有:使用MHTML 解决 data URI scheme 的浏览器兼容问题

具体做法看代码(肯定是用css hack来实现)

/*Content-Type: multipart/related; boundary="_ANY_STRING_WILL_DO_AS_A_SEPARATOR" --_ANY_STRING_WILL_DO_AS_A_SEPARATORContent-Location:the9Content-Transfer-Encoding:base64 /9j/4AA....+b0//2Q== (这里是base64编码)*/ #the9{  background-image: url("data:image/png;base64/9j/4AA....+b0//2Q=="); /* normal */  *background-image: url(mhtml:http://www.zhangjingwei.com/demo/scheme/style.css!the9);  width:300px;  height:300px;  color:#F00;  font-weight:900;}


3.预取

接下来讲Prefetching,预取,是优化的另一个思路。我们现在优化的思路无非就是少。很多都是从少的角度,比方说前面把文档大小减少,把图片的大小减少。很多张的图片变成一张精灵图,都是为了把发送请求的数量减少。预取的话,是另一种思路,提早加载好资源,用户去点的时候,实际上已经加载好,那肯定是更快了。

预取,一共有两部分:一部分是资源的预取,还有一部分是DNS的预解析。

资源预加载有几个点需要注意:

预加载只是在浏览器空闲的时候才会去拉,但不保证一定会去拉,这是很重要的一点。因为本身浏览器有一个全局的监听器,这是内部的一个接口,当浏览气空闲的时候,它会去执行浏览器空闲的时候应该做事情,但是这个空闲的回调不一定被触发,所以说并不保证一定会执行预加载。

Chrome不支持HTTPS资源的预加载,像Alipay是HTTPS的页面,Chrome不会去预拉取。

一个预拉取的页面虽存在后不可见,实际上它是在正常解析。假如说我预拉取登陆页面,登陆页面有很多资源,比方说有图片,有CSS文件,JS文件。它是从上往下正常的会被解析,解析的过程中,这个页面没有显现,但是它实际上是存在的。在HTML5里面,可通过document.visibilityState得到当前页面状态,通常页面有两种状态,可见与不可见,但是现在有一个新的状态,叫做预渲染的状态。可以直接通过document.visibilityState 是否等于 prerender 来判断页面是否在预渲染状态。

扩展:

资源预取

  现在,图像占据了很多主流网站总字节数的一大部分。通常,发出请求和下载图像这两个系统开销对性能有显著的影响。不过许多情况下,网站开发人员都知道在什么情况下图像需要不被浏览器过早地检测到,例如通过ajax请求或其他用户在页面操作而加载的图像。资源预取是提前将图像、脚本、样式表或其他资源加载进浏览器。这经常用来处理图像,但也可以处理其他可放在浏览器缓存的资源。

  我在这里所提到的三种技术目前为止都是最经典和最常用的。可惜我不能给出一个具体的使用数,因为通过我在访问Alexa时检测到有太多方法可以实现。然而,许多网站没有正确利用这种技术,甚至只预加载一些会引起巨大的用户体验差异的图像。

页面预取/预渲染

  页面预取和资源预取十分相似,除了我们实际上让新页面提前自行加载这一点之外。Firefox是第一个使用此技术的浏览器。通过使用下列的标签,你可以提示浏览器预取某个页面(或个别资源)。

  <linkrel="prefetch" href="/my-next-page.htm">

  在预渲染的情况下,浏览器不仅下载页面还下载页面所需的资源。它也开始在内存里渲染页面(用户不可见),如此一来当页面请求发出时,浏览器就能将页面瞬时呈现给用户。Chrome是首个采用这种技术的浏览器。通过使用下列的标签,你可以提示浏览器预渲染某个页面。

  <linkrel="prerender"href=http://mydomain.com/my-next-page.htm>

  目前为止,与另外两种技术相比,这一技术目是风险最高并最饱受争议的。只有在十分确定用户接下来将浏览哪一个页面的情况下才能进行预渲染。谷歌在这方面是最有名的例子,在有十足把握的情况下它将会预渲染第一个结果页。在我所访问Alexa排名前十万的网站中,我仅发现有95个这样做的网站。尽管这一技术显然不是针对每一个用例,但我认为更多的网站应充分利用这种技术以便改善用户体验。


4.DNS解析

接下来是关于DNS的解析。有时候我们登入页面,对用户可能点的地方相对而言是比较难探测到,当然有时候我们会做一些埋点来探知用户下一步行为大部分是往里走。但有些情况下,我们不知道用户下一步具体会走到哪一个页面的时候,但是我们知道他要走到哪一个域。这个时候,我就可以预解析DNS。因为实际上,整个页面的请求过程中间有一个很长的DNS的解析过程,如果说这个我们提前做了,就可以更进一步让用户看到这一页面。

以下是Q+壁纸的案例。Q+壁纸是Q+某一个系统系统,首先Q+整个的架构是基于WEB + 客户端。我们现在看到的就是一个WEB的页面,虽然它外面是一个客户端的壳,但是它的心是WEB的。整个过程在我们第一次在完成的时候,因为图片比较多,所有的静态资源是分配到十几个静态服务器上。也就是说,如果我要去拉的时候,我就要解析10个DNS,这个时间是相当耗时的,最慢的时候可能会延迟几秒钟,这是我们肉眼能感觉到的。如果进行DNS预解析,因为本身资源我不知道具体是哪一个,所有图片都是随机的,所以我们只能说在DNS预解析上下功夫,来提升它的速度。这样的话,从原来可能需要2秒钟,我就变成1秒钟。

接下来讲Q+中的应用。我们会像QQ里面一样,QQ里面跟Q+都有很多文字链,就是窗口的左下角有一个文字APP信息的推送。这边是通过WEB时时去拉取后端,后端拉取过来然后在前台显示。但是在某一个时期,其实所有的APP它一共推送的运营信息是固定的。如果说按某个具体APP去分析每个文字链对应数组的话,这个时候是非常大数据。因为这里一个就大概有达到三四百个字节,从优化的角度说,我们把这些每次拉区过来的存在本地。再存上本地的localStorage,我们是同一域,所有的APP之间的信息都是可以相互访问的。然后就是把所有拉过的ID,就不会再重新拉一遍。

在这里也有一个需要注意的点,localStorage目前很多厂商的实现是同步的。如果你大量地调用localStorage这个接口,实际上他会阻塞你的渲染进程。这个时候,当用户往下拖动页面的时候,然后你这个时候又正好在做存储数据,这个数据又比较大,这个时候用户就会感觉你这个页面非常卡。之前他们都有讨论这个问题,本身这个接口的设计IE是设计成异步的,他们设计是成同步。这个会导致在调这个借口的时候,假设你程序比较多,因为有一个序列化的过程,序列到磁盘。这样的话,整个过程就会显得比较慢。再加上本身localStorage可以做不同的窗口之间共享这个数据,它会在这个数据上加锁。如果大量地数据在调用这个本地接口,它就会显得比较卡。所以目前没有什么特别好的解决方案,但是这是需要记住的。即使说目前最大的五点多兆,如果你用了五点多兆,会让用户很悲催。因为你如果一去调用这个借口,用户在拖用鼠标,就觉得非常卡。

扩展:

DNS是实现人类可读的域名(mysite.com)到计算机可读的IP(123.123.123.123)的映射的协议。DNS解析非常快,每次都在100毫秒内,它必须在任何请求发送到服务器之前进行,这就会引起一个对页面整体加载时间有实际影响的级联反应。我们都知道其他一些域名在随后的页面或者用户会话需要加载资源,例如静态内容的二级域名(images.mydomain.com)或第三方内容的域名。有一些浏览器支持使用meta标签来识别需要被解析的域名,这样一来浏览器可以提前解析它们。通过使用下列的标签做到这一点是十分简单的。

  <link href="//my.domain.com" rel="dns-prefetch" />
  <link href=http://my.domain.com/ rel="prefetch" /> <!– IE9+ –>

  添加这一标签使得浏览器可以提前解析DNS,而不是等到资源请求之后才开始解析。对网站上游客可能会访问的其他页面进行DNS预取可能是最有价值的一项技术。Chrome、Firefox以及9.0以上版本的IE浏览器都支持这一特性。

  尽管减少几百毫秒看起来微不足道,但汇总起来之后就是一个值得注意的收益。这也是一个安全的优化方法并且易于实现。我很好奇这种技术的使用率,于是我访问了Alexa网站上排名前十万的网站。事实表明只有552个网站(0.55%)目前正在使用DNS预解析技术。这只是成功的一小步,还有更多的网站需利用这种技术。


5.离线存储

接下来讲离线存储在性能方面给用户带来的好处。首先是进离线存储的定义文件,在Q+中所有的系统模块,都是有定义离线支持。就是说所有的应用,如果网断了,还是可以用。在文档中加入MANIFEST的文件,MANIFEST是一个定义文件,声明当前页面哪些是需要存储在本地的?哪些是不需要存储的?哪些如果说请求失败,应该用哪些新的图片或者什么来代替?这样分三块:

第一,CACHE,哪些需要存储到本地。

第二,NETWORK,是不会存储在本地的,它每次都回去请求一遍但是这里需要指出的是,本地存储跟浏览器存储实际上是两回事情,他们存的是两块不同的地方。即使NETWORK这边需要告诉APP说,我需要每次都拉一次,因为像Chrome,他这个存储缓存是非常可恶的,比较难清除的,必须通过手动去清除,才能完全生效。所以说你即使设置了不要让它存储在本地,但是浏览器可能本身把它存储起来了,因为他存的是两块不同地方。

第三,FALLBACK。如果说一个图片假如说请求失败,它是404。那要用什么图片代替?我觉得这个比较好玩。

MAEIFEST怎么设置? MANIFEST这里需要注意的是三点:

MANIFEST同源限制;

MIME类型必须为text/cache-manifest,这是标准的,如果是其他格式,都不会生效;

CHROME,如果要看这个东西有没有生效,可能通过CHROME这个伪协议的方式在浏览器输入,chrome://appcache-internals。

关于如何去更新应用的缓存。为什么要离线存储?离线存储在本地,当浏览器知道你有离线存储你,它会首先去离线存储的目录下,去找这个资源是否已经被Cache。当它已被Cache的时候,他就直接从这边拿到这个资源,不会再去发送一个请求。因为浏览器的请求是这样的,当有离线存储的话,就连请求都不会发,所以说会更快。 如果说有的时候我们需要更新,更新的时候怎么办?

用户可以手动去清除浏览器的Cache,这个时候自动把本地存储给清除了。

修改MANIFEST的任何内容,这是比较推荐的方式,也是我们线上用的方式。就是说我们可以修改里面的的具体项目,但是这里应该最好是修改注释,因为我每次发布的时候,我们自动发布机制,发布的时候在上面注释修改一下就可以了。这样的话,每次发布的内容,都会实时同步到客户端的本地;

通过程序去执行,程序的就是window.applicationCache.update()。就是我要去操作离线存储,其实我有时候叫应用存储,因为它的语意就是应用存储。我们去手动的更新应用存储。

扩展:

html5离线存储入门http://i.wanz.im/2010/07/01/html5_offline_file_cache_guide_for_beginner/


6.Web Worker

接下来Web Worker。 Web Worker是一个多线程的JS进程。应用场景其实我们在线上的话,是没有的,我就不讲了。但是可以讲下具体我看到过的应用场景。

首先介绍一下WEBWORK是什么东西?它是一个OS级别的线程。之前我们模仿多线程,实际上都是多开一个窗口。但是现在的话,浏览器本身就提供了,这个会让操作带来更多便利,是让我们整个文档比较重,并不是很建议的方式。

然后WebWorker访问能力是有限的,它并不能访问到很多全局对象。比如说documnet对象它是访问不了的。 WebWorker最适合的场景就是CPU密集型的计算操作。之前我们做游戏的时候,我们用BOX2D。应该很多人听到过,它涉及到大量的计算,就是整个页面里面,下面所有的物体要去计算它们的碰撞关系,这个计算量是非常大的。但是如果放在当前的JS的进程里面去执行,这个计算量一大,一计算,整个页面就非常卡。但是如果用WebWorker去做,它是异步的过程,实时的发送过去,在计算的过程中还能干其他事,这就是多线程。

7.设备API

讲一下设备API。设备API我觉得最重要在性能方面,也是目前实现最早的API。一个是CONNECTION,就是网络带宽。这个有什么作用?在中国这个场景下,必须得记住,很多用户的网速依旧是很低的。我们希望让用户网速低的时候,能够自动降级到一个比较低的方案。如果用现有的技术,我们是做不到的。但是使用设备API我们是可以的。因为我们知道,从设备上可以取到这些信息。它的宽带是多少,多少宽带的时候我们能干的事情。比方说宽带好的时候,我就用高清图片。宽带比较低的时候,就用清晰度比较低的图片。

8.电池

下面一个是关于电池的。我觉得从性能角度来说,主要是电量方面。假如说用户电池电量比较低的时候,我觉得是应该尽量少做一些事情。本身手机现在电池的技术来没有突破,我觉得让APP看起来比较高性能,也是一个宣传亮点。

9.CANVAS

接下来是CANVAS。讲CANVAS的几个性能优化点,用了这些东西,性能会有10倍的提升。

第一,每个CANVAS就是一个画布,我们要去渲染一个图形的时候, 我们是可以把它分层的。就是像PS里面一样,是一层两层三层。很多用户在做游戏的时候,直接把所有东西仿放到一个层里,一更新所有的东西都要更新。但如果你把它分层,你让背景放在背景层,角色放到角色层。这样的话,我要更新角色的时候,只会更新角色,背景层不需要变。让CPU干的事情更少了,性能自然而然就提升了。

第二,context.drawImage。不要去缩放图片,我们一开始就犯了一个错误,我们的美工做的图片始终跟我们不一致,然后我们要去缩放图片。因为本身设备它的图片大小是这样的,我们必须按比率缩放图片。缩放图片以后发现在低端设备下,比方说iPad或者iPhone就会非常卡,我们就想为什么?就进行代码上的分析,当用这个方法时候,花费的时候特别多。

第三,requestAnimationFrame。这是专门为渲染优化的一个方法。它本身的原理是这样的,当浏览器每过一桢的时候,会触发这个方法,当我在触发的时候,Canvas得到这个浏览器已经准备好做下桢的事情。如果用传统的方法,是不会去考虑你更多的东西,它只会知道我过了多少时间,我就要执行。假如说用户之前被阻塞了,每10秒钟执行这一方法,在10秒之内,实际他之前的事情还没有做完,然后这个事情就会被延后。它就是为了动画看上去更流畅而优化的,因为每一桢的时候,它就告诉你说,你可以做一些事情。

Via:InfoQ


扩展阅读:

来自Google的网站加速技巧大全:http://developer.51cto.com/art/200906/132210.htm

给网页设计师和前端开发者看的前端性能优化:http://www.oschina.net/translate/front-end-performance-for-web-designers-and-front-end-developers