老话新说 CSS Sprites

陈养剑
陈养剑
08届浙大软件工程毕业生。 前网新恒天软件有限公司 Engineer,现淘宝前端开发。角色多变,目前从事 Flex/ActionScript 开发。喜欢被各色编程语言调戏,心系 Web。偶尔设计,做点 Good artists copy,Great artists steal 之类的事情。

以下内容来自陈养剑,由陈养剑撰写完成,陈养剑校对。并同步发布于陈养剑个人博客

版权声明文章版权归原作者和求是设计会共同拥有,转载时请以超链接形式标明文章原始出处和作者信息。

篇首语

求是设计会上出现一篇技术博客可能会让人奇怪。作为腆着脸加入求是 Network 的不从事设计的家伙,我鸭梨很大。发布本文目的单纯,希望 Web 设计者或者前端开发者,尽量使用正确的技术。通过一个简单容易的技术,节省带宽,提高速度。

什么是 CSS Sprites


某款红白机中的 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

先说基本,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-y
  • background-attachment 是否跟随页面滚动滚动,scroll, fixed
  • background-position

上述简写便是以这个顺序。详细语法可以参考 w3school。我们要讲的重头是 background-position。它的值的模式为 xpos yposxpos 可以是 left, center, right, x% 或者实际的像素单位的坐标;ypos 则是 top, middle, bottom, y% 或者实际坐标。

了解这个之后,我们就可以做个示例了。

示例一

 
<ul id="skyline">
  <li id="panel1b"><a href="#1"></a></li>
  <li id="panel2b"><a href="#2"></a></li>
  <li id="panel3b"><a href="#3"></a></li>
  <li id="panel4b"><a href="#4"></a></li>
</ul>

这个示例来自于 A List Apart。我们要做的事情就是,做一个看起来还不错的导航栏。

先把 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 的缓存控制设置为不缓存,每次都去服务器取资源的话,背景图会闪(解决方式)。

下面,我们通过求是设计会的博客导航栏,来做一个更为纯粹的示例。

示例二 - 求是设计会的导航栏

 
<ul id="menu">
  <li class="home"><a href="#">首页</a></li>
  <li class="translate"><a href="#">译文</a></li>
  <li class="job"><a href="#">工作</a></li>
  <li class="camp"><a href="#">活动</a></li>
  <li class="bookstore"><a href="#">读书</a></li>
  <li class="network"><a href="#">网络</a></li>
  <li class="microblog"><a href="#">微博</a></li>
</ul>

求是博客的导航栏,是一个比上例更为经典的 CSS Sprites 应用。它的背景图片 是一张包括常态、鼠标悬停之后、disable 之后三个效果的图片,并对应求是设计各个模块。

HTML 如前文所述,遵循简明扼要、语义化的规则。注意这里虽然最终效果里头并不需要文字,但是依然写明,并通过 text-indent 将它设为不可见。实际的代码中,还含有 titletarget 等属性。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 文件

示例三 - 让导航栏眨眼睛

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);
    });

这么做的问题也很明显。如果用户玩导航栏,在那上面划来划去,之前的淡入淡出效果并不会消失,而是慢慢做完。后果就是,鼠标甩过去之后导航栏闪个不停。修正的方法是每次mouseentermouseleave 的时候判断是否正在特效进行中,如果是就清除。下面的例子思路跟这个是一样的,然而实际解决起来似乎代码容易写得比较猥琐,所以这里就不再描述了。领会精神,明白不要让导航栏跳来跳去的必要就好。

示例四 - 让它真的动态一点

下面,我们做个高级一点的,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);
    }

示例五 - 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(){}})是其他参数,前者是动画的持续时间,后者是当动画完成之后的回调。示例中的回调将控件重置,主要是针对动画结束背景图位置有偏差的情况。

小结

总的来说,CSS Sprites 和 Web 设计新潮流中的其他诸多技术一样,老概念新做法。而 JavaScript 则因为各种甜蜜的库而成为 Web 设计者们开发网站原型时愈来愈频繁使用的语言,尤其是在各种 jQuery 插件的帮助下,JavaScript 可以被简易理解为特效、增强,而无需去理会其本身真正的语言思想。

回顾往昔,Web 的整个历史也是如此。对用户对开发者,都是可用性为先。

无觅相关文章插件

Category: Network, 教程

Tagged: , , ,

发表评论

使用腾讯微博登陆

使用新浪微博登陆

直接发表评论