导言
话说web前端的技术先驱,国外当属Yahoo,而国内则当属淘宝。所以它们的一举一动都是整个互联网的风向标。
就在前几天,偶然发现曾经一直困扰自己的鼠标悬停共用部分空间的问题在淘宝这里有了解决方案,马上拿来分析下。
悬停状态共用某一区域
用语言描述总是怪怪的,还是上图吧。
可以看到,按照正常的的导航制作方法,每个按钮元素都有自己的独立区域,它们之间互不影响。但在本例中,两个按钮却在悬停状态下共用了中间一条线的区域,它是如何实现的,我们一步步来分析。先看它的背景原图(其它部分不重要,无视之)
从原图上看,与我们传统的Sprite并无区别。再来看看Html结构
1
2
3
4
5
6
|
/*这是我自己写的,与淘宝原始结构稍有不同*/
<
ul
>
<
li
class
=
"nav1"
><
a
href
=
"#"
>1</
a
></
li
>
<
li
class
=
"nav2"
><
a
href
=
"#"
>2</
a
></
li
>
<
li
class
=
"nav3"
><
a
href
=
"#"
>3</
a
></
li
>
</
ul
>
|
此结构与正常结构并无区别,继续用普通方法定义CSS
1
2
3
4
5
6
7
8
|
li {
float
:
left
;
margin
:
0
;
padding
:
0
;}
li a {
float
:
left
;
display
:
inline
;
height
:
38px
;
text-indent
:
-10000px
;
overflow
:
hidden
;
background
:
url
(
'sprites.gif'
)
no-repeat
;}
li.nav
1
a {
width
:
103px
;
background-position
:
0
-19px
;}
li.nav
1
a:hover {
background-position
:
0
-57px
;}
li.nav
2
a {
width
:
91px
;
background-position
:
-103px
-19px
;}
li.nav
2
a:hover {
background-position
:
-103px
-57px
;}
li.nav
3
a {
width
:
106px
;
background-position
:
-194px
-19px
;}
li.nav
3
a:hover {
background-position
:
-194px
-57px
;}
|
效果如下
可以看出,当鼠标悬停在中间按钮时,并没有实现我们想要的效果,这当然也是在意料之中的。继续分析,我们希望鼠标悬停在中间按钮时按钮空间可以占据右侧竖线的一像素,这时想到当:hover时,元素可以使margin-left负一个像素,这样中间按钮就向左偏移了一个像素,为了使它的物理大小不发生变化,再给它定义一个像素的padding-left,这样该按钮在逻辑上增大了一个像素,物理上却没有变化,通过修改CSS,让:hover下背景定位向右偏一个像素
1
2
3
4
5
|
/*修改过的CSS*/
li a:hover {
margin-left
:
-1px
;
padding-left
:
1px
;}
li.nav
1
a:hover {
background-position
:
1px
-57px
;}
li.nav
2
a:hover {
background-position
:
-102px
-57px
;}
li.nav
3
a:hover {
background-position
:
-193px
-57px
;}
|
效果如下
这正是我们要的效果。经测试,发现进行负值操作后IE6下面出现bug
乍一看,我们的css没有生效,其实不然,为了解决此bug,按钮元素需要加上position:relative;
1
|
li a {
position
:
relative
;
float
:
left
;
display
:
inline
;
height
:
38px
;
text-indent
:
-10000px
;
overflow
:
hidden
;
background
:
url
(
'sprites.gif'
)
no-repeat
;}
|
这样,此bug就已经被修复了,IE6下显示正常。(对此bug的产生原因并没有深入研究,请高手补充)
到此,我们需要的效果就已经实现了。
等等!如果仅仅只是到此,这不过是一个小技巧而已,并没有什么。其实好戏才刚刚开始!
淘宝新的Sprite解决方案
在研究淘宝代码的过程中,发现它用了一个怪异的文档结构,如下图
可以看到,它没有用我们常用的<li class="nav1"><a href="#">1</a></li>方式制作按钮,而是采用了img图片,src中的地址正是用来做Sprite的背景图片。就在我对此百思不得其解的时候,猛然想起曾经看过的一篇关于浏览器对图片加载顺序的文章,我恍然大悟,难道它这么做是与此有关?
补充,如果称传统Sprite技术为CSS Sprite的话,该技术则为IMG Sprite
马上进行测试,文档结构如下
1
2
3
4
5
6
|
<
ul
>
<
li
class
=
"nav1"
><
a
href
=
"#"
>1</
a
></
li
>
<
li
class
=
"nav2"
><
a
href
=
"#"
>2</
a
></
li
>
<
li
class
=
"nav3"
><
a
href
=
"#"
>3</
a
></
li
>
</
ul
>
<
img
src
=
"37.jpg"
/>
|
结果如下
结果果然如预期所料,由于浏览器渲染时认为img为内容,而background只是修饰,所以在加载时,浏览器会先加载img图片,而最后才加载background的图片。浏览器这样认为,从逻辑上来讲是对的,但在实际运用中,我们往往会把导航做为最重要的部分,而且希望它能够最快的加载出来。由于浏览器的这个特性,我们往往不得不接受在加载大量img图片之后才看到导航缓缓出现,如果background在导航中仅仅只是修饰作用还好,如果像此例般,描述性文字是存在于图片中,继而让浏览者面长时间对空白等待,这就不可接受了。
经过上述分析,再回过头来重新看淘宝的结构,便能明白他这样做的良苦用心。
按照淘宝的风格,重新定义文档结构如下
1
2
3
4
5
6
|
<
ul
>
<
li
class
=
"nav1"
><
a
href
=
"#"
><
img
src
=
"img/sprites.gif"
/></
a
></
li
>
<
li
class
=
"nav2"
><
a
href
=
"#"
><
img
src
=
"img/sprites.gif"
/></
a
></
li
>
<
li
class
=
"nav3"
><
a
href
=
"#"
><
img
src
=
"img/sprites.gif"
/></
a
></
li
>
</
ul
>
<
img
src
=
"37.jpg"
/>
|
再次测试
同样,结果与预料中一样,它按照我们想要的顺序来执行加载。至于CSS同样是使用margin负值,并无太多新东西,直接贴出
1
2
3
4
5
6
7
8
9
10
11
12
|
li {
float
:
left
;
margin
:
0
;
padding
:
0
;}
li a {
position
:
relative
;
float
:
left
;
display
:
inline
;
height
:
38px
;
overflow
:
hidden
;}
li a:hover {
margin-left
:
-1px
;
padding-left
:
1px
;}
li.nav
1
a {
width
:
103px
;}
li.nav
1
a img {
margin
:
-19px
0
0
0
;}
li.nav
1
a:hover img {
margin
:
-57px
0
0
0
;}
li.nav
2
a {
width
:
91px
;}
li.nav
2
a img {
margin
:
-19px
0
0
-103px
;}
li.nav
2
a:hover img {
margin
:
-57px
0
0
-103px
;}
li.nav
3
a {
width
:
106px
;}
li.nav
3
a img {
margin
:
-19px
0
0
-194px
;}
li.nav
3
a:hover img {
margin
:
-57px
0
0
-194px
;}
|
效果同样是正确的,IE6同样(这里有一事不解,如果不对li a:hover进行定义,li.nav1 a:hover img的定义在IE6下便会失效)
到此,这个效果算是大功告成了。
当然,采用此方法也有弊端,由于按钮使用的是图片,在禁用CSS或WAP下浏览,浏览者就会因为图片没有进行过定位的整张Sprite图片而感到迷惑。当然,在通常情况下这种方法所带来的性能优化与所付出的代价相比是值得的。
后记
本文是我认真写的第一篇博文,并没有参考过多前辈的文章,所以此方法或许早有人推而广之了。在这里还是要感谢淘宝的UED们,你们的作品让我收获不已。同时以上分析仅仅是我的一家之言,或许淘宝他们之所以这么做还有其它理由,或许只有淘宝的UED们才讲得清了。最后,以上分析如有错误,请多多指正。