初衷
网上找的几款js写的拼图小游戏均有问题: 有些需要jq 支持且存在拖拽过快 ,导致拼图碎片出现位置偏差的bug
特色
1.原生js编写 , 无须依赖额外插件支持
2.解决拖拽过快, 拼图位置出现偏差或重叠bug
3.通过new关键字创建对象 , 并暴露配置对象进行拼图相关配置
4.html和css代码量大量减少
5.方便拼图调整且不影响游戏外操作处理
移动端拼图小游戏最新改版
注意: 1 rem = 50px
pc => rem转换 js
function IsPC() { var userAgentInfo = navigator.userAgent; var reg = new RegExp("(Android|iPhone|SymbianOS|Windows Phone|iPad|iPod)", "ig"); var isPC = !reg.test(userAgentInfo); return isPC } var initFontSize = function () { var n = document.getElementsByTagName("html")[0], e = document.documentElement.clientWidth; if (IsPC()) { if (e > 750) { n.style.fontSize = "50px" } else { n.style.fontSize = e / 750 * 50 + "px" } } else { n.style.fontSize = e / 750 * 50 + "px"; } }; initFontSize(); window.onresize = function () { initFontSize() };
html部分
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="./js/px-rem-index.js"></script>
<link rel="stylesheet" href="./index.css">
<title>Document</title>
</head>
<body style=" 15rem; margin-left: auto; margin-right: auto; position: relative;">
<div id="app">
</div>
<button class='reset'>复原</button>
<script src="./js/index.js"></script>
<script>
new Puzzle({
el:'#app', //插槽
data:{
10,//宽 单位rem 按照1rem = 50px的比例换算的
height:10,//高
row:3, //行数
col:3, //列数
puzzleImg:'./pintu.jpg', //图片路径
// isReset:true
},
success(){
// 拼图成功后的回调
console.log('拼图成功')
}
})
</script>
</body>
</html>
css部分
* { padding: 0; margin: 0; } .puzzle { position: relative; 10rem; height: 10rem; border: 0.04rem solid #ccc; margin-bottom: 0.3rem; } .puzzle .block { box-sizing: border-box; position: absolute; border: 0.04rem solid #ccc; }
js部分
Puzzle.prototype = {
init (options){ //初始化
this.initData(options);
if(!this.isReset && this.startBtn){
this.isReset = true;
this.render(this.isReset) ;
this.handle();
setTimeout(()=>{
if(document.all) { // document.all可以判断浏览器是否是IE
this.startBtn.click()
}else{
var e = document.createEvent("MouseEvents");
e.initEvent("click", true, true);
this.startBtn.dispatchEvent(e);
}
},0)
}else{
this.render(this.isReset) ;
this.handle();
}
},
initData(options){ //数据初始化赋值
var self = this;
this.options = options;
this.el = document.querySelector(options.el);
this.oPuzzle = document.createElement('div');
this.startBtn = document.querySelector(options.data.startBtn) || false; // 绑定触发拼图的按钮
this.puzzleWidth = options.data.width;
this.puzzleHeight = options.data.height;
this.row = options.data.row; //总行数
this.col = options.data.col; //总列数
this.puzzleImg = options.data.puzzleImg; //背景图片的路径
this.callBack = options.success || function(){return}; //拼图成功后的回调
this.mounted = options.mounted || function(){return}; //初始化完成后执行的回调
this.stepUpdate = options.stepUpdate || function(){return}; //滑动图片碎片结束后执行的回调
this.beforeHandle = options.beforeHandle;
this.isReset = options.data.isReset || false ; //是否不随机
//每个块的宽高 = 总宽高 / 行列
this.blockWidth = this.puzzleWidth / this.col;
this.blockHeiht = this.puzzleHeight / this.row;
//包含每个拼图背景的位置信息对象的数组
this.blockImgPosition = this.getBlockImgPosition();
//随机排序的 元素位置信息对象的数组
this.blockPosition = this.getBlockPosition();
setTimeout(function(){
self.oBlockMap = self.oPuzzle.getElementsByClassName('puzzle_block');
},0)
this.htmlFontSize = document.querySelector("html").style.fontSize //获取1rem对应的px换算
if ( this.htmlFontSize) {this.htmlFontSize = parseFloat(this.htmlFontSize)}
this.modalID = 'puzzle_modal';
this.timing = options.data.timing || '.4s';
},
getBlockImgPosition(){ // return 数组 包含每个拼图背景的位置信息
var arr= [];
//通过行列与每块元素宽高的乘积获取背景图显示的位置在每块元素上显示的position对象
for (var i = 0; i < this.row; i++) {
for (var j = 0; j < this.col; j++) {
arr.push({
x : j*this.blockWidth,
y : i*this.blockHeiht
})
}
}
return arr ;
},
getBlockPosition:function(){ //打乱每个拼图元素的位置
var newArr = [];
for (var i = 0; i < this.blockImgPosition.length; i++) {
newArr[i] = this.blockImgPosition[i];
}
newArr.sort(function(){ //将每个含有背景图位置的数组随机打乱
return Math.random()-0.5
})
return newArr;
},
render(isReset){ //渲染节点
var template = '';
for (var i = 0; i < this.blockPosition.length; i++) {
if (isReset) { //根据配置项,决定是否为乱序
//img位置
var imgX = this.blockImgPosition[i].x;
var imgY = this.blockImgPosition[i].y;
}else{
//img位置
var imgX = this.blockPosition[i].x;
var imgY = this.blockPosition[i].y;
}
//元素位置
var positionX = this.blockImgPosition[i].x;
var positionY = this.blockImgPosition[i].y;
template += `
<div
class='puzzle_block'
style="
${this.blockWidth}rem;
height: ${this.blockHeiht}rem;
background-image: url(${this.puzzleImg});
background-position:${-imgX}rem ${-imgY}rem;
background-size:${this.puzzleWidth}rem ${this.puzzleHeight}rem;
position: absolute;
box-sizing: border-box;
top:${positionY}rem;
left: ${positionX}rem;
"
></div>
`
//追加元素
this.oPuzzle.innerHTML = template;
this.oPuzzle.style.width = this.puzzleWidth + 'rem';
this.oPuzzle.style.height = this.puzzleHeight + 'rem';
this.oPuzzle.style.position = 'relative';
this.oPuzzle.setAttribute('class','puzzle');
this.el.appendChild(this.oPuzzle);
}
},
handle(){ //行为
var self = this;
this.resetImg();
var startx, starty,x,y,endx,endy,from,to,newLeft,newtop;
var lis = document.querySelectorAll(".puzzle_block");
for(var i = 0; i < lis.length;i++){
lis[i].addEventListener('touchstart',function(e){
self.createModal(self.modalID) //遮罩层,避免多个手指同时操作造成错乱
this.style.zIndex = 100; //设置拖拽元素的z-index值,使其在最上面。
from = event.target || event.srcElement; //起点元素
startx = from.style.left; //记录起点位置,以left和top来标记更为准确,避免用this.offsetLeft计算有图片重叠的bug
starty =from.style.top;
x = e.changedTouches[0].pageX/self.htmlFontSize - parseFloat(startx); //获取点击的位置到所选对象的边框的水平距离
y = e.changedTouches[0].pageY/self.htmlFontSize - parseFloat(starty);
this.style.transition = 'none';
self.oneFlag = false ; //添加成功后的执行函数只执行一次的开关
// e.preventDefault();
})
lis[i].addEventListener("touchmove", function(e){
newLeft = e.targetTouches[0].pageX/self.htmlFontSize - x; //记录拖拽的水平状态发生改变时的位置
newtop = e.targetTouches[0].pageY/self.htmlFontSize - y;
if (newLeft <= -self.blockWidth / 2) { //限制边界代码块,拖拽区域不能超出边界的一半
newLeft = -self.blockWidth / 2;
} else if (newLeft >= (self.puzzleWidth - self.blockWidth / 2)) {
newLeft = (self.puzzleWidth - self.blockWidth / 2);
}
if (newtop <= -self.blockHeiht / 2) {
newtop = -self.blockHeiht / 2;
} else if (newtop >= (self.puzzleHeight - self.blockHeiht / 2)) {
newtop = (self.puzzleHeight - self.blockHeiht / 2);
}
this.style.left = newLeft + 'rem';
this.style.top = newtop + 'rem'; //设置目标元素的left,top
e.preventDefault();
})
lis[i].addEventListener('touchend',function(e3){
this.style.zIndex = 0;
this.style.transition = 'all '+self.timing+' ease 0s'; //添加css3动画效果
endx = e3.changedTouches[0].pageX /self.htmlFontSize- x;
endy = e3.changedTouches[0].pageY /self.htmlFontSize - y;
to = self.change(from,endx,endy)
if (to == from) { //所去的位置还是原来
to.style.left = startx;
to.style.top = starty;
} else {
to.style.transition = 'all '+self.timing+' ease 0s'; //添加css3动画效果
var toLeft,toTop;
toLeft = to.style.left; //交换坐标
toTop = to.style.top;
from.style.left=toLeft
from.style.top=toTop
to.style.left = startx;
to.style.top = starty;
}
let modal = document.getElementById(self.modalID);
self.oPuzzle.removeChild(modal);
if(!self.blockModal){
self.blockModal = false;
}
})
lis[i].addEventListener('transitionend', function () {
if(to && from && self.oneFlag == false){
self.oneFlag =true;
self.stepUpdate(from,to);
self.checkWin() //动画结束,判断是否为赢
}
})
}
this.mounted() //渲染完成后执行
},
createModal (id) { //遮罩层
let modal = document.getElementById(id)
if (!modal) { // 在没有遮罩的时候创建遮罩
modal = document.createElement('div')
modal.id = id
modal.style.cssText = `position: absolute; left: 0; top: 0; right: 0; bottom: 0; z-index: 999;${this.puzzleWidth}rem;height:${this.puzzleHeight}rem`
this.oPuzzle.appendChild(modal)
}
},
cellOrder(arr){ //将打乱后的数组进行排序
var len = arr.length;
for (var i = 0; i < len; i++) {
this.oBlockMap[i].style.transition = 'all 0.4s ease 0s';
this.oBlockMap[i].style.left = arr[i].x + 'rem';
this.oBlockMap[i].style.top = arr[i].y+ 'rem';
}
},
change(from, x, y){ //获取交换元素
var lis = document.querySelectorAll(".puzzle_block");
for (var i = 0; i < lis.length; i++) { //
//计算被拖拽的碎片与哪个img位置相差小于 宽高的1/2 还必须判断是不是当前原素本身。将自己排除在外
if (Math.abs(lis[i].offsetLeft/this.htmlFontSize - x) <= this.blockWidth / 2 && Math.abs(lis[i].offsetTop/this.htmlFontSize - y) <= this.blockHeiht / 2 && lis[i] != from)
return lis[i];
}
return from; //返回当前
},
checkWin(){ //将标准数组中的top 和 left 与 当前的backgroundPosition 的x y 进行对比
var isWin = true;
for (var i = 0; i < this.oBlockMap.length; i++) {
var oBlock = this.oBlockMap[i];
//top left //当background-position 与 position坐标相同 则为赢
var blockLeft = parseInt('-'+oBlock.style.left);
var blockTop = parseInt('-'+oBlock.style.top);
// x y
var imgLeft = parseInt(oBlock.style.backgroundPositionX);
var imgTop = parseInt(oBlock.style.backgroundPositionY);
if (!(imgLeft == blockLeft && imgTop == blockTop)) {
isWin=false;
break;
}
}
//isWin ==true 则判断为赢 this.oneFlag==false 只执行一次的开关,避免重复执行 this.isReset == false 避免复原成功状态下点击继续执行
if (isWin && this.isReset == false) {
this.oneFlag = true
this.GameWin(this.callBack)
}
},
GameWin(callBack){ //拼图成功后
this.isReset = true;
if(this.startBtn){
this.startBtn.innerText = '开始'
this.startBtn.className += ' oPuzzle_start';
}
this.createModal(this.modalID)
callBack()
},
resetImg(){ //复原
if(this.startBtn){
var self = this;
if (self.isReset) {
this.startBtn.innerText = '开始' ; //初始按钮显示文本
this.startBtn.className += ' oPuzzle_start'
this.startBtn.classList.remove('oPuzzle_reset')
self.createModal(self.modalID)
}else{
this.startBtn.innerText = '复原';
this.startBtn.className += ' oPuzzle_reset'
this.startBtn.classList.remove('oPuzzle_start')
let modal = document.getElementById(self.modalID)
if(modal){
self.oPuzzle.removeChild(modal)
}
}
self.blockPosition = self.getBlockPosition()
this.startBtn.addEventListener('click',function(){
let modal = document.getElementById(self.modalID)
if (self.isReset) {
if(modal){
self.oPuzzle.removeChild(modal)
}
self.isReset =false ;
this.innerText = '复原' ; //初始按钮显示文本
this.className += ' oPuzzle_reset'
this.classList.remove('oPuzzle_start');
self.blockPosition = self.getBlockPosition()
self.cellOrder(self.blockPosition)
} else {
if(!modal){
self.createModal(self.modalID)
}
self.isReset =true;
this.innerText = '开始';
this.className += ' oPuzzle_start'
this.classList.remove('oPuzzle_reset')
self.cellOrder(self.blockImgPosition)
}
},false)
}
},
}
function Puzzle (options) {
this.init(options);
}
/**
* @param {绑定渲染节点插槽(必填)} el
* @param {整体宽 单位rem(必填)} width
* @param {整体高 单位rem(必填) } hieght
* @param { 行数(必填) } row
* @param { 列数(必填) } col
* @param { 图片路径(必填) } puzzleImg
* @param {绑定触发游戏开始结束按钮(选填)} startBtn
* @param { 拖拽动画缓冲时长(选填,默认400ms) } timing
* @param { 初次渲染是否为默认为未开始状态(选填) } isReset
* @param { 初始化完成后执行的回调(同步执行)(选填) } mounted
* @param { 游戏成功后执行的回调(同步执行)(选填) } success
* @param { 游戏拖拽每一步动画结束后会执行的回调(同步执行)(选填) } stepUpdate
*/
var puzzle = new Puzzle({
el:'#app', //插槽
data:{
9,//宽
height:9,//高
row:3, //行数
col:3, //列数
puzzleImg:'https://img.hbhcdn.com/zhuanti/20881/pintu.jpg',
startBtn:'.reset1',
isReset:true,//是否初始化为 待开始状态 判断游戏是否在进行中
timing:'.4s' //拖拽缓冲时长
},
mounted(){ //初始化完成后执行 同步执行
var reset1 = document.querySelector('.reset1') ;
var isStart = reset1.classList.contains('oPuzzle_start');
if(isStart){
reset1.innerText ='自定义按钮--开始';
}else {
reset1.innerText ='自定义按钮--复原';
}
//自定义初始化
reset1.onclick=function(){
var isStart = reset1.classList.contains('oPuzzle_start');
if(isStart){
reset1.innerText ='自定义按钮--开始';
}else {
reset1.innerText ='自定义按钮--复原';
}
}
},
stepUpdate(fromDom,toDom){//toDom 被交换的元素 , fromDom被拖拽的元素
// console.log(fromDom,toDom)
},
success(){// 拼图成功后的回调 同步执行
document.querySelector('.reset1').innerText='自定义按钮--开始'
alert('拼图成功,你实在是太棒了 ≧◠◡◠≦✌!!!')
}
})
1-1.基于jq制作拖拽图片小游戏 (pc版)
HTML内容
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <div class="wrap"> <div class="start"> 开始</div> <div class="right" id="app"> <!-- 在此处渲染 --> </div> </div> <script src="https://cdn.bootcdn.net/ajax/libs/jquery/1.11.3/jquery.js"></script> <script src="./index.js"></script> <script> //调用 new Puzzle({ el:'#app', //插槽 data:{ 400,//宽 height:400,//高 row:4, //行数 col:4, //列数 puzzleImg:'./1.png', //图片路径
startBtn:'.start' //绑定触发按钮类名
}, success(){ // 拼图成功后的回调 console.log('拼图成功') } }) </script> </body> </html>
css部分
*{ margin: 0; padding: 0; } .wrap{ width: 500px; height: 500px; margin: 0 auto; position: relative; border: 1px solid black; } .start{ height: 40px; width: 80px; border:1px solid black; margin: 10px auto; text-align: center; }
js部分
Puzzle.prototype = { init(options){ this.initData(options) this.render() this.handle() }, initData(options){ //初始化数据 this.options = options; this.el = $(options.el); this.imgArea = $('<div class="imgArea" style="position:relative;"></div>'); this.puzzleWidth = options.data.width; //拼图总宽度 this.puzzleHeight = options.data.height; //拼图总高度 this.startBtn = $(options.data.startBtn); // 绑定触发拼图的按钮 this.row = options.data.row; //总行数 this.col = options.data.col; //总列数 this.puzzleImg = options.data.puzzleImg; //背景图片的路径 this.callBack = options.success ; //拼图成功后的回调 //每个块的宽高 = 总宽高 / 行列 this.cellW = this.puzzleWidth / this.col; this.cellH = this.puzzleHeight / this.row; this.oriArr = []; //标准数组 this.ranArr = []; //随机数组 this.flag = true; //标准数组与随机数组之间的切换锁 }, handle(){ //功能 this.gameState() }, render(){ //渲染 var cell,template='' //行 for(var i = 0; i < this.row; i++) { //列 for (var j = 0; j <this.col; j++) { this.oriArr.push(i*this.col +j)//[0,1,2,3,4,5,6,7,8] template += `<div class='cell' style=" position: absolute; ${this.cellW}px; height: ${this.cellH}px; top:${this.cellH * i}px; left:${this.cellW * j}px; background-position:${-this.cellW * j}px ${-this.cellH * i}px; background-image: url(${this.puzzleImg}); background-size: ${this.puzzleWidth}px ${this.puzzleHeight}px; border: 1px solid #fff; z-index: 10; transition-property: background-position; transition-duration: 300ms; transition-timing-function: ease-in-out; " ></div> </div> ` ; } } this.imgArea.html(template); this.imgArea.css({ 'width':this.puzzleWidth+ 'px', 'height':this.puzzleHeight+'px' }) this.el.append(this.imgArea); this.cell = $('.cell') }, gameState(){ var that = this; var imgCell = this.cell, imgArea = this.imgArea this.startBtn.on('click',function(){ if (that.flag) { $(this).text('复原') that.flag = false; that.randomArr(); //打乱数组 that.cellOrder(that.ranArr);//对数组进行样式排序 imgCell.on('mousedown',function(e){ var index1 = $(this).index(); //获取在标准数组中的索引值 var left = e.pageX - imgCell.eq(index1).offset().left; var top =e.pageY- imgCell.eq(index1).offset().top; $(document).on('mousemove',function(e2){ imgCell.eq(index1).css({ 'z-index':'40', 'left':e2.pageX - left - imgArea.offset().left , 'top':e2.pageY - top - imgArea.offset().top , }) }).on('mouseup',function(e3){ var left = e3.pageX - imgArea.offset().left; var top =e3.pageY - imgArea.offset().top var index2 = that.changeIndex(left,top,index1) ;//可能将要改变的位置 console.log(index1,index2,that.ranArr) if (index1 == index2) { //判断是否还在原来的位置上 that.cellReturn(index1) //索引一样 }else{ that.cellChange(index1,index2) } $(document).off('mousemove').off('mouseup'); }) }) }else{ $(this).text('开始') that.flag = true; //复原 that.cellOrder(that.oriArr); $(document).off('mousemove').off('mouseup').off('mousedown'); } }) }, changeIndex(x,y,index){ //与之交换的元素下标 if (x < 0 || x > this.puzzleWidth || y < 0 || y > this.puzzleHeight) { //移动出去或者移动很小 return index; //返回当前的索引 } //鼠标拖动碎片在大图范围内移动 //判断第几行 第几列 var row = Math.floor(y/this.cellH); //拖拽了多少行 var col = Math.floor(x/this.cellW); //拖拽了多少列 var l = row * this.row +col; var i =0, len = this.ranArr.length; while((i < len ) && (this.ranArr[i] != l)){ i++; } return i; }, cellReturn(index) { //飞回index的位置 var j = this.ranArr[index] % this.col ; var i =Math.floor(this.ranArr[index] /this.row); this.cell[index].style.left = this.cellW * j + 'px'; this.cell[index].style.top = this.cellH * i + 'px'; this.cell[index].style.zIndex = '10'; }, randomArr(){ //获取打乱后的随机数组 //进来之前清空 this.ranArr = []; var len = this.oriArr.length; for (var i = 0; i < len; i++) { order = Math.floor(Math.random()*len); if(this.ranArr.length>0){ while($.inArray(order,this.ranArr)>-1){ order = Math.floor(Math.random()*len); } } this.ranArr.push(order) } return; }, //i *3 + j 根据元素的下标除以3取整拿到行数i 乘3取余拿到 列数j cellOrder(arr){ //将打乱后的数组进行排序 var len = arr.length; var that = this; console.log(that.cell) for (var i = 0; i < len; i++) { that.cell.eq(i).animate({ 'left':arr[i] % that.row * that.cellW + 'px', 'top':Math.floor(arr[i] / that.col) * that.cellH + 'px' },400) } }, /** * * @param {被拖动的元素在标准数组中的下标} from * @param {被交换的元素在标准数组中的下标} to */ cellChange(from,to){ const { ranArr,cellW,cellH ,cell } = this; var that = this; //改变前的位置 var fromj = ranArr[from] % that.col; var fromi =Math.floor(ranArr[from] /that.row); //要到哪个位置 var toj = ranArr[to] % that.col; var toi =Math.floor(ranArr[to] /that.row); var temp = ranArr[from]; cell.eq(from).animate({ 'left': cellW * toj + 'px', 'top': cellH * toi + 'px' },400,function(){ $(this).css('z-index','10') }); // console.log(from,to) cell.eq(to).animate({ 'left': cellW * fromj + 'px', 'top': cellH * fromi + 'px' },400,function(){ //恢复样式,更新数组 $(this).css('z-index','10'); ranArr[from] = ranArr [to]; ranArr[to] =temp; that.check() }) }, check(){ //判断是否完成游戏 if(this.oriArr.toString() == this.ranArr.toString()){ // alert('right'); $('.start').text('开始'); this.flag = true; this.callBack(); this.cell.unbind('mousemove').unbind('mouseup').unbind('mousedown'); // $(document).off('mousemove').off('mouseup').off('mousedown'); } } } function Puzzle(options){ this.init(options); }
1-2.基于jq制作拖拽图片小游戏 (移动端版)
注释:样式,结构与pc版基本相同,js中,mouse事件改为touch事件,此版注释更为详细
Puzzle.prototype = { init(options){ this.initData(options) this.render() this.handle() }, initData(options){ //初始化数据 this.options = options; this.el = $(options.el); this.imgArea = $('<div class="imgArea" style="position:relative;"></div>'); this.puzzleWidth = options.data.width; //拼图总宽度 this.puzzleHeight = options.data.height; //拼图总高度 this.startBtn = $(options.data.startBtn); // 绑定触发拼图的按钮 this.row = options.data.row; //总行数 this.col = options.data.col; //总列数 this.puzzleImg = options.data.puzzleImg; //背景图片的路径 this.callBack = options.success ; //拼图成功后的回调 //每个块的宽高 = 总宽高 / 行列 this.cellW = this.puzzleWidth / this.col; this.cellH = this.puzzleHeight / this.row; this.oriArr = []; //标准数组 this.ranArr = []; //随机数组 this.flag = true; //标准数组与随机数组之间的切换锁 this.htmlFontSize = $("html").css("font-size") && parseFloat($("html").css("font-size")); //获取1rem对应的px }, handle(){ //功能 this.gameState() }, render(){ //渲染 var cell,template='' //行 for(var i = 0; i < this.row; i++) { //列 for (var j = 0; j <this.col; j++) { this.oriArr.push(i*this.col +j)//[0,1,2,3,4,5,6,7,8] template += `<div class='cell' style=" position: absolute; ${this.cellW}rem; height: ${this.cellH}rem; top:${this.cellH * i}rem; left:${this.cellW * j}rem; background-position:${-this.cellW * j}rem ${-this.cellH * i}rem; background-image: url(${this.puzzleImg}); background-size: ${this.puzzleWidth}rem ${this.puzzleHeight}rem; border: 1px solid #fff; z-index: 10; transition-property: background-position; transition-duration: 300ms; transition-timing-function: ease-in-out; " ></div> </div> ` ; } } this.imgArea.html(template); this.imgArea.css({ 'width':this.puzzleWidth+ 'rem', 'height':this.puzzleHeight+'rem' }) this.el.append(this.imgArea); this.cell = $('.cell') }, gameState(){ var that = this; var imgCell = this.cell, imgArea = this.imgArea this.startBtn.on('click',function(){ if (that.flag) { $(this).text('复原') that.flag = false; that.randomArr(); //打乱数组 that.cellOrder(that.ranArr);//对数组进行样式排序 imgCell.on('touchstart',function(e){ var index1 = $(this).index(); //获取在标准数组中的索引值 var left = e.originalEvent.touches[0].pageX- imgCell.eq(index1).offset().left; var top =e.originalEvent.touches[0].pageY- imgCell.eq(index1).offset().top; $(document).on('touchmove',function(e2){ imgCell.eq(index1).css({ 'z-index':'40', 'left':e2.originalEvent.touches[0].pageX - left - imgArea.offset().left , 'top':e2.originalEvent.touches[0].pageY - top - imgArea.offset().top , }) }).on('touchend',function(e3){ var left = e3.originalEvent.changedTouches[0].pageX - imgArea.offset().left; var top =e3.originalEvent.changedTouches[0].pageY - imgArea.offset().top var index2 = that.changeIndex(left,top,index1) ;//可能将要改变的位置 // if (index1 == index2) { //判断是否还在原来的位置上 that.cellReturn(index1) //索引一样 }else{ //标准数组中的第几张图片与标准数组中的第几张图片进行交换 that.cellChange(index1,index2) } $(document).off('touchmove').off('touchend'); }) }) }else{ $(this).text('开始') that.flag = true; //复原 that.cellOrder(that.oriArr); $(document).off('touchmove').off('touchend').off('touchstart'); } }) }, changeIndex(x,y,index){ //被交换节点在节点列表中的下标 //判断拖拽位置是否在大图片内 if (x < 0 || x > this.puzzleWidth*this.htmlFontSize || y < 0 || y > this.puzzleHeight*this.htmlFontSize) { //移动出去或者移动很小 return index; //返回当前的索引 } //鼠标拖动碎片在大图范围内移动 //判断第几行 第几列 var row = Math.floor((y/this.htmlFontSize)/this.cellH); //拖拽了多少行 var col = Math.floor((x/this.htmlFontSize)/this.cellW); //拖拽了多少列 // 例如:[2,5,1,4,3,0,6,7,8] 第0张图片2放在九宫格中 0 的位置上 , 第一张1放在5的位置上 //被交换节点在节点排序中为 3 那么就要找到3 在 乱序数组中所在是索引,乱序数组的索引代表的是第几张图片 ,乱序数组的元素代表的是位置 var l = row * this.row +col; var i =0, len = this.ranArr.length; while((i < len ) && (this.ranArr[i] != l)){ i++; } return i; //获取乱序数组中from值的索引 }, cellReturn(index) { //飞回原来的位置 var j = this.ranArr[index] % this.col ; var i =Math.floor(this.ranArr[index] /this.row); this.cell[index].style.left = this.cellW * j + 'rem'; this.cell[index].style.top = this.cellH * i + 'rem'; this.cell[index].style.zIndex = '10'; }, randomArr(){ //获取打乱后的随机数组 //进来之前清空 this.ranArr = []; var len = this.oriArr.length; for (var i = 0; i < len; i++) { order = Math.floor(Math.random()*len); if(this.ranArr.length>0){ while($.inArray(order,this.ranArr)>-1){ order = Math.floor(Math.random()*len); } } this.ranArr.push(order) } return; }, /** * * 乱序数组的索引代表的是标准数组中的第几张图片 ,乱序数组中的元素代表的是位置 * 例如:[2,5,1,4,3,0,6,7,8] 第0张图片2放在九宫格中 0 的位置上 , 第一张1放在5的位置上 * [[2,0],[5,1],[1,2]...] [[所放位置 , 第几张图片]] * 算法逻辑: i *3 + j 根据元素的下标除以3取整拿到行数i 乘3取余拿到 列数j */// // cellOrder(arr){ //将打乱后的数组进行排序 记录图片所放的位置 var len = arr.length; var that = this; for (var i = 0; i < len; i++) { that.cell.eq(i).animate({ 'left':arr[i] % that.row * that.cellW + 'rem', 'top':Math.floor(arr[i] / that.col) * that.cellH + 'rem' },400) } }, /** * 通过索引 ,获取到两个图片的位置 , 再进行运算得出行和列后,进行交换位置 * @param {被拖动的元素在标准数组中的下标} from * @param {被交换的元素在标准数组中的下标} to */ cellChange(from,to){ const { ranArr,cellW,cellH ,cell ,col,row} = this; var that = this; //改变前的位置 var fromj = ranArr[from] % col; var fromi =Math.floor(ranArr[from] / row); //要到哪个位置 var toj = ranArr[to] % that.col; var toi =Math.floor(ranArr[to] / row); var temp = ranArr[from]; cell.eq(from).animate({ 'left': cellW * toj + 'rem', 'top': cellH * toi + 'rem' },400,function(){ $(this).css('z-index','10') }); // console.log(from,to) cell.eq(to).animate({ 'left': cellW * fromj + 'rem', 'top': cellH * fromi + 'rem' },400,function(){ //恢复样式,更新数组 $(this).css('z-index','10'); ranArr[from] = ranArr [to]; ranArr[to] =temp; that.check() //判断是否完成 }) }, check(){ //判断是否完成游戏 if(this.oriArr.toString() == this.ranArr.toString()){ $('.start').text('开始'); this.flag = true; this.callBack(); this.cell.unbind('touchmove').unbind('touchend').unbind('touchstart'); } } } function Puzzle(options){ this.init(options); }
1-3.基于js制作拖拽图片小游戏 (移动端版)
html结构 与 css无变动
js部分:
Puzzle.prototype = { init (options){ //初始化 this.initData(options); if(!this.isReset && this.startBtn){ this.isReset = true; this.render(this.isReset) ; this.handle(); setTimeout(()=>{ if(document.all) { // document.all可以判断浏览器是否是IE this.startBtn.click() }else{ var e = document.createEvent("MouseEvents"); e.initEvent("click", true, true); this.startBtn.dispatchEvent(e); } },0) }else{ this.render(this.isReset) ; this.handle(); } }, initData(options){ //数据初始化赋值 var self = this; this.options = options; this.el = document.querySelector(options.el); this.oPuzzle = document.createElement('div'); this.startBtn = document.querySelector(options.data.startBtn) || false; // 绑定触发拼图的按钮 this.puzzleWidth = options.data.width; this.puzzleHeight = options.data.height; this.row = options.data.row; //总行数 this.col = options.data.col; //总列数 this.puzzleImg = options.data.puzzleImg; //背景图片的路径 this.callBack = options.success || function(){return}; //拼图成功后的回调 this.mounted = options.mounted || function(){return}; //初始化完成后执行的回调 this.update = options.update || function(){return}; //滑动图片碎片结束后执行的回调 this.beforeHandle = options.beforeHandle; this.isReset = options.data.isReset || false ; //是否不随机 //每个块的宽高 = 总宽高 / 行列 this.blockWidth = this.puzzleWidth / this.col; this.blockHeiht = this.puzzleHeight / this.row; //包含每个拼图背景的位置信息对象的数组 this.blockImgPosition = this.getBlockImgPosition(); //随机排序的 元素位置信息对象的数组 this.blockPosition = this.getBlockPosition(); setTimeout(function(){ self.oBlockMap = self.oPuzzle.getElementsByClassName('puzzle_block'); },0) this.htmlFontSize = document.querySelector("html").style.fontSize //获取1rem对应的px换算 if ( this.htmlFontSize) {this.htmlFontSize = parseFloat(this.htmlFontSize)} }, getBlockImgPosition(){ // return 数组 包含每个拼图背景的位置信息 var arr= []; //通过行列与每块元素宽高的乘积获取背景图显示的位置在每块元素上显示的position对象 for (var i = 0; i < this.row; i++) { for (var j = 0; j < this.col; j++) { arr.push({ x : j*this.blockWidth, y : i*this.blockHeiht }) } } return arr ; }, getBlockPosition:function(){ //打乱每个拼图元素的位置 var newArr = []; for (var i = 0; i < this.blockImgPosition.length; i++) { newArr[i] = this.blockImgPosition[i]; } newArr.sort(function(){ //将每个含有背景图位置的数组随机打乱 return Math.random()-0.5 }) return newArr; }, render(isReset){ //渲染节点 var template = ''; for (var i = 0; i < this.blockPosition.length; i++) { if (isReset) { //根据配置项,决定是否为乱序 //img位置 var imgX = this.blockImgPosition[i].x; var imgY = this.blockImgPosition[i].y; }else{ //img位置 var imgX = this.blockPosition[i].x; var imgY = this.blockPosition[i].y; } //元素位置 var positionX = this.blockImgPosition[i].x; var positionY = this.blockImgPosition[i].y; template += ` <div class='puzzle_block' style=" ${this.blockWidth}rem; height: ${this.blockHeiht}rem; background-image: url(${this.puzzleImg}); background-position:${-imgX}rem ${-imgY}rem; background-size:${this.puzzleWidth}rem ${this.puzzleHeight}rem; position: absolute; box-sizing: border-box; top:${positionY}rem; left: ${positionX}rem; " ></div> ` //追加元素 this.oPuzzle.innerHTML = template; this.oPuzzle.style.width = this.puzzleWidth + 'rem'; this.oPuzzle.style.height = this.puzzleHeight + 'rem'; this.oPuzzle.style.position = 'relative'; this.oPuzzle.setAttribute('class','puzzle'); this.el.appendChild(this.oPuzzle); } }, handle(){ //行为 var self = this; this.resetImg(); var startx, starty,x,y,endx,endy,from,to; var lis = document.querySelectorAll(".puzzle_block"); for(var i = 0; i < lis.length;i++){ lis[i].addEventListener('touchstart',function(e){ this.style.zIndex = 100; //设置拖拽元素的z-index值,使其在最上面。 from = event.target || event.srcElement; //起点元素 startx = from.style.left; //记录起点位置,以left和top来标记更为准确,避免用this.offsetLeft计算有图片重叠的bug starty =from.style.top; if(self.isReset){ x = e.changedTouches[0].pageX/self.htmlFontSize - startx; y = e.changedTouches[0].pageY/self.htmlFontSize - starty; return false }else{ x = e.changedTouches[0].pageX/self.htmlFontSize - parseFloat(startx); y = e.changedTouches[0].pageY/self.htmlFontSize - parseFloat(starty); } this.style.transition = 'none'; self.oneFlag = false ; //添加成功后的执行函数只执行一次的开关 document.body.style.overflow='hidden';//禁止页面滚动 }) lis[i].addEventListener("touchmove", function(e){ newLeft = e.targetTouches[0].pageX/self.htmlFontSize - x; //记录拖拽的水平状态发生改变时的位置 newtop = e.targetTouches[0].pageY/self.htmlFontSize - y; if (newLeft <= -self.blockWidth / 2) { //限制边界代码块,拖拽区域不能超出边界的一半 newLeft = -self.blockWidth / 2; } else if (newLeft >= (self.puzzleWidth - self.blockWidth / 2)) { newLeft = (self.puzzleWidth - self.blockWidth / 2); } if (newtop <= -self.blockHeiht / 2) { newtop = -self.blockHeiht / 2; } else if (newtop >= (self.puzzleHeight - self.blockHeiht / 2)) { newtop = (self.puzzleHeight - self.blockHeiht / 2); } this.style.left = newLeft + 'rem'; this.style.top = newtop + 'rem'; //设置目标元素的left,top }); lis[i].addEventListener('touchend',function(e3){ document.body.style.overflow='auto';//解除禁止页面滚动 this.style.zIndex = 0; this.style.transition = 'all 0.5s ease 0s'; //添加css3动画效果 endx = e3.changedTouches[0].pageX /self.htmlFontSize- x; endy = e3.changedTouches[0].pageY /self.htmlFontSize - y; to = self.change(from,endx,endy) if (to == from) { //所去的位置还是原来 to.style.left = startx; to.style.top = starty; } else { to.style.transition = 'all 0.5s ease 0s'; //添加css3动画效果 var toLeft,toTop; toLeft = to.style.left; //交换坐标 toTop = to.style.top; from.style.left=toLeft from.style.top=toTop to.style.left = startx; to.style.top = starty; } }) lis[i].addEventListener('transitionend', function () { if(to && from && self.oneFlag == false){ self.oneFlag =true; self.update(to,from); self.checkWin() //动画结束,判断是否为赢 } }) } this.mounted() //渲染完成后执行 }, cellOrder(arr){ //将打乱后的数组进行排序 var len = arr.length; for (var i = 0; i < len; i++) { this.oBlockMap[i].style.transition = 'all 0.4s ease 0s'; this.oBlockMap[i].style.left = arr[i].x + 'rem'; this.oBlockMap[i].style.top = arr[i].y+ 'rem'; } }, change(from, x, y){ //获取交换元素 var lis = document.querySelectorAll(".puzzle_block"); for (var i = 0; i < lis.length; i++) { //还必须判断是不是当前原素本身。将自己排除在外 if (Math.abs(lis[i].offsetLeft/this.htmlFontSize - x) <= this.blockWidth / 2 && Math.abs(lis[i].offsetTop/this.htmlFontSize - y) <= this.blockHeiht / 2 && lis[i] != from) return lis[i]; } return from; //返回当前 }, checkWin(){ //将标准数组中的top 和 left 与 当前的backgroundPosition 的x y 进行对比 var isWin = true; for (var i = 0; i < this.oBlockMap.length; i++) { var oBlock = this.oBlockMap[i]; //top left //当background-position 与 position坐标相同 则为赢 var blockLeft = parseInt('-'+oBlock.style.left); var blockTop = parseInt('-'+oBlock.style.top); // x y var imgLeft = parseInt(oBlock.style.backgroundPositionX); var imgTop = parseInt(oBlock.style.backgroundPositionY); if (!(imgLeft == blockLeft && imgTop == blockTop)) { isWin=false; break; } } //isWin ==true 则判断为赢 this.oneFlag==false 只执行一次的开关,避免重复执行 this.isReset == false 避免复原成功状态下点击继续执行 if (isWin && this.isReset == false) { this.oneFlag = true this.GameWin(this.callBack) } }, GameWin(callBack){ //拼图成功后 this.isReset = true; if(this.startBtn){ this.startBtn.innerText = '开始' this.startBtn.className += ' oPuzzle_start' } callBack() }, resetImg(){ //复原 if(this.startBtn){ var self = this; if (self.isReset) { this.startBtn.innerText = '开始' ; //初始按钮显示文本 this.startBtn.className += ' oPuzzle_start' this.startBtn.classList.remove('oPuzzle_reset') }else{ this.startBtn.innerText = '复原'; this.startBtn.className += ' oPuzzle_reset' this.startBtn.classList.remove('oPuzzle_start') } self.blockPosition = self.getBlockPosition() this.startBtn.addEventListener('click',function(){ if (self.isReset) { self.isReset =false ; this.innerText = '复原' ; //初始按钮显示文本 this.className += ' oPuzzle_reset' this.classList.remove('oPuzzle_start'); self.blockPosition = self.getBlockPosition() self.cellOrder(self.blockPosition) } else { self.isReset =true; this.innerText = '开始'; this.className += ' oPuzzle_start' this.classList.remove('oPuzzle_reset') self.cellOrder(self.blockImgPosition) } },false) } }, } function Puzzle (options) { this.init(options); } /** * @param {绑定渲染节点插槽(必填)} el * @param {整体宽 单位rem(必填)} width * @param {整体高 单位rem(必填) } hieght * @param { 行数(必填) } row * @param { 列数(必填) } col * @param { 图片路径(必填) } puzzleImg * @param {绑定触发游戏开始结束按钮(选填)} startBtn * @param { 初次渲染是否为默认为未开始状态(选填) } isReset * @param { 初始化完成后执行的回调(同步执行)(选填) } mounted * @param { 游戏成功后执行的回调(同步执行)(选填) } success * @param { 游戏拖拽每一步动画结束后会执行的回调(同步执行)(选填) } update */ var puzzle = new Puzzle({ el:'#app', //插槽 data:{ 9,//宽 height:9,//高 row:3, //行数 col:3, //列数 puzzleImg:'https://img.hbhcdn.com/zhuanti/20881/pintu.jpg', startBtn:'.reset1', isReset:true//是否初始化为 待开始状态 判断游戏是否在进行中 }, mounted(){ //初始化完成后执行 同步执行 var reset1 = document.querySelector('.reset1') ; var isStart = reset1.classList.contains('oPuzzle_start'); if(isStart){ reset1.innerText ='自定义按钮--开始'; }else { reset1.innerText ='自定义按钮--复原'; } //自定义初始化 reset1.onclick=function(){ var isStart = reset1.classList.contains('oPuzzle_start'); if(isStart){ reset1.innerText ='自定义按钮--开始'; }else { reset1.innerText ='自定义按钮--复原'; } } }, update(toDom,fromDom){//toDom 被交换的元素 , fromDom被拖拽的元素 console.log(toDom,fromDom) }, success(){// 拼图成功后的回调 同步执行 document.querySelector('.reset1').innerText='自定义按钮--开始' alert('拼图成功,你实在是太棒了 ≧◠◡◠≦✌!!!') } })
2.基于H5拖拽制作拼图小游戏 (H5拖拽API drap不支持移动端)
html部分
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <script src="./js/px-rem-index.js"></script> <link rel="stylesheet" href="./tuozhuai.css"> <title>Document</title> </head> <body style=" 15rem; margin-left: auto; margin-right: auto; position: relative;"> <div id="app"> </div> <button class='reset'>复原</button> <script src="./js/tuozhuai.js"></script> <script> new Puzzle({ el:'#app', //插槽 data:{ 6,//宽 单位rem 1rem: 50px height:6,//高 row:3, //行数 col:3, //列数 puzzleImg:'./pintu.jpg', // isReset:true }, success(){ // 拼图成功后的回调 console.log('拼图成功') } }) </script> </body> </html>
css部分
margin: 0; } .puzzle { position: relative; /* 10rem; height: 10rem; */ border: 0.04rem solid #ccc; margin-bottom: 0.3rem; } .puzzle .block { box-sizing: border-box; position: absolute; border: 0.04rem solid #ccc; } .blockPrant{ position: absolute; }
js部分
Puzzle.prototype = { init(options) { //初始化 this.initData(options) this.render(this.isReset); this.handle() }, initData(options) { //数据初始化赋值 var self = this; this.options = options; this.el = document.querySelector(options.el); this.oPuzzle = document.createElement('div'); this.puzzleWidth = options.data.width; this.puzzleHeight = options.data.height; this.row = options.data.row; //总行数 this.col = options.data.col; //总列数 this.puzzleImg = options.data.puzzleImg; //背景图片的路径 this.callBack = options.success; //拼图成功后的回调 this.isReset = options.data.isReset || false; //是否不随机 //每个块的宽高 = 总宽高 / 行列 this.blockWidth = this.puzzleWidth / this.col; this.blockHeiht = this.puzzleHeight / this.row; //包含每个拼图背景的位置信息对象的数组 this.blockImgPosition = this.getBlockImgPosition(); //随机排序的 元素位置信息对象的数组 this.blockPosition = this.getBlockPosition(); setTimeout(function () { self.blockPrantS = document.querySelectorAll('.blockPrant'); self.oBlockMap = self.oPuzzle.getElementsByClassName('block'); }, 0) }, getBlockImgPosition() { // return 数组 包含每个拼图背景的位置信息 var arr = []; //通过行列与每块元素宽高的乘积获取背景图显示的位置在每块元素上显示的position对象 for (var i = 0; i < this.row; i++) { for (var j = 0; j < this.col; j++) { arr.push({ x: j * this.blockWidth, y: i * this.blockHeiht }) } } return arr; }, getBlockPosition: function () { //打乱每个拼图元素的位置 var newArr = []; for (var i = 0; i < this.blockImgPosition.length; i++) { newArr[i] = this.blockImgPosition[i]; } var lastEle = newArr[newArr.length - 1]; //最后一个元素的坐标; newArr.length = newArr.length - 1; //将最后一个元素清除 空出位置; newArr.sort(function () { //将每个含有背景图位置的数组随机打乱 return Math.random() - 0.5 }) newArr.push(lastEle); return newArr; }, render(isReset) { //渲染节点 var template = ''; for (var i = 0; i < this.blockPosition.length; i++) { if (isReset) { //img位置 var imgX = this.blockImgPosition[i].x; var imgY = this.blockImgPosition[i].y; } else { //img位置 var imgX = this.blockPosition[i].x; var imgY = this.blockPosition[i].y; } //元素位置 var positionX = this.blockImgPosition[i].x; var positionY = this.blockImgPosition[i].y; template += ` <div class='blockPrant' style=" ${this.blockWidth}rem; height: ${this.blockHeiht}rem; top:${positionY}rem; left: ${positionX}rem; " > <div class='block' style=" ${this.blockWidth}rem; height: ${this.blockHeiht}rem; background-image: url(${this.puzzleImg}); background-position:${-imgX}rem ${-imgY}rem; background-size:${this.puzzleWidth}rem ${this.puzzleHeight}rem; " draggable="true" ></div> </div> ` //追加元素 this.oPuzzle.innerHTML = template; this.oPuzzle.style.width = this.puzzleWidth + 'rem'; this.oPuzzle.style.height = this.puzzleHeight + 'rem'; this.oPuzzle.setAttribute('class', 'puzzle'); this.el.appendChild(this.oPuzzle); } }, handle() { //行为 var self = this; this.resetImg() // 兼容性问题,目前版本的Firefox不支持 var lis = document.querySelectorAll(".block"); var from, to, fromPosi, toPosi; var blockPrantS = document.querySelectorAll('.blockPrant'); for (var i = 0; i < lis.length; i++) { lis[i].setAttribute("draggable", "true"); lis[i].ondragstart = function (event) { from = event.target || event.srcElement; //起点元素 var x = from.style.backgroundPositionX; var y = from.style.backgroundPositionY; fromPosi = { x, y } } blockPrantS[i].ondragenter = function (e) { //拖拽的目标地点元素 to = e.target; var x = to.style.backgroundPositionX; var y = to.style.backgroundPositionY; toPosi = { x, y } } lis[i].ondragend = function (e) { //交换位置 self.moveBlock(to, from, fromPosi, toPosi); //判断是否完成 self.checkWin(); } } }, moveBlock(to, from, fromPosi, toPosi) { //交换位置逻辑 将点击元素与空白元素 top 和 left 互换 to.style.backgroundPositionX = fromPosi.x; to.style.backgroundPositionY = fromPosi.y; from.style.backgroundPositionX = toPosi.x; from.style.backgroundPositionY = toPosi.y; }, checkWin() { //将标准数组中的top 和 left 与 当前的backgroundPosition 的x y 进行对比 var isWin = true; for (var i = 0; i < this.oBlockMap.length; i++) { var oBlock = this.oBlockMap[i]; var blockPrantS = this.blockPrantS[i] //top left var blockLeft = parseInt('-' + blockPrantS.style.left); var blockTop = parseInt('-' + blockPrantS.style.top); // x y var imgLeft = parseInt(oBlock.style.backgroundPositionX); var imgTop = parseInt(oBlock.style.backgroundPositionY); console.log(blockLeft, imgLeft) if (!(imgLeft == blockLeft && imgTop == blockTop)) { isWin = false; break; } } if (isWin) { this.GameWin(this.callBack) } }, GameWin(callBack) { //拼图成功后 拼图无法点击 空白区域显示 触发成功后的回调 for (var i = 0; i < this.oBlockMap.length; i++) { this.oBlockMap[i].ondragstart = null; this.oBlockMap[i].ondragend = null; this.oBlockMap[i].setAttribute("draggable", "false"); } callBack() }, resetImg() { //复原 var self = this; document.querySelector('.reset').onclick = function () { self.el.innerHTML = ''; self.options.data.isReset = true self.init(self.options) } } } function Puzzle(options) { this.init(options); }
改良版
Puzzle.prototype = { init (options){ //初始化 this.initData(options); if(!this.isReset && this.startBtn){ this.isReset = true; this.render(this.isReset) ; this.handle(); setTimeout(()=>{ if(document.all) { // document.all可以判断浏览器是否是IE this.startBtn.click() }else{ var e = document.createEvent("MouseEvents"); e.initEvent("click", true, true); this.startBtn.dispatchEvent(e); } },0) }else{ this.render(this.isReset) ; this.handle(); } }, initData(options){ //数据初始化赋值 var self = this; this.options = options; this.el = document.querySelector(options.el); this.oPuzzle = document.createElement('div'); this.startBtn = document.querySelector(options.data.startBtn) || false; // 绑定触发拼图的按钮 this.puzzleWidth = options.data.width; this.puzzleHeight = options.data.height; this.row = options.data.row; //总行数 this.col = options.data.col; //总列数 this.puzzleImg = options.data.puzzleImg; //背景图片的路径 this.callBack = options.success || function(){return}; //拼图成功后的回调 this.mounted = options.mounted || function(){return}; //初始化完成后执行的回调 this.stepUpdate = options.stepUpdate || function(){return}; //滑动图片碎片结束后执行的回调 this.beforeHandle = options.beforeHandle; this.isReset = options.data.isReset || false ; //是否不随机 //每个块的宽高 = 总宽高 / 行列 this.blockWidth = this.puzzleWidth / this.col; this.blockHeiht = this.puzzleHeight / this.row; //包含每个拼图背景的位置信息对象的数组 this.blockImgPosition = this.getBlockImgPosition(); //随机排序的 元素位置信息对象的数组 this.blockPosition = this.getBlockPosition(); setTimeout(function(){ self.oBlockMap = self.oPuzzle.getElementsByClassName('puzzle_block'); },0) this.htmlFontSize = document.querySelector("html").style.fontSize //获取1rem对应的px换算 if ( this.htmlFontSize) {this.htmlFontSize = parseFloat(this.htmlFontSize)} this.modalID = 'puzzle_modal'; this.timing = options.data.timing || '.4s'; }, getBlockImgPosition(){ // return 数组 包含每个拼图背景的位置信息 var arr= []; //通过行列与每块元素宽高的乘积获取背景图显示的位置在每块元素上显示的position对象 for (var i = 0; i < this.row; i++) { for (var j = 0; j < this.col; j++) { arr.push({ x : j*this.blockWidth, y : i*this.blockHeiht }) } } return arr ; }, getBlockPosition:function(){ //打乱每个拼图元素的位置 var newArr = []; for (var i = 0; i < this.blockImgPosition.length; i++) { newArr[i] = this.blockImgPosition[i]; } newArr.sort(function(){ //将每个含有背景图位置的数组随机打乱 return Math.random()-0.5 }) return newArr; }, render(isReset){ //渲染节点 var template = ''; for (var i = 0; i < this.blockPosition.length; i++) { if (isReset) { //根据配置项,决定是否为乱序 //img位置 var imgX = this.blockImgPosition[i].x; var imgY = this.blockImgPosition[i].y; }else{ //img位置 var imgX = this.blockPosition[i].x; var imgY = this.blockPosition[i].y; } //元素位置 var positionX = this.blockImgPosition[i].x; var positionY = this.blockImgPosition[i].y; template += ` <div class='puzzle_block' style=" ${this.blockWidth}rem; height: ${this.blockHeiht}rem; background-image: url(${this.puzzleImg}); background-position:${-imgX}rem ${-imgY}rem; background-size:${this.puzzleWidth}rem ${this.puzzleHeight}rem; position: absolute; box-sizing: border-box; top:${positionY}rem; left: ${positionX}rem; " ></div> ` //追加元素 this.oPuzzle.innerHTML = template; this.oPuzzle.style.width = this.puzzleWidth + 'rem'; this.oPuzzle.style.height = this.puzzleHeight + 'rem'; this.oPuzzle.style.position = 'relative'; this.oPuzzle.setAttribute('class','puzzle'); this.el.appendChild(this.oPuzzle); } }, handle(){ //行为 var self = this; this.resetImg(); var startx, starty,x,y,endx,endy,from,to,newLeft,newtop; var lis = document.querySelectorAll(".puzzle_block"); for(var i = 0; i < lis.length;i++){ lis[i].addEventListener('touchstart',function(e){ self.createModal(self.modalID) //遮罩层,避免多个手指同时操作造成错乱 this.style.zIndex = 100; //设置拖拽元素的z-index值,使其在最上面。 from = event.target || event.srcElement; //起点元素 startx = from.style.left; //记录起点位置,以left和top来标记更为准确,避免用this.offsetLeft计算有图片重叠的bug starty =from.style.top; x = e.changedTouches[0].pageX/self.htmlFontSize - parseFloat(startx); //获取点击的位置到所选对象的边框的水平距离 y = e.changedTouches[0].pageY/self.htmlFontSize - parseFloat(starty); this.style.transition = 'none'; self.oneFlag = false ; //添加成功后的执行函数只执行一次的开关 // e.preventDefault(); }) lis[i].addEventListener("touchmove", function(e){ newLeft = e.targetTouches[0].pageX/self.htmlFontSize - x; //记录拖拽的水平状态发生改变时的位置 newtop = e.targetTouches[0].pageY/self.htmlFontSize - y; if (newLeft <= -self.blockWidth / 2) { //限制边界代码块,拖拽区域不能超出边界的一半 newLeft = -self.blockWidth / 2; } else if (newLeft >= (self.puzzleWidth - self.blockWidth / 2)) { newLeft = (self.puzzleWidth - self.blockWidth / 2); } if (newtop <= -self.blockHeiht / 2) { newtop = -self.blockHeiht / 2; } else if (newtop >= (self.puzzleHeight - self.blockHeiht / 2)) { newtop = (self.puzzleHeight - self.blockHeiht / 2); } this.style.left = newLeft + 'rem'; this.style.top = newtop + 'rem'; //设置目标元素的left,top e.preventDefault(); }) lis[i].addEventListener('touchend',function(e3){ this.style.zIndex = 0; this.style.transition = 'all '+self.timing+' ease 0s'; //添加css3动画效果 endx = e3.changedTouches[0].pageX /self.htmlFontSize- x; endy = e3.changedTouches[0].pageY /self.htmlFontSize - y; to = self.change(from,endx,endy) if (to == from) { //所去的位置还是原来 to.style.left = startx; to.style.top = starty; } else { to.style.transition = 'all '+self.timing+' ease 0s'; //添加css3动画效果 var toLeft,toTop; toLeft = to.style.left; //交换坐标 toTop = to.style.top; from.style.left=toLeft from.style.top=toTop to.style.left = startx; to.style.top = starty; } let modal = document.getElementById(self.modalID); self.oPuzzle.removeChild(modal); if(!self.blockModal){ self.blockModal = false; } }) lis[i].addEventListener('transitionend', function () { if(to && from && self.oneFlag == false){ self.oneFlag =true; self.stepUpdate(from,to); self.checkWin() //动画结束,判断是否为赢 } }) } this.mounted() //渲染完成后执行 }, createModal (id) { //遮罩层 let modal = document.getElementById(id) if (!modal) { // 在没有遮罩的时候创建遮罩 modal = document.createElement('div') modal.id = id modal.style.cssText = `position: absolute; left: 0; top: 0; right: 0; bottom: 0; z-index: 999;${this.puzzleWidth}rem;height:${this.puzzleHeight}rem` this.oPuzzle.appendChild(modal) } }, cellOrder(arr){ //将打乱后的数组进行排序 var len = arr.length; for (var i = 0; i < len; i++) { this.oBlockMap[i].style.transition = 'all 0.4s ease 0s'; this.oBlockMap[i].style.left = arr[i].x + 'rem'; this.oBlockMap[i].style.top = arr[i].y+ 'rem'; } }, change(from, x, y){ //获取交换元素 var lis = document.querySelectorAll(".puzzle_block"); for (var i = 0; i < lis.length; i++) { // //计算被拖拽的碎片与哪个img位置相差小于 宽高的1/2 还必须判断是不是当前原素本身。将自己排除在外 if (Math.abs(lis[i].offsetLeft/this.htmlFontSize - x) <= this.blockWidth / 2 && Math.abs(lis[i].offsetTop/this.htmlFontSize - y) <= this.blockHeiht / 2 && lis[i] != from) return lis[i]; } return from; //返回当前 }, checkWin(){ //将标准数组中的top 和 left 与 当前的backgroundPosition 的x y 进行对比 var isWin = true; for (var i = 0; i < this.oBlockMap.length; i++) { var oBlock = this.oBlockMap[i]; //top left //当background-position 与 position坐标相同 则为赢 var blockLeft = parseInt('-'+oBlock.style.left); var blockTop = parseInt('-'+oBlock.style.top); // x y var imgLeft = parseInt(oBlock.style.backgroundPositionX); var imgTop = parseInt(oBlock.style.backgroundPositionY); if (!(imgLeft == blockLeft && imgTop == blockTop)) { isWin=false; break; } } //isWin ==true 则判断为赢 this.oneFlag==false 只执行一次的开关,避免重复执行 this.isReset == false 避免复原成功状态下点击继续执行 if (isWin && this.isReset == false) { this.oneFlag = true this.GameWin(this.callBack) } }, GameWin(callBack){ //拼图成功后 this.isReset = true; if(this.startBtn){ this.startBtn.innerText = '开始' this.startBtn.className += ' oPuzzle_start'; } this.createModal(this.modalID) callBack() }, resetImg(){ //复原 if(this.startBtn){ var self = this; if (self.isReset) { this.startBtn.innerText = '开始' ; //初始按钮显示文本 this.startBtn.className += ' oPuzzle_start' this.startBtn.classList.remove('oPuzzle_reset') self.createModal(self.modalID) }else{ this.startBtn.innerText = '复原'; this.startBtn.className += ' oPuzzle_reset' this.startBtn.classList.remove('oPuzzle_start') let modal = document.getElementById(self.modalID) if(modal){ self.oPuzzle.removeChild(modal) } } self.blockPosition = self.getBlockPosition() this.startBtn.addEventListener('click',function(){ let modal = document.getElementById(self.modalID) if (self.isReset) { if(modal){ self.oPuzzle.removeChild(modal) } self.isReset =false ; this.innerText = '复原' ; //初始按钮显示文本 this.className += ' oPuzzle_reset' this.classList.remove('oPuzzle_start'); self.blockPosition = self.getBlockPosition() self.cellOrder(self.blockPosition) } else { if(!modal){ self.createModal(self.modalID) } self.isReset =true; this.innerText = '开始'; this.className += ' oPuzzle_start' this.classList.remove('oPuzzle_reset') self.cellOrder(self.blockImgPosition) } },false) } }, } function Puzzle (options) { this.init(options); } /** * @param {绑定渲染节点插槽(必填)} el * @param {整体宽 单位rem(必填)} width * @param {整体高 单位rem(必填) } hieght * @param { 行数(必填) } row * @param { 列数(必填) } col * @param { 图片路径(必填) } puzzleImg * @param {绑定触发游戏开始结束按钮(选填)} startBtn * @param { 拖拽动画缓冲时长(选填,默认400ms) } timing * @param { 初次渲染是否为默认为未开始状态(选填) } isReset * @param { 初始化完成后执行的回调(同步执行)(选填) } mounted * @param { 游戏成功后执行的回调(同步执行)(选填) } success * @param { 游戏拖拽每一步动画结束后会执行的回调(同步执行)(选填) } stepUpdate */ var puzzle = new Puzzle({ el:'#app', //插槽 data:{ 9,//宽 height:9,//高 row:3, //行数 col:3, //列数 puzzleImg:'https://img.hbhcdn.com/zhuanti/20881/pintu.jpg', startBtn:'.reset1', isReset:true,//是否初始化为 待开始状态 判断游戏是否在进行中 timing:'.4s' //拖拽缓冲时长 }, mounted(){ //初始化完成后执行 同步执行 var reset1 = document.querySelector('.reset1') ; var isStart = reset1.classList.contains('oPuzzle_start'); if(isStart){ reset1.innerText ='自定义按钮--开始'; }else { reset1.innerText ='自定义按钮--复原'; } //自定义初始化 reset1.onclick=function(){ var isStart = reset1.classList.contains('oPuzzle_start'); if(isStart){ reset1.innerText ='自定义按钮--开始'; }else { reset1.innerText ='自定义按钮--复原'; } } }, stepUpdate(fromDom,toDom){//toDom 被交换的元素 , fromDom被拖拽的元素 // console.log(fromDom,toDom) }, success(){// 拼图成功后的回调 同步执行 document.querySelector('.reset1').innerText='自定义按钮--开始' alert('拼图成功,你实在是太棒了 ≧◠◡◠≦✌!!!') } })
3.点击拼图小游戏
html部分
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <script src="./js/px-rem-index.js"></script> <link rel="stylesheet" href="./index.css"> <title>Document</title> </head> <body style=" 15rem; margin-left: auto; margin-right: auto; position: relative;"> <div id="app"> </div> <button class='reset'>复原</button> <script src="./js/index.js"></script> <script> new Puzzle({ el:'#app', //插槽 data:{ 10,//宽 单位rem 按照1rem = 50px的比例换算的 height:10,//高 row:3, //行数 col:3, //列数 puzzleImg:'./pintu.jpg', //图片路径 // isReset:true }, success(){ // 拼图成功后的回调 console.log('拼图成功') } }) </script> </body> </html>
css部分
* { padding: 0; margin: 0; } .puzzle { position: relative; width: 10rem; height: 10rem; border: 0.04rem solid #ccc; margin-bottom: 0.3rem; } .puzzle .block { box-sizing: border-box; position: absolute; border: 0.04rem solid #ccc; }
js部分
Puzzle.prototype = { init (options){ //初始化 this.initData(options) this.render(this.isReset) ; console.log(this.isReset) this.handle() }, initData(options){ //数据初始化赋值 var self = this; this.options = options; this.el = document.querySelector(options.el); this.oPuzzle = document.createElement('div'); this.puzzleWidth = options.data.width; this.puzzleHeight = options.data.height; this.row = options.data.row; //总行数 this.col = options.data.col; //总列数 this.puzzleImg = options.data.puzzleImg; //背景图片的路径 this.callBack = options.success ; //拼图成功后的回调 this.isReset = options.data.isReset || false ; //是否不随机 //每个块的宽高 = 总宽高 / 行列 this.blockWidth = this.puzzleWidth / this.col; this.blockHeiht = this.puzzleHeight / this.row; //包含每个拼图背景的位置信息对象的数组 this.blockImgPosition = this.getBlockImgPosition(); //随机排序的 元素位置信息对象的数组 this.blockPosition = this.getBlockPosition(); setTimeout(function(){ self.oEmptyBlock = self.oPuzzle.querySelector('div[ref=empty]'); self.oBlockMap = self.oPuzzle.querySelectorAll('div[ref=block]'); },0) }, getBlockImgPosition(){ // return 数组 包含每个拼图背景的位置信息 var arr= []; //通过行列与每块元素宽高的乘积获取背景图显示的位置在每块元素上显示的position对象 for (var i = 0; i < this.row; i++) { for (var j = 0; j < this.col; j++) { arr.push({ x : j*this.blockWidth, y : i*this.blockHeiht }) } } return arr ; }, getBlockPosition:function(){ //打乱每个拼图元素的位置 并 空出一个没有背景的元素 var newArr = []; for (var i = 0; i < this.blockImgPosition.length; i++) { newArr[i] = this.blockImgPosition[i]; } var lastEle = newArr[newArr.length-1]; //最后一个元素的坐标; newArr.length = newArr.length - 1 ; //将最后一个元素清除 空出位置; newArr.sort(function(){ //将每个含有背景图位置的数组随机打乱 return Math.random()-0.5 }) newArr.push(lastEle); return newArr; }, render(isReset){ //渲染节点 var template = ''; for (var i = 0; i < this.blockPosition.length; i++) { if (isReset) { //img位置 var imgX = this.blockImgPosition[i].x; var imgY = this.blockImgPosition[i].y; }else{ //img位置 var imgX = this.blockPosition[i].x; var imgY = this.blockPosition[i].y; } //元素位置 var positionX = this.blockImgPosition[i].x; var positionY = this.blockImgPosition[i].y; //判断是否为最后一块元素 var isLastBlock = i == this.blockImgPosition.length - 1; template += ` <div class='block' style=" ${this.blockWidth}rem; height: ${this.blockHeiht}rem; top:${positionY}rem; left: ${positionX}rem; background-image: url(${this.puzzleImg}); background-position:${-imgX}rem ${-imgY}rem; opacity: ${isLastBlock ? 0 : 1} " ref="${isLastBlock ? 'empty': 'block'}" ></div> ` //追加元素 this.oPuzzle.innerHTML = template; this.oPuzzle.style.width = this.puzzleWidth; this.oPuzzle.style.height = this.puzzleHeight; this.oPuzzle.setAttribute('class','puzzle'); this.el.appendChild(this.oPuzzle); } }, handle(){ //行为 var self = this; this.resetImg() this.oPuzzle.onclick = function(e){ var dom = e.target; // 通过判断点击的元素是否含有 'block' 是否为空白元素 var isBlock = dom.classList.contains('block') && dom.getAttribute('ref') === 'block'; if (isBlock) { self.handleBlock(dom) } } }, handleBlock(dom){ //点击元素后执行 var canMove = this.checkMove(dom); //是否满足交换条件 if(!canMove) return;// 不满足交换条件 //交换位置函数 this.moveBlock(dom); // 判断是否胜利 this.checkWin(); }, checkMove(dom){ //获取当前元素的行和列 var blockRow = this.getEleIndex(dom).row; var blockCol = this.getEleIndex(dom).col; //获取空白元素的行和列 var emptyRow = this.getEleIndex(this.oEmptyBlock).row; var emptyCol = this.getEleIndex(this.oEmptyBlock).col; //检查是否可以和空白元素交换 //行数与空白元素相差一行 且列相同 或者 列数相差一列 行相同 则满足与空白元素交换条件 return blockRow === emptyRow && Math.abs(blockCol - emptyCol) === 1 || blockCol === emptyCol && Math.abs(blockRow - emptyRow) === 1 }, getEleIndex(dom){//获取点击元素的行和列 var left = dom.offsetLeft/50; var top = dom.offsetTop/50; var row = Math.round(top / this.blockHeiht); var col = Math.round(left / this.blockWidth); return { row, col } }, moveBlock(oBlock){ //交换位置逻辑 将点击元素与空白元素 top 和 left 互换 // 获取block元素的left值和top值 var blockLeft = oBlock.style.left; var blockTop = oBlock.style.top; // 设置block元素的left和top为empty元素的left和top oBlock.style.left = this.oEmptyBlock.style.left; oBlock.style.top = this.oEmptyBlock.style.top; // 设置empty元素的left和top为block元素的left和top this.oEmptyBlock.style.left = blockLeft; this.oEmptyBlock.style.top = blockTop; }, checkWin(){ //将标准数组中的top 和 left 与 当前的backgroundPosition 的x y 进行对比 var isWin = true; for (var i = 0; i < this.oBlockMap.length; i++) { var oBlock = this.oBlockMap[i]; //top left var blockLeft = parseInt('-'+oBlock.style.left); var blockTop = parseInt('-'+oBlock.style.top); // x y var imgLeft = parseInt(oBlock.style.backgroundPositionX); var imgTop = parseInt(oBlock.style.backgroundPositionY); if (!(imgLeft == blockLeft && imgTop == blockTop)) { isWin=false; break; } } if (isWin) { this.GameWin(this.callBack) } }, GameWin(callBack){ //拼图成功后 拼图无法点击 空白区域显示 触发成功后的回调 this.oPuzzle.onclick = null; this.oEmptyBlock.style.opacity = 1; callBack() }, resetImg(){ //复原 var self = this ; document.querySelector('.reset').onclick = function(){ self.el.innerHTML=''; self.options.data.isReset =true self.init(self.options) } } } function Puzzle (options) { this.init(options); }
持续优化中...