前几天的时候在QQ群里有一位朋友遇到了这样一个问题,是关于布局转换的,在说这个问题之前,我希望给大家介绍一下什么叫布局转换。
首先我们经常会遇到这种布局:
我想大家一看就会想到外面一个父级,里面6个子元素,一浮动,OK了,只有脑子稍稍不正常的人才会用position:absolute定位一个一个定。我和大家想的也一样,因此这样一个布局自然就出来了
1 <ul id="ul1" class="clear"> 2 <li>1</li> 3 <li>2</li> 4 <li>3</li> 5 <li>4</li> 6 <li>5</li> 7 <li>6</li> 8 </ul>
对应的样式为
1 .clear:after{ content:""; clear:both; display: block;} 2 #ul1{ margin:100px auto; width:360px; border:1px solid #000;} 3 #ul1 li{ width:100px; height:100px; background:#f00; float: left;}
这个时候,一切看起来都很好,但是如果来了这样一个需求:需要给这6个li加一个动画,希望移入每个li的时候从中间放大,类似这种效果
首先我们可以考虑一下做这个效果的思路:假如 li 的宽和高都变成了原来的两倍,但同时 left 和 top 也变化了,left 在原来的基础上减小的值是原来矩形宽度的一半,top 减小的值也是原来矩形高度的一半,这个时候,大家感觉到什么问题了吗?
我所有的 li 都是浮动的,哪里来的left和top啊?这时,一定马上有人会有反应说:这还不简单,改成定位呗。
但是大家想一想,这种布局适合定位吗?如果定位的话,给每一个li设置left和top值,那将会是一件极其恶心的事情,因此答案一定是否定的。
既然想用浮动,又想获取left和top的值,这怎么办呢?这个时候就需要用布局转换,顾名思义,就是把浮动的布局转换为定位的布局,从而方便我们拿到它们的left和top值,很容易的能够想到:
1 window.onload=function(){ 2 var oUl=document.getElementById("ul1"); 3 var aLi=oUl.getElementsByTagName("li"); 4 5 for(var i=0;i<aLi.length;i++){ 6 aLi[i].style.position="absolute"; 7 aLi[i].style.left=aLi[i].offsetLeft+"px"; 8 aLi[i].style.top=aLi[i].offsetTop+"px"; 9 } 10 };
这时在浏览器当中预览,我们就可以看到:
如果效果和我的一样,那么恭喜你,这种做法是错误的,很明显,效果不是咱们想要的,那么问题到底出在哪儿呢?
我们在这里打一个断点(第7行):
1 window.onload=function(){ 2 var oUl=document.getElementById("ul1"); 3 var aLi=oUl.getElementsByTagName("li"); 4 5 for(var i=0;i<aLi.length;i++){ 6 aLi[i].style.position="absolute"; 7 debugger; 8 aLi[i].style.left=aLi[i].offsetLeft+"px"; 9 aLi[i].style.top=aLi[i].offsetTop+"px"; 10 } 11 };
预览之后发现一个很诡异的界面:
1之后居然是3,2跑到哪里去了?
好的,写到这里,本文的精华部分即将开始:
我们在第7行打了断点之后,当循环进行第一遍时,i的值为0,也就是说此时我们为第1个li设置了绝对定位,此时要注意,设置了绝对定位的元素是要脱离文档流的,何为脱离文档流?就是指元素会漂起来,因此,1就会漂在2 3 4 5 6的上方,此时1将会把2遮住,所以我们就看不见2了,此时你再获取1的offsetLeft和offsetTop,必然是10(如果给ul加了相对定位的话),那么为什么他们最后都会挤到一块儿呢?不要着急,按下F8,放开现在的断点,进入下次循环。
当进入第二次断点之后,如下图所示:
这时,根据我们上次的分析,相信大家对这次的结果心里必然有底,因为给第2个li加了absolute,所以第二个li飘起来,在3 4 5 6的上方,将3遮住了,所以我们就看不见3了,但是有人一定着急了,你那2形状怎么那么奇怪啊,好像错出来一部分一样,事实上不是这样的,错出来的那部分实际上不是别人,就是1,不要忘了,我们在第一次断点之后给1设置了left和top值,因此1就被定位定在那里了,但是可能大家又会想,设置了left和top值又怎样呢,那不应该是相对于父级,也就是图上黑色的边框left和top各10px吗?
但是大家不要忘了,我们的li可是有10px的margin啊,如果说到这里还不明吧的话,可以看看下面这张图:
我们的2和3实际上是在粉色的框那个位置,所以看起来就错位了,这一块可能稍微有些费解,请大家认真揣摩一下。
这样一来,每次加完定位之后剩下的li都会往前面挤,然后当前li(正在脱离文档流的li)再被定到left:10px;top:10px的位置,就得到了最开始那幅6个li挤到了一起的图。
说了这么多了,那到底该如何解决这个问题呢?这时,我那位QQ里面的朋友就想出这样一种办法:
window.onload=function(){ var oUl=document.getElementById("ul1"); var aLi=oUl.getElementsByTagName("li"); for(var i=0;i<aLi.length;i++){ aLi[i].style.left=aLi[i].offsetLeft+"px"; aLi[i].style.top=aLi[i].offsetTop+"px"; aLi[i].style.position="absolute"; } };
从上面的代码中我们可以看出,他把position:absolute;放在了设置left和top后面,我们分析一下这样做可以吗?
很显然还是不可以,为什么呢?我们还是从for循环的第一次开始分析,第1个li我们为它设置了left和top值,很好,没什么问题,但是,马上又给第1个li加了绝对定位,那第1个li马上脱离文档流,2 3 4 5 6照样还会挤过去,还会出现上面说的问题,因此,这样做是不对的。
分析到这里,相信大家已经有感觉了,都是定位惹的祸,也就是说这句话
aLi[i].style.position="absolute";
放置的位置,成了关键。
那么这句到底应该放在哪里呢?
我们应该有感觉了,只要在获取到offsetLeft和offsetTop并赋值给left和top之后设置,这样就很好了,所以出现了下面的解决方案:
for(var i=0;i<aLi.length;i++){ aLi[i].style.left=aLi[i].offsetLeft+"px"; aLi[i].style.top=aLi[i].offsetTop+"px"; } for(var i=0;i<aLi.length;i++){ aLi[i].style.position="absolute"; }
从代码中,我们可以看出来,我们将设置left和top与设置定位分开来放到两个不同的块级作用域当中了,这样就将它们彻底分开了。
到现在为止,不要高兴的太早,之前我们遗留下来一个问题就是在第二次断点的时候我们发现1和原来的位置相比错位了,那这个错位怎么解决呢?
仔细想想这个错位是怎么来的呢?
是这样的,left和top定位的时候定的是我们整体盒模型(在这里就是指的margin),而不单单是从元素width height开始的那部分,但是offsetLeft和offsetTop在计算的时候,是将margin算上之后赋给left和top的,所以margin就相当于多算了一次,因此,我们还需要把多算的这次margin去掉,来让li回到原来的位置。所以,最终的代码应该是这样:
for(var i=0;i<aLi.length;i++){ aLi[i].style.left=aLi[i].offsetLeft+"px"; aLi[i].style.top=aLi[i].offsetTop+"px"; } for(var i=0;i<aLi.length;i++){ aLi[i].style.position="absolute"; aLi[i].style.margin=0; }