CSS Sprites
What is CSS Sprites
Web 是个很神奇的地方,很多时候会给你“OMG! The old things is new again!”的错觉。
比如初衷是数据表格的 <table/>
标签,会被用来取得更有弹性的布局效果;
比如 <h1/>
还是 <p class="title"/>
作为网站的标题,都可以成为争辩的话题;
比如 <canvas/>
和 Flash 的比较。
CSS Sprites 也是如此。
我们先把 CSS Sprites 这名字拆分一下,CSS 和 Sprites,CSS 么大家都知道,Cascading Styles Sheets,层叠样式表。 只不过它的层叠算法有的时候很奇怪,代码结构看着也不想表。但这不妨碍你把它去繁取简, 理解为样式(Style)。事实上在 Flex 里头,Adobe 就是这么做的。
而 Sprites 呢?查下字典,意为小精灵。在计算机图形的世界,
这个词的意思
是一张二维的图片,用来嵌入到场景当中。通常它是由多张小图拼成的。同样,在 ActionScript 里面,
有个 Class
唤作
Sprite。
采取这种做法的原因,跟现在一样,是
速度。
把一张大图载入内存,每次重绘的时候只改变它的显示位置,比每次重绘都把图片数据搞来搞去要经济很多。
而两个词结合起来,就是本文要说的,使用 CSS 应用古老的 Sprites 技术。在开始之前,我们先了解一下为什么。
在 YUI 的 Yslow 网站优化工具评分标准里头,有一项重要的评分标准是
减少资源请求次数。
网页上各种菜单的图标,布局里的背景图片,
按钮等等,可以将这些图片弄到一张大图上,再在 CSS 中通过 background-position
制定需要显示的图片位置。
也就是让浏览器自己去调整显示结果,取得“抠”图的效果。它是一项网页优化的技术。
是否要使用这项技术已经不是个问题了,它无处不在。
【】有趣的是,在笔者写本文的时候, CSS-Tricks 也发了篇博文。在“为什么”的部分说的比较清楚。
CSS
CSS 的中文意思,前面介绍过了。对于它的意义、简单语法等,可以参考我的 HTML & CSS。
先说基本,background
属性的语法。示例声明(来自鹿木)。
这是一个简写。在 CSS 中这种简写很多,使用最多的就是 padding 和 margin 的语法。
background: #000 url("/picture/3003_m.jpg") no-repeat scroll center top;
接着说背景声明。详细属性分为:
background-color
各种颜色;background-image
背景图,可以是url()
,也可以是 BASE64 编码的图片;background-repeat
重复模式,no-repeat, repeat, repeat-x, repeat-ybackground-attachment
是否跟随页面滚动滚动,scroll, fixedbackground-position
上述简写便是以这个顺序。详细语法可以参考 w3school。
我们要讲的重头是 background-position
。它的值的模式为 xpos ypos
。
xpos
可以是 left, center, right, x% 或者实际的像素单位的坐标;
ypos
则是 top, middle, bottom, y% 或者实际坐标。
了解这个之后,我们就可以做个示例了。
Demo 1
这个示例来自于 A List Apart。 我们要做的事情就是,把上一节中的图片作 Sprites,做一个看起来还不错的导航栏。最终效果如右图所示:
先把 HTML 搞起来。通常写 HTML 的原则有两个:简介其一、语义化其二。把语义化作为重要准则的原因也有两个, SEO 和可用性。因为搜索引擎的爬虫和屏幕阅读器都不会识别通过 CSS 后加的图片、文字,它们只认 HTML。 代码如右图下方所示。
接下来,配上 CSS。首先,将这个菜单的高宽设置为背景图大小,配上背景图;把 <li/>
的默认属性重置掉。
关于 position
的部分说起来容易话长,
对这方面不熟的童鞋建议看看《十步走学会 CSS Position》。
简单地说这里的代码就是让每个菜单条目使用绝对定位,方便布置鼠标悬停的效果。
#skyline { width: 400px; height: 200px; background: url(test-3.jpg); margin: 10px auto; padding: 0; position: relative; } #skyline li { margin: 0; padding: 0; list-style: none; position: absolute; top: 0; } #skyline li, #skyline a { height: 200px; display: block; } #panel1b {left: 0; width: 95px;} #panel2b {left: 96px; width: 75px;} #panel3b {left: 172px; width: 110px;} #panel4b {left: 283px; width: 117px;}
布置好菜单之后,通过 a:hover
加上特效:
#panel1b a:hover { background: transparent url(test-3.jpg) 0 -200px no-repeat; } #panel2b a:hover { background: transparent url(test-3.jpg) -96px -200px no-repeat; } #panel3b a:hover { background: transparent url(test-3.jpg) -172px -200px no-repeat; } #panel4b a:hover { background: transparent url(test-3.jpg) -283px -200px no-repeat; }
这个例子应用 CSS Sprites 的方式有点特别,经典应用方式是背景图片只指定一次,
需要的时候再去改 background-position
。这个做法是个 Workaround,而非 Fix,主要原因是
IE6 里头的一个 bug:如果 IE 的缓存控制设置为不缓存,每次都去服务器取资源的话,
背景图会闪(解决方式)。
下面,我们通过求是设计的博客,来做一个更为纯粹的示例。
Demo 2 - Blog of Qiushi Design
求是博客的导航栏,是一个比上例更为经典的 CSS Sprites 应用。 它的背景图片 是一张包括常态、鼠标悬停之后、disable 之后三个效果的图片,并对应求是设计各个模块。
HTML 如前文所述,遵循简明扼要、语义化的规则。注意这里虽然最终效果里头并不需要文字,
但是依然写明,并通过 text-indent
将它设为不可见。实际的代码中,还含有 title
、target
等属性。title
用来鼠标悬停时提示说明,target="_blank"
则告诉浏览器要新开一个窗口或者标签。
此处为了简明,略去了。有 Firebug 的童鞋,可以查看右侧的示例元素。
关于 #menu li a
的 CSS 声明:
#menu li a { background:url("http://blog.qiushid.com/wp-content/themes/qiushid/images/navitems.gif") no-repeat scroll 0 -468px transparent; display:block; float:left; height:26px; text-indent:-999em; width:85px; }
因为每个模块对应的图标都不一样,所以实际位置通过 #menu li a.class
指定。以首页(home
)为例:
#menu li.home a { background-position: 0 -182px; } #menu li.home a:hover { background-position: 0 0; }
其余代码可以参考求是博客的 CSS 文件。
Demo 3 - (Blink) With JavaScript
A List Apart 在几年后又发一文,
加入了 JavaScript 的部分。
使用 jQuery 的 .fadeIn(), slideDown()
等 UI 方面的方法,来模拟淡入淡出等效果。
原文的示例代码不很清爽,就不再转用。下面是参考
hover
文档
做的求是博客导航栏的基础上制作的示例。
有没有 jQuery is the new <blink/>
的错觉?廉价的特效,廉价的代码:
$("#qiushid-menu").find("a").hover(function() { $(this).fadeOut(500, function() { $(this).fadeIn(500); }); }, function() { $(this).fadeIn(100); });
这么做的问题也很明显。如果用户玩导航栏,在那上面划来划去,之前的淡入淡出效果并不会消失,
而是慢慢做完。后果就是,鼠标甩过去之后导航栏闪个不停。修正的方法是每次
mouseenter
、mouseleave
的时候判断是否正在特效进行中,如果是就清除。
下面的例子思路跟这个是一样的,所以这个例子的方法我就不再说了。
Demo 4 - (Marquee) With JavaScript
下面,我们做个高级一点的,jQuery 版本的 <marquee/>
。
我们知道,求是设计的导航栏的背景图是在鼠标悬停(hover)的时候,通过调整
background-position
得到效果。现在呢,就要用 JavaScript 让这个过程慢一点。
实际执行代码请查看本页面源代码末尾的 JavaScript 部分,这里就伪代码介绍一下。
var yPosMap = { // 最独立的办法自然是从 CSS 里头取,但是感觉费力 home: [-182, 0], camp: [-260, -78], bookstore: [-286, -104], microblog: [-338, -156], network: [-312, -130] }; var timerMap = {}; // 每个动画的定时器应该是独立的 $marqueemenu.find('a').hover(function() { animate(this, 'show'); }, function() { animate(this, 'hide'); }); function animate(block, mode) { clearTimers(clsName); // 清除正在进行的动画 if (reachTargetPosition()) { reset(); return; // 已经到了 } timerMap[clsName] = setInterval(function() { pos = getYPos($a); /* * 最关键的语句,CSS Sprites 就是这么回事: */ $a.css('background-position', '0 '+(pos+delta)+'px'); if (reachTargetPostion()) { clearInterval(timerMap[clsName]); reset(); } }, 10); }
Demo 5 - jQuery.animate()
我们的示例到这还没结束。
jQuery 自带了个 .animate()
,但是对个别 CSS
属性的动画支持并不好。有达人做了个
jQuery Bg Pos 补丁
让 jQuery 可以以 background-position
做动画。实际效果可以看右方示例。
有没有觉得示例四很有重复发明轮子的嫌疑?
详细内容,不妨移步 Snook 的博文 和他的示例。这里简单讲述一下用法。
首先要在页面上引入 jQuery 与 jQuery.bgPos 插件。后者的内容并不多, 不妨把它并入你的网站的主 JavaScript 文件里头去。
lt;script type="text/javascript" src="jquery-1.4.2.min.js">lt;/script> lt;script type="text/javascript" src="jquery.bgpos.js">lt;/script>
然后是使用:
$('#snook-menu').find('a') .css( {backgroundPosition: "-20px 35px"} ) .mouseover(function(){ $(this).stop().animate({backgroundPosition:"(-20px 94px)"}, {duration:500}) }) .mouseout(function(){ $(this).stop().animate({backgroundPosition:"(40px 35px)"}, {duration:200, complete:function(){ $(this).css({backgroundPosition: "-20px 35px"}) }}) });
为需要特效的空间注册鼠标移入移出的响应函数,也可以使用前面示例的 .hover()
简写。
重点代码是 .animate()
的参数部分。第一个就是特效的结束值,.animate()
会根据这个值计算应该如何动画,详细的过程与示例四的 animate()
函数相仿。
第二个({duration:200, complete:function(){}}
)是其他参数,前者是动画的持续时间,
后者是当动画完成之后的回调。示例中的回调将控件重置,主要是针对动画结束背景图位置有偏差的情况。
Summary
看完本文,了解了这四个例子之后,我想你对 CSS Sprites 应该有很清楚的认知了。
最后广告一下:做前端开发,目前为止最好用的工具依然是 Firefox + Firebug 组合。 Google Chrome 的开发者工具也已经越来越完善,但是小细节总有不愉处,可能是我习惯了前者的缘故。 IE9 如今成了 Underdog,但是也在慢慢赶上。此外,最好整个 IE Tester。 当然,如果你的机器上还有 IE6,直接测也行。不过,
你的电脑上还有 IE6?