最近在修改Blogger范本之后,就想说应该拿个什么测速工具之类的检测一下Blog有什么问题。赫然想起之前电脑玩物 介绍了Google Page Speed Online ,他可以提供网站速度评测指标的分析与指导,似乎颇值得拿来参考。
分析之后,Page Speed Online指出「布丁布丁吃什么?」最优先需要修改的建议是「将图片合并到CSS合并图片」,也就是
它建议我应用CSS sprites技巧来改善网页的读取速度。
我用Firebug检查了一下图片的请求状况,发现光是
布丁的自我简介(2011年版) 就有56个请求(如上图)。趁着改良Blog的机会,我也想来练习做做看CSS Sprites,提升自己的程设能力。
<^>CSS Scrites原理
CSS Scrites是一种提高网页读取速度的技巧。其原理是降低图片请求(request)数量,以节省请求时额外消耗的速度。
概要作法是将网页中多张图片结合起来,再透过CSS语法调整,让每个位置都只显示该部分的图片。应用CSS Scrites之后,原本网页需要读取多张图片时需要跟伺服器请求的数量,会因为合并成一张图片,而大幅降低了请求数量,因此也节省了多次请求而消耗的速度。
其原理很容易懂,但是实作的时候却不容易。这需要熟悉HTML跟CSS语法才能进行,而且也需要分辨哪些图片可以应用CSS Scrites,或是哪些不行。
Page Speed Online有给我们一些建议,我尝试翻译如下:
- 合并会一起读取的图片:建议合并时常在同一页面中同时读取的图片。例如,每一页都会用到的同一组图示,就适合进行合并。相反的,每一次读取都会改变的动态图片,例如大头贴照片、或是在页面中会时常变更的图片,就不建议进行合并。
- 优先合并GIF跟PNG图片:GIF跟PNG图片使用无损压缩法,因此合并时并不会因此降低合并图片的品质。
- 优先合并小型图片:每一个图片请求都会需要固定的额外请求时间(request overhead),即使是下载小型图片,浏览器也会需要为此耗费额外的请求时间。藉由合并小型图片,将可以从每一次请求一张图片到一次请求就读取整张合并的图片,因此降低了额外请求的时间。
- 合并可以快取的图片:建议合并快取时间(caching lifetime)较长的图片。如果图片已经被浏览器快取,那浏览器就不需要再次下载该张图片,以提高读取的效率。
- 使用CSS Sprite服务:合并图片时,可以使用SpriteMe 之类的服务,让你轻易应用CSS sprites。
- 最小化合并图片中的空白处:为了显示图片,浏览器必须解压缩并解码该图片。图片的尺寸通常是跟图片的解析度成正比。因此,当合并图片中的空白处过多的时候,即使没有明显改变图片的档案大小,但是没有显示的像素依然会占据记忆体的用量,造成浏览器回应速度变慢。
- 合并使用同样色彩的图片:合并图片如果超过256色,将会让PNG从palette type改成使用truecolor type,并造成合并图片档案变大。为了产生最佳化的合并图片,要合并的图片最好都使用相同的256色。如果你的图片还有调整的空间,建议考虑想办法让你的合并图片色彩数量降低到256色。
既然Page Speed Online都建议我先从
SpriteMe 开始了,那就先用SpriteMe看看有什么好的建议吧。
<^>安装SpriteMe书签
SpriteMe是一个书签小工具(Bookmarklet)请把下面的连结拖曳到书签列上吧。
SpriteMe <^>使用SpriteMe
打开你要分析的网页,这边我一样以
布丁的自我简介(2011年版) 来做做看。SpriteMe将网页中的图片分成「Suggested Sprites」(建议合并)与「Non-Sprited Images」(不合并图片)这两种。以下是详细的列表:
接着让我们来看看SpriteMe为什么建议合并与不建议合并的理由。
<^>合并建议1:合并不重复的图片
第一项是「vertical, varied width」(垂直的,多变的宽度),直接翻译还真是看不懂是什么意思,但仔细一看他列出的图片,大多都是宽度、高度不等,而且在CSS中都是不重复(no-repeat)的图片,简单来说就是建议合并的大杂汇啦。
以下举几张例子:
<^>合并建议2:X轴重复、宽度相等
这一个建议很特别,他分析出两张宽度相等(760px)的背景图,而且他们也都是设定为X轴重复(repeat-x),因此也适合合并成一张图。
这两张图各别是:
他们都Y轴的渐层效果。仔细一看,似乎这背景图也不需要这么宽,就能用X轴重复达到填满的效果了。
除了合并的建议之外,SpriteMe也给了不合并的建议。我把图片的长宽尺寸与理由列举如下:
<^>SpriteMe的使用过程
SpriteMe不仅仅是分析建议很详细,就连使用起来也很容易。
上面提到SpriteMe建议我合并X轴重复、宽度相等的图片,而该区右上角有个「make sprite」按钮,就能够自动产生CSS Sprite的效果。
按下去之后稍待一会,图片就合并成一张了。打开项目的详细事项,里面记载着SpriteMe调整过的元素内容。点选该元素,他会在元素外围描绘上红色的框线。
他同时也提供了一张合并后的图片,如上图。尽管我很好奇的是,不知道为什么SpriteMe合并之后的图片间会有这么多空白间隔(padding)。可能是预留排版出错时的缓冲空间吧?
SpriteMe直接将合并之后图片的语法写在受到调整的元素中。上图是页首背景图片直接套用了SpriteMe的合并图片,可以看到他以background-image跟background-position设定直接写在元素的style属性中了。
虽然右上角有个「export CSS」功能,可以把合并后的图片与语法输出成CSS。只是在Chrome里面发生了JavaScript错误而无法执行,后来我改用Firefox 4来操作,就能够开启SpriteMe Export CSS网页。
Export CSS网页中,先告诉我刚刚我合并的图片网址。
然后下面列出了这个网页使用的CSS档案,并尝试在这些档案中找寻刚刚修改的元素设定位置。可惜因为Firefox的跨网域限制,SpriteMe没办法自动帮我分析这些CSS档案的内容。
接着他列出CSS的建议修改方式,包括删掉原本的图片,并替换上新的图片。这个建议可以让我轻易地修改CSS档案,非常地实用。其内容如下:
DIV id=header class=header section {
background-image: url("http://www.blogblog.com/thisaway/bg_header.gif")
background-image: url("http://www.jaredhirsch.com/ coolrunnings/public_images/a98ceddb07/spriteme2.png");
background-position: 0px -18px;
}
DIV id=footer class=footer section {
background-image: url("http://www.blogblog.com/thisaway/bg_footer.gif")
background-image: url("http://www.jaredhirsch.com/ coolrunnings/public_images/a98ceddb07/spriteme2.png");
background-position: 0px -118px;
}
当然,我会把SpriteMe产生的合并图片下载之后,上传到自己的空间,然后再把之间的网址改成我的空间,这样才不会造成SpriteMe伺服器的负担。
修改完成之后,图片的请求数从原本的56个降低为55个啰。
<^>分组宽度相当、颜色相近的图片来建立合并图片
如果直接采用SpriteMe的建议,把宽度、高度不等的图片直接合并,就会出现像上面的合并图片。在Page Speed Online建议合并图片要尽量降低空白处,而上图很明显的违反了这个建议。多次尝试之后,我发现SpriteMe只会将图片垂直排列来合并。因此,如果只将宽度相当的图片进行合并,就能够将合并图片的空白处降低到最小。
上图是SpriteMe预设的建议,图片的宽度从760px到10px都有,合并起来将会出现相当多的空白。还好,SpriteMe也提供了让使用者自订合并图片的功能,请按下上面的「new sprite」按钮。
这时候上面就会出现新的合并列表,但是是空的。
你可以从下面的图片,将宽度差不多的图片拖曳到这个区块,SpriteMe就会依照你指定的图片建立新的合并图片。
我将合并图片分成三组,个别是宽度为10px的图片、宽度为760px的图片,以及宽度为47px到54px之间的图片来进行合并。
合并之后的结果如下:
这样子空白处就减少很多啰。
另外Page Speed Online也建议将合并图片的颜色数量降低到256色之内,这也是分组时的一个参考依据。
<^>SpriteMe忽略了<img>图片
仔细比较一下Page Speed Online给的建议,会发现SpriteMe还忽略了很多图片。再细部分析一下,这些图片都是以<img>图片标签显示的内容。
上面的「订阅所有留言」功能就用了大量的<img>标签,而且都是固定常出现的小型图片。Page Speed Online建议我合并这些图片,但是SpriteMe并没有分析到这边。
为了要让SpriteMe侦测到这些图片,我必须先把<img>中src指定的图片,改成以background-image背景图片的方式来显示。
原本我是想在<img>直接设定背景图片,但是效果却不如预期。Firefox中,只有将<img>显示型态设为block的时候,才能顺利显示背景图。因此,我决定将<img>改成<div>,并以CSS的background-image来显示图片。
<^><img>改成<div>背景图
以下我以这个「订阅所有留言」的功能来说明修改的过程。这是一个写在小工具区的HTML程式码,内容如下:
< div class ="subscribe-wrapper subscribe-type-COMMENT" >
< div style ="display: none;" id ="SW_READER_LIST_Subscribe1COMMENT" class ="subscribe expanded subscribe-type-COMMENT" >
< div class ="top" >
< span onclick ="return(_SW_toggleReaderList(event, "Subscribe1COMMENT"));" class ="inner" >
< img src ="http://img2.blogblog.com/img/widgets/arrow_dropdown.gif" class ="subscribe-dropdown-arrow" />
< img border ="0" align ="absmiddle" src ="http://img1.blogblog.com/img/icon_feed12.png" class ="feed-icon" alt ="" />
订阅所有留言
</ span >
< div class ="feed-reader-links" >
< a target ="_blank" href ="http://www.google.com/ig/add?source=bstp&feedurl=http%3A%2F%2Fpulipuli.blogspot.com%2Ffeeds%2Fcomments%2Fdefault" class = "feed-reader-link" >
< img src ="http://img1.blogblog.com/img/widgets/subscribe-google.png" />
</ a >
< a target ="_blank" href ="http://www.bloglines.com/sub/http://pulipuli.blogspot.com/feeds/comments/default" class ="feed-reader-link" >
< img src ="http://img1.blogblog.com/img/widgets/subscribe-bloglines.png" />
</ a >
< a target ="_blank" href ="http://www.netvibes.com/subscribe.php?url=http%3A%2F%2Fpulipuli.blogspot.com%2Ffeeds%2Fcomments%2Fdefault" class ="feed-reader -link" >
< img src ="http://img1.blogblog.com/img/widgets/subscribe-netvibes.png" />
</ a >
< a target ="_blank" href ="http://www.newsgator.com/ngs/subscriber/subext.aspx?url=http%3A%2F%2Fpulipuli.blogspot.com%2Ffeeds%2Fcomments%2Fdefault" class = "feed-reader-link" >
< img src ="http://img1.blogblog.com/img/widgets/subscribe-newsgator.png" />
</ a >
< a target ="_blank" href ="http://add.my.yahoo.com/content?url=http%3A%2F%2Fpulipuli.blogspot.com%2Ffeeds%2Fcomments%2Fdefault" class ="feed-reader -link" >
< img src ="http://img1.blogblog.com/img/widgets/subscribe-yahoo.png" />
</ a >
< a target ="_blank" href ="http://pulipuli.blogspot.com/feeds/comments/default" class ="feed-reader-link" >
< img align ="absmiddle" src ="http://img1.blogblog.com/img/icon_feed12.png" class ="feed-icon" />
Atom
</ a >
</ div >
</ div >
< div class ="bottom" ></ div >
</ div >
< div onclick ="return(_SW_toggleReaderList(event, "Subscribe1COMMENT"));" id ="SW_READER_LIST_CLOSED_Subscribe1COMMENT" class ="subscribe" >
< div class ="top" >
< span class ="inner" >
< img src ="http://img2.blogblog.com/img/widgets/arrow_dropdown.gif" class ="subscribe-dropdown-arrow" />
< span onclick ="return(_SW_toggleReaderList(event, "Subscribe1COMMENT"));" >
< img border ="0" align ="absmiddle" src ="http://img1.blogblog.com/img/icon_feed12.png" class ="feed-icon" alt ="" />
订阅所有留言
</ span >
</ span >
</ div >
< div class ="bottom" ></ div >
</ div >
</ div >
程式码有点长,不过构造还算简单。大致上需要改的有两种类型,以下一一叙述作法。
<^>修改显示类型为block(区块)图片
有些<img>被赋予了display:block;的设定,表示他会跟<div>一样以block(区块)的样式显示。在「订阅所有留言」中,下拉选单的各种图示都是以这种形式呈现。
这种形式的<img>图片可以很容易地修改成<div>背景图,也不容易影响排版。
让我们先看看Add to Google这个图示的原始码:
1: < a target ="_blank"
2: href ="http://www.google.com/ig/add?source=bstp&feedurl=http%3A%2F%2Fpulipuli.blogspot.com%2Ffeeds%2Fcomments%2Fdefault"
3: class ="feed-reader-link" >
4: < img src ="http://img1.blogblog.com/img/widgets/subscribe-google.png" />
5: </ a >
这是一个<a>连结标签包含着<img>图片标签的元素。在其他的CSS当中,此处的<img>被设定为display:block;,因此我们可以考虑直接把这种<img>换成<div>,并加上额外的CSS设定。
修改的过程有几个步骤:
- 在<img>后面建立<div>,并给予适当的class名称,以便后续CSS设定中可以正确地选择到该<div>。这边要注意的是,必须完整撰写<div></div>标签,而不能用<div/>这种空标签喔。
- 增加额外的CSS设定,包括:移除原本的<img>
- background-image: url(图片网址):指定<img>读取的图片
- background-repeat: no-repeat:不重复图片
- width & height:根据图片大小设定
以下是修改之后的元素程式码与CSS设定:
1: < a target ="_blank" href ="http://www.google.com/ig/add?source=bstp&feedurl=http%3A%2F%2Fpulipuli.blogspot.com%2Ffeeds%2Fcomments%2Fdefault" class ="feed-reader-link" >
2: < div class ="subscribe-google" ></ div >
3: </ a >
4: < style type ="text/css" >
5: .feed-reader-link .subscribe-google {
6: background-image: url(http://img1.blogblog.com/img/widgets/subscribe-google.png);
7: background-repeat: no-repeat;
8: width: 104px;
9: height: 17px;
10: }
11: </ style >
尽管大致上改到如此就可以告一段落,但是这段HTML还有继续改善的空间。
由于<a>标签中只包含<img>(后来被我换成<div>了)一个元素,因此我们可以考虑将样式直接套用到<a>中,省略掉多余的<div>。
更进一步修改之后的程式码如下。必须注意的是,CSS中选择器改成指定<a>的class名称,并加入显示类型display: block的设定啰:
1: < a target ="_blank" href ="http://www.google.com/ig/add?source=bstp&feedurl=http%3A%2F%2Fpulipuli.blogspot.com%2Ffeeds%2Fcomments%2Fdefault" class ="feed-reader-link subscribe-google" >
2: </ a >
3: < style type ="text/css" >
4: .feed-reader-link.subscribe-google {
5: background-image: url(http://img1.blogblog.com/img/widgets/subscribe-google.png);
6: background-repeat: no-repeat;
7: width: 104px;
8: height: 17px; display: block;
9: }
10: </ style >
修改之后的程式码又更简洁了。实际应用的时候,CSS设定会集中在其他档案中,而不会像上面一样跟HTML写在一起。
<^>修改显示类型为inline(同轴)的图片
<img>图片的预设显示类型是inline,意思是会跟文字一样一起排列。而<div>却是block显示类型,会强制将后面的内容换行。
有些时候,<img>会以原始的inline跟其他文字一起排列,这种情况要改成<div>就比较困难,通常还要搭配float浮动样式、margin外距调整、以及搭配一些CSS修改经验才能达成。
这次我要修改的是「订阅所有留言」左边的RSS图片,这张图片与「订阅所有留言」文字一起排列,是以预设的inline显示类型呈现。它的原始码如下:
1: < span
2: onclick ="return(_SW_toggleReaderList(event, "Subscribe1COMMENT"));" >
3: < img border ="0"
4: align ="absmiddle"
5: src ="http://img1.blogblog.com/img/icon_feed12.png"
6: class ="feed-icon" alt ="" />
7: 订阅所有留言
8: </ span >
我为它进行的修改步骤为:
- 在<img>后面建立<div>,并给予适当的class名称,以便后续CSS设定中可以正确地选择到该<div>。
- 增加额外的CSS设定,包括:移除原本的<img>
- background-image: url(图片网址):指定<img>读取的图片
- background-repeat: no-repeat:不重复图片
- width & height:根据图片大小设定
- float: left:因为该图片位于文字的左方,所以用此设定
- margin 调整外距
修改后的程式码与新增的CSS设定如下:
1: < span
2: onclick ="return(_SW_toggleReaderList(event, "Subscribe1COMMENT"));" >
3: < div class ="feed-icon" ></ div >
4: 订阅所有留言
5: </ span >
6: < style type ="text/css" >
7: .inner .feed-icon {
8: background-image: url(http://img1.blogblog.com/img/icon_feed12.png);
9: background-repeat: no-repeat;
10: width: 12px;
11: height: 12px;
12: float: left;
13: margin-top: 2px;
14: margin-left: 7px;
15: }
16: </ style >
修改之后,因为加上了margin外距调整,感觉比之前的<img>更顺眼了点呢。
<^>显示图片让SpriteMe能够侦测
即使把<img>改成<div>的背景图片了,还要记得把他们显示出来(visible),SpriteMe才能够侦测并判断它是否适合成为合并的图片,否则会被归类成不适合合并的图片。
在分析之前,我先将「订阅所有留言」的选单打开,再使用SpriteMe的功能,如上图。
这下子SpriteMe总算侦测到刚刚我修改的<div>背景图,而且将它列入建议合并的图片中了。
<^>其他CSS Sprites技巧
SpriteMe是分析背景图片(background-image)以达到CSS Sprites技巧的效果,不过除了背景图片之外,还有其他技巧可以使用。
CSS拥有无限的可能性,而且随着浏览器跟标准不断地改变,未来也可能会有更好用的技巧出现也说不定吧。
<^>真的有必要做CSS Sprites吗?
尽管CSS Sprites能够降低图片请求数量、提升网页读取的速度,但任何技术都不是万灵丹,在使用CSS Sprites的时候我也发现到一些限制,在此提出来跟大家讨论一下:
<^>合并后的图片难以管理
现在我们将多张图片合并成一张大型图片,用CSS Sprites设定背景图片与背景位置。如果未来需要变更其中一张图片的高度,这不仅要把之前所有的图片都找回来,还需要修正下面的图片的背景位置。
为了避免这种情况发生,在使用CSS Sprites技巧时,尽量挑选不会变更的图片,或是将可能会一起变更的图片一起合并,要改的时候也一起改。
最后,要记得保留合并前的旧图片,以免未来要重新合并时找不到图片。我在CSS样式档中,就会将旧图片的连结先注解掉,让未来还有回复成原本图片的机会:
1: #header {
2: background-color : #634320;
3: /*background-image: url(http://www.blogblog.com/thisaway/bg_header.gif);*/
4: background-image : url(http: //dl.dropbox.com/u/717137/blogger/img/bg_header_footer.png);
5: background-position : 0px -18px;
6: background-repeat : repeat -x;
7: }
<^>将<img>转换成<div>的人工成本与风险相当高
SpriteMe不会分析<img>中的图片,而需要我们手动将<img>转换成<div>背景图,才能顺利让SpriteMe分析。前面我也简单地叙述了两种转换的过程,不过实际使用时一定会遇到许多更棘手的情况。
最大的问题仍是在<img>以inline显示类型与<div>的block显示类型基本上就有很大的差别。由于<div>一定要设定为block才能设定宽度与高度,所以不能单纯转换为inline显示类型。
当然,还有许多CSS设计技巧可以回避掉类似的问题,但不论是哪种方法,都需要相当有经验的CSS设计师才能做到。随随便便套用CSS设定,都会带来版面构造破坏的风险。
<^>区块有重复延展的需求时,尽量不要做CSS Sprites
前面SpriteMe的建议都是针对不重复、或是针对X轴重复的图片建议合并,但是有时候SpriteMe的建议也不是万能,它并不能预测到你这个区块未来是否有需要延展的空间。
有一种CSS设计,是在指定不重复(background-repeat: no-repeat)的背景图片(background-image),同时搭配背景颜色(background-color)的情况。这种设计并不是让背景图片重复来填满背景,而是用背景颜色来填满,但只有特定的地方显示背景图片而已。
「布丁布丁吃什么?」的范本中时常出现这种设计,例如上图的页首区块。它的CSS设定如下:
1: #header {
2: background-color : #634320;
3: background-image : url(http: //www.blogblog.com/thisaway/bg_header.gif);
4: background-repeat : repeat -x;
5: }
如果当页首资料量太多的时候(我绕了好多远路),背景还能顺利地延展开来,不会让版面变得很奇怪。
但是如果照前述的方式将页首区块的背景合并成CSS Sprites,当资料量一多的时候,就会发现页首丧失了延展性,背景变得很奇怪了。
即使不是资料量变多,而只是单纯地缩小视窗宽度,资料自动往下挤压而造成额外的高度需求,那也可能发生类似的破版画面。
尽管有很多技巧可以避免上述的问题,不过这边我想说的是,当区块有扩增、延展的需求时,不要轻易地将它的背景图片做成CSS Sprites,以免徒增版面发生错误的风险。
经过CSS Sprites调整之后,
布丁的自我简介(2011年版) 从56个图片请求数降低到了47个请求数。老实说,感觉成效并不明显。
另一方面主要的原因在于大多数图片并不是我能掌控的范围,像是Google Friend Connect的大头像、Plurk的显示图片。结果Page Speed Online还是建议我去合并这些图片,我也没辄了。
附带一提,我并没有特别去注意下载速度与图片压缩的数量,因为实际上都是小图片的整并,比较这一点点差距没有多大意义。
总之,经过这次的把玩,总算对CSS Sprites这个技巧有更深入的了解。以后在设计网页的时候,不妨预先考量到可以进行CSS Sprites的转换空间,将<img>以<div>替代,然后最后再利用SpriteMe来动手术,努力提升提升网页读取效率吧。