初识CSS中的sprite技巧

所谓的CSS Sprites技术,就是将一些较小的图片统一排放在一个大的图片文件中,然后依靠代码来显示其中所需的图标部分,从而代替将这些小图标单独保存为多个较小图片文件。我们先来看一个例子:


这个页面主要实现的是一个日历功能(参考jquery javascript与css开发入门经典一书第七章AJAX)。最下方的控制按钮就是采用了CSS Sprites技术。当用户点击最左方的箭头时可查看前一个月的日历,点击右方按钮时可查看后一个月的日历,同时按钮颜色瞬变变换颜色。这个按钮的原始图片如下:


 

即在一张大图里包含了按钮所有的状态。一个更复杂的例子是我在虾米的@关注微博网页中见到过的。主要是右侧的导航栏(原始图片如右所示,也是包含了所有按钮的状态):


如果你曾经使用过某些游戏引擎,如Unity,就会发现这与其中的NGUI中的Atlas十分相似!(详细请看这里)在NGUI里,大贴图中的小贴图被称为Sprite(精灵),而大贴图被称为Atlas(图集)。使用Atlas主要是为了减少游戏的Drawcall,从而提高游戏性能,具体不在这里赘述。同样,CSS中使用Sprite技术的原因也类似:主要有以下几点:

  • 多个单独的小图片文件将会占用更多的存储空间和带宽,而只需花费少量的额外时间就可以方便地将多个分离的小图片创建为一个较大的图片。相反,如果把大图片切割成多个小图片后,这些小图片的总大小将超过原来的大图片。
  • 使用CSS Sprites后,根据用户的交互行为切换不同的图片的响应速度将变得更快,这是因为当用户单击一个命令按钮或者鼠标移动过某个页面元素时,浏览器无需下载这些图片文件——因为每个状态的文件已经被包含在同一个图片文件中。
  • 像服务器发送过多的HTTP请求将导致服务器变慢,都会给服务器带来一定的负载和延迟。而对一个较大的文件发送请求的负载,要比对多个较小的文件发起多个请求要快得多。
好啦,现在我们就以日历为例子,来具体讲讲是怎么实现的吧!

首先看下html中的主要框架代码(只粘贴控制按钮的部分):

<!-- The Calendar Control -->
  <div id = 'webExample_CalendarControls'>
    <div id = 'webExample_CalendarControlPrevious'></div>
    <div class = 'webExample_CalendarSeparator' id = 'webExample_CalendarSeparatorOne'></div>
    <div id = 'webExample_CalendarControlDay' class = 'webExample_CalendarControlToggle'></div>
    <div class = 'webExample_CalendarSeparator' id = 'webExample_CalendarSeparatorTwo'></div>
    <div id = 'webExample_CalendarControlWeek' class = 'webExample_CalendarControlToggle'></div>
    <div class = 'webExample_CalendarSeparator' id = 'webExample_CalendarSeparatorThree'>
      <div></div>
    </div>
    <div id = 'webExample_CalendarControlMonth' class = 'webExample_CalendarControlToggle'>
      <div class = 'webExample_CalendarControlOn'></div>
    </div>
    <div class = 'webExample_CalendarSeparator' id = 'webExample_CalendarSeparatorFour'>
      <div></div>
    </div>
    <div id = 'webExample_CalendarControlNext'></div>
  </div>
主要是把一个大的div(id = 'webExample_CalendarControls',即下图中最外面的红色区域)分成了九个div。向前和向后的按钮(<div id = 'webExample_CalendarControlPrevious'></div> <div id = 'webExample_CalendarControlNext'></div>)、按钮之间的四个分界线(<div class = 'webExample_CalendarSeparator' id = 'webExample_CalendarSeparatorOne'></div>,id分别包含One、Two、Three、Four),以及三个分别控制Day、Week、Month的按钮(<div id = 'webExample_CalendarControlDay' class = 'webExample_CalendarControlToggle'></div>等)。细心的童鞋可以发现在上述代码中,Month对应的div下比Day和Week多了一层下属div:<div class = 'webExample_CalendarControlOn'></div>,同时两侧的分界线的div下也分别多了一行空的div代码:<div></div>。正是因为如此,日历的初始画面是Month被选中,这是在CSS中实现的效果,换句话说,哪个按钮被选中,就会增加一层div,并将其类名设为'webExample_CalendarControlOn',哪个按钮两侧的分界线对应的div便会增加一个div区域,从而实现蓝色被按下的效果。

再来看它的样式表。首先是最外部div的样式表:

div#webExample_CalendarControls
{
    position : absolute;
    left : 50%;
    bottom : 8px;
    width : 188px;
    height : 25px;
    margin : 0 0 0 -94px;
}

主要是实现将图片居中显示,需要说明是的原图片的大小为188px*75px,因为共有三层状态所以height设为25。下面的样式规则定义了CSSSprites的基本布局,即九个较小div的布局:

div#webExample_CalendarControls div 
{
    position : absolute;
    height : 25px;
    top : 0;
    left : 0;
    background : url('../img/Navigation.png') no-repeat top left;
}

它把每个子<div>元素都设置成绝对定位,并把高度设为25像素,正好等于每个CSS Sprites图片的高度。最后把背景图片都设置为包含了所有图片的大图片。

div#webExample_CalendarControls div div
{
    width : 100%;
    height : 100%;
    background : url('../img/Navigation.png') no-repeat top left;
}

上面这条样式规则定义了每一个嵌套的<div>元素(即我们刚才说到的多出来的那些<div>元素)的样式,它首先将嵌套的<div>元素尺寸设为父元素的100%,然后将背景图片也设置为主图片。如上所说,这些按钮将用于显示主sprites文件中选中按钮和鼠标按下的图片状态。

div div#webExample_CalendarControlPrevious
{
    width : 23px;
}
div div#webExample_CalendarControlPrevious div
{
    background-position : 0 -50px;
}

终于到了关键的部分~上面两条样式规则用于定义向前按钮。要显示该按钮的初始状态,只需将默认的图片显示出来即可,并将width属性设置为23像素,即该按钮的宽度。而为了显示被鼠标按下的状态,只需调整该属性图片的位置,使之沿Y轴方向移动-50像素即可。在jQuery代码的协助下,当用户单击按钮时,背景图片沿Y轴移动-50像素后将最后一行图片显示出来(如有童鞋不懂background-position的用法,可以参见这里)。上面展示了Sprites的奥秘——其原理在于对背景图片位置的调整。


div div#webExample_CalendarControlNext
{
    width : 23px;
    left : 165px;
    background-position : -165px 0;
}
div div#webExample_CalendarControlNext div
{
    background-position : -165px -50px;
}

向后按钮的样式规则与向前按钮类似。向前和向后按钮都只有两个状态(未选中、被按下),而不像Day、Week、Month按钮有三个状态(未选中、被按下、选中),因此规则也多了一条。

/* Control Day */
div div#webExample_CalendarControlDay
{
    width : 46px;
    left : 24px;
    background-position : -24px 0;
}
div div#webExample_CalendarControlDay div.webExample_CalendarControlOn
{
    background-position : -24px -25px;
}
div div#webExample_CalendarControlDay div
{
    background-position : -24px -50px;
}

上面是对Day按钮的样式规则定义。第一个样式规则定义了Day按钮的<div>元素在其父类<div>元素中的位置。为了不与向前按钮重叠,样式规则将其left属性设置为24px(关于CSS定位的解释请看这里),即向前按钮的宽度加上1像素的分界线的宽度。Day按钮的宽度是46像素,且其初始状态图片位于主图片的(-24,0)位置处。第二条样式规则用于定义被选中状态的图片位置(此时该按钮增加了一个类名——webExample_CalendarControlOn,以区别按钮的被按下状态)。

/* Control Week */
div div#webExample_CalendarControlWeek
{
    width : 46px;
    left : 71px;
    background-position : -71px 0;
}
div div#webExample_CalendarControlWeek div.webExample_CalendarControlOn
{
    background-position : -71px -25px;
}
div div#webExample_CalendarControlWeek div
{
    background-position : -71px -50px;
}
/* Control Month */
div div#webExample_CalendarControlMonth
{
    width : 46px;
    left : 118px;
    background-position : -118px 0;
}
div div#webExample_CalendarControlMonth div.webExample_CalendarControlOn
{
    background-position : -118px -25px;
}
div div#webExample_CalendarControlMonth div
{
    background-position : -118px -50px;
}

而Week和Month按钮的定义与其相似,如上所示。下面介绍最后一类区域——分割线。

div#webExample_CalendarControl div.webExample_CalendarSeparator
{
    left : 23px;
    width : 1px;
    background-position : -23px 0;
}
div#webExample_CalendarControl div.webExample_CalendarSeparator div
{
    background-position : -23px -50px;
}
div#webExample_CalendarControl div#webExample_CalendarSeparatorTwo
{
    left : 70px;
}
div#webExample_CalendarControl div#webExample_CalendarSeparatorThree
{
    left : 117px;
}
div#webExample_CalendarControl div#webExample_CalendarSeparatorFour
{
    left : 164px;
}
接下来是Javascript代码部分,其中引用了jQuery代码。

$(document).ready(
    function () {
        $('div#webExample_CalendarControls > div').mousedown(
            function () {
                if (!$(this).hasClass('webExample_CalendarSeparator')) {
                    $(this).html("<div></div>");
                    $(this).prev().html("<div></div>");
                    $(this).prev().next("<div></div>");
                }
            }
        ).mouseup(
            function () {
                if (!$(this).hasClass('webExample_CalendarSeparator')) {
                    if ($(this).hasClass('webExample_CalendarControlToggle')) {
                        $('div#webExample_CalendarControls > div').not(this).empty();
                        $(this).find('div').addClass('webExample_CalendarControlOn');
                        $(this).prev().html("<div></div>");
                        $(this).prev().next("<div></div>");
                    } else {
                        $(this).empty();

                        if (!$(this).prev().prev().find('div').length) {
                            $(this).prev().empty();
                        }

                        if (!$(this).next().next().find('div').length) {
                            $(this).next().empty();
                        }

                        var $isNext = ($(this).attr('id').indexof('next') != -1);
                        $('div#webExample_Calendar').load('Example%20' + ($isNext ? 'Next' : 'Previous') + '.html');
                    }
                }
            }
        );
    }
);

第一段代码为ID为webExample_CalendarControls(即最外部的<div>元素)中所包含的每一个直接子元素<div>元素挂钩了一个mousedown事件,主要用以处理各个按钮被鼠标按下的状态,实现这些按钮的Sprites功能。首先,代码检查用户单击的是否是一个分界线如果不是,则为该<div>元素添加一个子<div>元素,并未该元素的前一兄弟节点和后一兄弟节点各添加一个子<div>元素,这样使得样式表中为这些按钮定义的被按下状态和按钮前后分隔栏状态被激活。类似于以下代码:

div div#webExample_CalendarControlNext div
{
    background-position : -165px -50px;
}

当用户释放鼠标按键时,即发生mouseup时间,即第二段代码。代码首先仍确定该元素不是一个分割线。再判断是否是一个Day、Week或Month按钮(类名为webExample_CalendarControlToggle)。如果是,则将其他所有非此元素的子<div>元素清空,清除其他按钮被选中状态,并为当前选中按钮的子<div>元素(在mousedown事件中添加)动态添加webExample_CalendarControlOn类名以实现被选中状态。剩下的就是对两侧分割线的修饰,就像在mousedown事件中做的一样。如果不是Day、Week或Month按钮,就说明是向前或向后按钮。因为这两个按钮没有被选中状态,因此一旦用户单击了这两个按钮,则应该将按钮状态恢复到未单击之前的状态。接下来的代码用于移除与向前或者向后按钮相连接的样式,但是仅在与该分隔栏相邻其他按钮未被选中时才移除。接下来的代码主要是为了加载日历,这里就不再讲述了。

————————————————————————————————————————————————————————————————

总结一下,CSS Sprites主要是利用了背景图片位置的变换,利用background-positionleft等CSS属性,加上不同状态下的类名来实现不同状态的转换。而类名主要是通过Javascript代码实现动态添加和去除的。

不知道看到这里大家懂了多少。。。如果有什么不懂或者建议神马的可以留言告诉我~


posted on 2012-09-22 21:05  王大王  阅读(226)  评论(0编辑  收藏  举报

导航