js原生图片拼图Demo

博客总是断断续续,我的语言组织能力太差劲了,总说不明白,但是有感觉直贴代码又不好,好囧。。。
自从找着了工作后已经有将近三个月没有写博客啦。最近在加强js的逻辑练习,所以收集了一些js小练习,手把手把它敲出来了。我把它记录下来,我的学习分享,嘿嘿。。。

贴下效果界面:
图1

这个拼图游戏主要分为几个步骤:

(1)小图片列表切换,点击显示相应的大图(大图是由20张小图片拼连起来的)

(2)点击开始游戏后,20张小图片随机排序

(3)此时鼠标移到大图上显示移动状态,可以拖动,开始记录时间

(4)碎片拼成原图后显示拼图成功以及所花的时间

下面我来分解一下流程:

(1)先展示html及css的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>拼图游戏</title>
<style>
body,ul,li{margin:0;padding:0;text-align: center;}
body{font:30px/1.5 Tahoma;}
#box{position:relative;width:1024px;height:768px;margin:10px auto;background:#808080;}
#box li{float:left;width:256px;height:154px;overflow:hidden;}
#box li img{width:256px;height:154px;}
#box li.hig{width:256px;height:154px;overflow:hidden;border:2px dashed yellow;}
#box li.hig img{width:256px;height:154px;opacity:0.5;filter:alpah(opacity=50);}
#mask{position:absolute;top:0;left:0;width:1024px;height:768px;background:red;opacity:0;filter:alpha(opacity=0);}
input{cursor:pointer;}
#photo{text-align:center;margin:10px 0;}
#photo img{width:100px;height:auto;border-radius:5px;margin:0 5px;opacity:0.5;filter:alpha(opacity=50);cursor:pointer;}
#photo img.hover{opacity:1;filter:alpha(opacity=100);}
#photo img.selected{border:2px solid yellow;width:96px;height:auto;opacity:1;filter:alpha(opacity=100);}
</style>
</head>
<body>
<div id="photo"><img src="img/flower/img_01.jpg" class="selected" /><img src="img/flower/img_02.jpg" /><img src="img/flower/img_03.jpg" /></div>
<input type="button" value="开始游戏" id="play-btn"/>
<ul id="box"></ul>
</body>
</html>

首先实现如下的效果:
图2

说明:鼠标悬浮到小图片上,图片透明度变为50%,点击图片,产生黄色边框,下面的大图也跟着变化,点击开始按钮,内容会变成重新开始

js代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
var oPhoto=document.getElementById("photo");
var aThumbImg=oPhoto.getElementsByTagName('img');
var oStartBtn=document.getElementById("play-btn");
var oBox=document.getElementById("box");
var isRandom=false;
var dirPath=0;
var odata=[];
for(var i=0;i<20;i++){
var temp=i+1;
odata.push(temp);
}
for(var i=0;i<aThumbImg.length;i++){
aThumbImg[i].index=i;
}
oPhoto.onmouseover=function(ev){
var ev = ev || window.event;
var target = ev.target || ev.srcElement;
if(target.nodeName.toLowerCase()=='img'){
target.className+=' hover';
}
}
oPhoto.onmouseout=function(ev){
var ev = ev || window.event;
var target = ev.target || ev.srcElement;
if(target.nodeName.toLowerCase()=='img'){
target.className=target.className.replace(/\shover/,'');
}
}
oPhoto.onclick=function(ev){
var ev = ev || window.event;
var target = ev.target || ev.srcElement;
if(target.nodeName.toLowerCase()=='img'){
for(var i=0;i<aThumbImg.length;i++){
aThumbImg[i].className='';
}
target.className='selected';
dirPath=target.index;
oStartBtn.value='开始游戏';
oBox.innerHTML=""
odata.sort(function(a,b){
return a-b;
});

}
}
oStartBtn.onclick=function(){
this.value="重新开始";
oBox.innerHTML="";
}

上面都是比较简单的,这个游戏的核心还是在于边界判断,移动上。

(2)现在来实现拖动这一部分,先讲解onmousedown和onmousemove事件

2.1 首先,还未开始游戏时,我设置了一层遮罩,就是不让其拖动,所以我们来建个遮罩层,也是放在id=’box’里面,跟li同级。

1
2
3
4
5
6
7
8
9
10
11
oPhoto.onclick=function(ev){
creatMask();
}

function creatMask(){
var mask=document.createElement('div');
mask.id='mask';
mask.style.zIndex=zIndex;
oBox.appendChild(mask);
}
creatMask();

当进入页面是生成遮罩层,点击小图片时,遮罩层还存在,只有点击开始游戏时,才会移除遮罩层。这时候,图片切块才能开始拖动。

2.2 点击开始游戏,给图片切块注册onmousedown和onmousemove事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
function drag(obj){
obj.style.cursor='move';
obj.onmousedown=function(event){
var event=event||window.event;
event.preventDefault();
var start_x=event.clientX-this.offsetLeft;
var start_y=event.clientY-this.offsetTop;
obj.style.zIndex = zIndex++;
    document.onmousemove=function(event){
var event=event||window.event;
event.preventDefault();
var iL=event.clientX-start_x;
var iT=event.clientY-start_y;
var maxL=obj.parentNode.clientWidth-obj.offsetWidth;
var maxT=obj.parentNode.clientHeight-obj.offsetHeight;
//边界判断
if(iL<0){iL=0;}
if(iT<0){iT=0;}
if(iL>maxL){iL=maxL;}
if(iT>maxT){iT=maxT;}
obj.style.left=iL+'px';
obj.style.top=iT+'px';
}
}
}

上面的代码主要实现了图片拖动,且拖动范围不能超过box的范围。

好了,现在要实现拖动的小块与周边的切片的碰撞检测,与之最近的切块显示黄色边框
效果:

图3

这其中的原理是: 查找拖动块附近的最近的li,然后给li容器添加黄色边线。

js代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
function findNear(obj){
var minLi=null;
var minNum=Number.MAX_VALUE;
var filterLi=[];
var aDistance=[];
for(var i=0;i<aLi.length;i++){
if(aLi[i]!=obj&&isButt(obj,aLi[i])){
aDistance.push(getDistance(obj,aLi[i]));
filterLi.push(aLi[i]);
}
}
var minNum=Number.MAX_VALUE;
//遍历查询最小值
for(var i=0;i<aDistance.length;i++){
if(aDistance[i]<minNum){
minNum=aDistance[i];
minLi=filterLi[i];
}
}
return minLi;
}
function isButt(obj1,obj2){
var l1=obj1.offsetLeft;
var lw1=obj1.offsetWidth+l1;
var t1=obj1.offsetTop;
var th1=obj1.offsetTop+t1;

var l2=obj2.offsetLeft;
var lw2=obj2.offsetWidth+l2;
var t2=obj2.offsetTop;
var th2=obj2.offsetTop+t2;
//上下左右判断
return !(th2<t1||th1<t2||lw2<l1||lw1<l2);
}
function getDistance(obj1,obj2){
var dir_x=(obj1.offsetLeft+obj1.offsetWidth/2)-(obj2.offsetLeft+obj2.offsetWidth/2);
var dir_y=(obj1.offsetTop+obj1.offsetHeight/2)-(obj2.offsetTop+obj2.offsetHeight/2);
var dir=Math.sqrt(dir_x*dir_x+dir_y*dir_y);
return dir;
}

然后在onmousedown里面调用

1
2
3
4
5
6
7
var oNear=null;
for (i = 0; i < aLi.length; i++) aLi[i].className = "";
oNear=findNear(obj);//返回一个最靠近obj的块
//对最近的块显示黄色边框
if(oNear){
oNear.className='hig';
}

边界判断、isButt()函数、 findNear()函数和的分析图:
图4
图5
图6

(3)实现了移动拖动这一部分,那么鼠标释放时,又怎么实现拖动块与最近切片的位置交换呢。

首先,得先清楚一点,取个例子:

1
2
3
4
5
<ul id="box">
<li>1</li>
<li>2</li>
<li>3</li>
</ul>

不管你怎么拖动里标签,改变的只是他的left和top,li标签在文档的先后顺序是不会改变的。那我们来看看li标签拖动过程的变化,如下图:

图7

js代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
document.onmouseup=function(event){
document.onmousemove = null;//移除事件
document.onmouseup = null;
var event=event||window.event;
event.preventDefault();
//鼠标释放后碎片与目标碎片对换
if(oNear){
var obj_index=obj.index;
obj.index=oNear.index;
oNear.index=obj_index;
//obj移动到oNear的位置
startMove(obj,oPos[obj.index]);
//oNear移动到obj的位置
startMove(oNear,oPos[oNear.index],function(){
//判断碎片是否还原成功
// console.log(finish());
if(finish()){
alert('恭喜你,拼图游戏完美成功!!!');
creatMask();
}
});
oNear.className="";//移除黄色边框
}else{
//回到原位
startMove(obj,oPos[obj.index]);
}
}
function startMove(obj,pos,onEnd){
obj.timer=setInterval(function(){
doMove(obj,pos,onEnd);
},30);
}
function doMove(obj,pos,onEnd){
var curOffsetLeft=obj.offsetLeft;
var curOffsetTop=obj.offsetTop;
//下面设置图片碎片每次向目标点移动的位移
var speedL=(pos.left-curOffsetLeft)/5;
var speedT=(pos.top-curOffsetTop)/5;

speedL=speedL>0 ? Math.ceil(speedL) : Math.floor(speedL);
speedT=speedT>0 ? Math.ceil(speedT) : Math.floor(speedT);
obj.style.left=(curOffsetLeft+speedL)+'px';
obj.style.top=(curOffsetTop+speedT)+'px';
//当obj的left等于对换oNear所保存的left,说明已经和它完全吻合了。此时移除计时器。
if(curOffsetLeft==pos.left && curOffsetTop==pos.top){
clearInterval(obj.timer);
//如果传入回调函数,则执行回调函数
onEnd && onEnd();
// return;
}
}
function finish(){
var success=true;
var tempArr=[];
tempArr.length=0;
for(var i=0;i<aLi.length;i++){
for(var j=0;j<aLi.length;j++){
if(i==aLi[j]['index']){
//li标签内图片的序号添加进数组
var img=aLi[j].getElementsByTagName('img')[0];
//返回匹配到的子集元素
var srcIndex=img.src.match(/\/(\d+).jpg/)[1];
// console.log(srcIndex);
tempArr.push(srcIndex);
}
}
}
for(var i=1;i<=tempArr.length;i++){
if(i!=tempArr[i-1]){
success=false;
break;
}
}
return success;
}

好啦,整个过程就是这样子。拿来练手还是不错的。

demo中的图片碎片是我原先切好的,其实我本来是想将原图用程序裁切的,奈何技术不够,所以只能用ps处理了一下,这是我觉得比较遗憾的事啦,有兴趣的你可以实现一下呗。。。

这里是demo的源代码地址:https://coding.net/u/U_can/p/puzzleGame/git

热评文章