什么是拖拽交换位置?看下图就明了,我们拖动里面的某个灰色方块,然后与其他灰色方块相互碰撞,如果那个方块离它的位置最近,则一开始那个方块出现红色虚线边框,松开鼠标然后这两者就进行位置互换。

对于这种效果,看似有些复杂。不过再复杂的效果还是由许多简单的事件构成的。以下就此效果做分部解析:

第一步 布局

或者对于你而言,这样的布局是简单的。但是如果需要让方块能够被拖拽,恐怕必须是绝对定位才能完成。而且在未知区域里方块的个数,你是无法对每个去计算他们对应的left和top值的,这个时候我们必须通过js来完成这种布局上的转换。

首先放上css和html代码:

<style>
ul li{list-style:none;}
#box{width:300px;margin:0 auto;}
#box li{width:80px;height:80px;float:left;margin:10px;background:#ccc;line-height:80px;text-align:center;display:inline;cursor:pointer;}
#box .act{border:1px dashed red;}
</style>

<ul id=”box”>
<li>111</li>
<li>222</li>
<li>333</li>
<li>444</li>
<li>555</li>
<li>666</li>
<li>777</li>
<li>888</li>
<li>999</li>
</ul>

然后对于列表进行布局转换:

var oBox=document.getElementById("box"),
aLi=oBox.getElementsByTagName("li"),
i=0,
len=aLi.length,
aPos=[];

for(i=0;i<len;i++){
aPos[i]={left:aLi[i].offsetLeft,top:aLi[i].offsetTop};
}

for(i=0;i<len;i++){
aLi[i].style.left=aPos[i].left+"px";
aLi[i].style.top=aPos[i].top+"px";
aLi[i].style.position="absolute";
aLi[i].style.margin=0;
}

此时每个li都有对应的left和top值,如下图:

第二步 对每个方块进行拖拽操作

关于拖拽,有以下代码:

function drag(obj){
var iMinZindex=100
obj.onmousedown=function(ev){
clearInterval(obj.timer);
iMinZindex++;
obj.style.zIndex=iMinZindex;
var e=ev||event;
var disX=e.clientX-obj.offsetLeft;
var disY=e.clientY-obj.offsetTop;

if(obj.setCapture){
obj.onmousemove=fnMove;
obj.onmouseup=fnUp;
obj.setCapture();
}

else{
document.onmousemove=fnMove;
document.onmouseup=fnUp;
}

function fnMove(ev){
var e=ev||event;
x=e.clientX-disX;
y=e.clientY-disY;
obj.style.left=x+"px";
obj.style.top=y+"px";
}

function fnUp(){
this.onmousemove=null;
this.onmouseup=null;
if(this.setCapture){
this.releaseCapture();
}
}
return false;
}
}

//对每个li进行拖拽处理
for(var i=0;i<len;i++){
drag(aLi[i]);
}

此时,每个li都可以进行随意的拖动了,并且每拖动一次,li的zIndex就会加1,所以,最后一个拖动的li层级总是最高的。

第三步 两个方块碰撞的条件?

假设碰撞的两个方块分别是obj1和obj2,obj1是被拖拽的方块,那么怎么样才能判断它们两个是否碰撞了呢?这里我们给出了一个反向思路,即obj1和obj2不碰撞的产生条件是什么?

  1. obj1的右边框要小于obj2的左边框

2.obj1的左边框要大于obj2的右边框

3.obj1的下边框要小于obj2的上边框

4.obj1的上边框要大于obj2的下边框

以上四种情况均可以使得obj1和obj2不碰撞。于是我们得到函数delect:

//碰撞检测
function delect(obj1,obj2){
var l1=obj1.offsetLeft,
r1=obj1.offsetLeft+obj1.offsetWidth,
t1=obj1.offsetTop,
b1=obj1.offsetTop+obj1.offsetHeight,
l2=obj2.offsetLeft,
r2=obj2.offsetLeft+obj2.offsetWidth,
t2=obj2.offsetTop,
b2=obj2.offsetTop+obj2.offsetHeight;
if(r1<l2||l1>r2||b1<t2||t1>b2){
return false;
}
else{
return true;
}
}

这样我们通过delect函数的返回值,就能知道两个物体是否发生了碰撞。

第四步 当同时与多个方块碰撞,求最短距离,并得到最短距离的方块

首先,当被拖拽的物体同时与几个方块相互碰撞时,则我们首先求出这些方块与被拖拽方块的距离,距离函数如下:

//两方块距离
function dist(obj1,obj2){
var a=obj1.offsetLeft-obj2.offsetLeft,
b=obj1.offsetTop-obj2.offsetTop;
return Math.sqrt(a*a+b*b);
}

再对这些方块的距离进行比较,从而返回那个与它位置最近的方块。

//最近距离
function shortestDis(obj){
var iNow=-1,iMin=99999; //这里iNow不能等于0
for(var i=0;i<len;i++){
if(obj==aLi[i])continue; //如果没有发生任何碰撞,则跳过
if(delect(obj,aLi[i])){
var dis=dist(obj,aLi[i]);
if(dis<iMin){ //比较求出最小的距离
iMin=dis;
iNow=i;
}
}
}
if(iNow==-1){ //没有发生碰撞的情况
return null;
}
else{ //发生碰撞情况,并返回最短距离的块
return aLi[iNow];
}
}

此时,通过这个函数我们得到了最短距离的方块,我们需要给它加一个红色边框,而这些操作都是拖动的过程中完成的,于是我们回到拖动的过程中,有:

function fnMove(ev){
var e=ev||event;
x=e.clientX-disX;
y=e.clientY-disY;
obj.style.left=x+"px";
obj.style.top=y+"px";

for(var i=0;i<len;i++){
aLi[i].className="";
}

var near=shortestDis(obj);

if(near){
near.className="act";
}
}

第五步 两个碰撞的方块进行位置互换

虽然我们知道交换位置是在释放鼠标时完成的。但是我们并不知道被我们拖拽的方块的索引值,这样我们就无法对这两个方块的进行位置赋值,怎么办呢?如果我们分别知道这被拖动的方块和距离最近的方块的索引值,再依据之前提到的aPos[i],那么这两个方块的位置就可以确定了,于是我们在开始循环的时候,给每个li加索引值:

for(i=0;i<len;i++){
aLi[i].style.left=aPos[i].left+"px";
aLi[i].style.top=aPos[i].top+"px";
aLi[i].style.position="absolute";
aLi[i].style.margin=0;
aLi[i].index=i; //加索引值
}

function fnUp(){
this.onmousemove=null;
this.onmouseup=null;

var near=shortestDis(obj);

if(near){ //碰撞到了方块
obj.style.left=aPos[near.index].left+"px";
obj.style.top=aPos[near.index].top+"px";
near.style.left=aPos[obj.index].left+"px";
near.style.top=aPos[obj.index].top+"px";
near.className="";
near.style.zIndex=iMinZindex;
var temp=obj.index; //交换位置时,同时把li的序列号也交换。避免再次交换时,回到被碰撞方块的初始位置
obj.index=near.index;
near.index=temp;
}
else{ //没有碰撞任何方块
obj.style.left=aPos[obj.index].left+"px";
obj.style.top=aPos[obj.index].top+"px";
}

if(this.setCapture){
this.releaseCapture();
}
}

整个效果就是以上的五个步骤。完整代码如下:

(function(){
var oBox=document.getElementById("box"),aLi=oBox.getElementsByTagName("li"),i=0,len=aLi.length,aPos=[];

for(i=0;i<len;i++){
aPos[i]={left:aLi[i].offsetLeft,top:aLi[i].offsetTop};
}
for(i=0;i<len;i++){
aLi[i].style.left=aPos[i].left+"px";
aLi[i].style.top=aPos[i].top+"px";
aLi[i].style.position="absolute";
aLi[i].style.margin=0;
aLi[i].index=i;
}

function drag(obj){
var iMinZindex=100
obj.onmousedown=function(ev){
clearInterval(obj.timer);
iMinZindex++;
obj.style.zIndex=iMinZindex;
var e=ev||event;
var disX=e.clientX-obj.offsetLeft;
var disY=e.clientY-obj.offsetTop;

if(obj.setCapture){
obj.onmousemove=fnMove;
obj.onmouseup=fnUp;
obj.setCapture();
}

else{
document.onmousemove=fnMove;
document.onmouseup=fnUp;
}

function fnMove(ev){
var e=ev||event;
x=e.clientX-disX;
y=e.clientY-disY;
obj.style.left=x+"px";
obj.style.top=y+"px";

for(var i=0;i<len;i++){
aLi[i].className="";
}

var near=shortestDis(obj);

if(near){
near.className="act";
}
document.title=obj.index;
}

function fnUp(){
this.onmousemove=null;
this.onmouseup=null;

var near=shortestDis(obj);

if(near){
obj.style.left=aPos[near.index].left+"px";
obj.style.top=aPos[near.index].top+"px";
near.style.left=aPos[obj.index].left+"px";
near.style.top=aPos[obj.index].top+"px";
near.className="";
near.style.zIndex=iMinZindex;
var temp=obj.index;
obj.index=near.index;
near.index=temp;
}
else{
obj.style.left=aPos[obj.index].left+"px";
obj.style.top=aPos[obj.index].top+"px";
}

if(this.setCapture){
this.releaseCapture();
}
}

return false;
}
}

for(var i=0;i<len;i++){
drag(aLi[i]);
}

function delect(obj1,obj2){
var l1=obj1.offsetLeft,
r1=obj1.offsetLeft+obj1.offsetWidth,
t1=obj1.offsetTop,
b1=obj1.offsetTop+obj1.offsetHeight,
l2=obj2.offsetLeft,
r2=obj2.offsetLeft+obj2.offsetWidth,
t2=obj2.offsetTop,
b2=obj2.offsetTop+obj2.offsetHeight;
if(r1<l2||l1>r2||b1<t2||t1>b2){
return false;
}
else{
return true;
}
}

function dist(obj1,obj2){
var a=obj1.offsetLeft-obj2.offsetLeft,
b=obj1.offsetTop-obj2.offsetTop;
return Math.sqrt(a*a+b*b);
}

function shortestDis(obj){
var iNow=-1,iMin=99999;
for(var i=0;i<len;i++){
if(obj==aLi[i])continue;
if(delect(obj,aLi[i])){
var dis=dist(obj,aLi[i]);
if(dis<iMin){
iMin=dis;
iNow=i;
}
}
}
if(iNow==-1){
return null;
}
else{
return aLi[iNow];
}
}
})()