摘要:基本需求有一个固定区域,被拆分成个同等大小的碎片,拿走其中一块,靠近缺口的块可以向缺口方向移动当拼出原来的图样视为完成。左右移动很简单,序号大的序号小的即可。
先不废话,请看演示。
公司要搞这么个微信活动,可现在没有前端开发,没办法,身为打杂总监只好临时顶下这个空缺了。先找了一些 JS 代码,试用了下都不太理想,好一点的写的又太复杂,改起来有难度,干脆撸起袖子自己干。
基本需求有一个固定区域,被拆分成 c*r 个同等大小的碎片,拿走其中一块,靠近缺口的块可以向缺口方向移动;当拼出原来的图样视为完成。
依照此需求,需要经历 加载图片-》拆分图片-》随机打散-》移动碎片-》判定完成 这些步骤。为了更有可玩性,能自行选择自己的图片就更妙了。
下面就重点说明下各个步骤,为编写方便,引入 jQuery 作为辅助库。
加载图片首先当然是载入图片,计算宽高,对比拼图区域的尺寸进行缩放,如果比例不同,还得“裁剪”掉多余的部分。
/** * 加载图片 * cal 的回调参数为: * ox 横向偏移 * oy 纵向偏移 * this 指向载入的图片的 jQuery 对象 * @param {String} src 图片路径 * @param {int} w 额定宽 * @param {int} h 额定高 * @param {Fucntion} cal 加载完成后的回调方法 */ function loadr(src, w, h, cal) { var img = new Image(); img.onload = function() { var xw = img.width ; var xh = img.height; var zw = xh * w / h; if (zw > xw) { // 宽度优先 img.width = w; img.height = xh * w / xw; xh = (h - img.height) / 2; xw = 0; } else { // 高度优先 img.height = h; img.width = xw * h / xh; xw = (w - img.width ) / 2; xh = 0; } cal.call(img, xw, xh); }; img.src = src ; }
以上的“裁剪”仅仅是计算出偏移,然后将其传递给加载就绪的回调函数。
拆分图片图有了,已缩放,现在需要“拆分”成碎片。这里自然不是真的切割了,而是将图片 clone 出 c*r 片,然后利用负的坐标定位,其实质是用一个块遮盖了“切除”的部分,仅显示需要的碎片部分。
/** * 拆分图片 * @param {jQuery} that 容器对象 * @param {int} cols 行 * @param {int} rows 列 * @param {int} ew 板块宽度 * @param {int} eh 板块高度 * @param {int} ox 图片横向偏移 * @param {int} oy 图片纵向偏移 * @param {Image} im 图片对象 */ function split(that, cols, rows, ew, eh, ox, oy, im) { that.empty(); for(var j = 0 ; j < rows; j ++) { for(var i = 0 ; i < cols; i ++) { var k = i + j * rows; var pic = $(""); pic.attr("id", "pt-pic-"+k); pic.data("idx", k); pic.appendTo(that); pic.css ({ "position": "relative", "overflow": "hidden", "border" : "0", "width" : ew + "px", "height" : eh + "px" }); var img = $(im.cloneNode()); img.appendTo(pic); img.css ({ "position": "absolute", "z-index" : "88", "border" : "0", "left" : (0 - i * ew + ox) + "px", "top" : (0 - j * eh + oy) + "px" }); // 因边框可能影响宽高计算, 故边框多带带用一个块来放 var bor = $(""); bor.appendTo(pic); bor.css ({ "position": "absolute", "z-index" : "99", "width" : "100%", "height" : "100%" }); // 由于样式宽高并不含边框, 故再次计算尺寸的偏移量 bor.css ({ "width" : (2 * bor.width () - bor.outerWidth ()) + "px", "height" : (2 * bor.height() - bor.outerHeight()) + "px" }); } } }
稍微注意,为方便人眼分辨碎片,最好给碎片加个边框,但加边框必然影响坐标的计算,故在图片上再覆盖一层,边框设在他上面,就算加个撕裂效果的透明图做边框都没问题了。这样碎片内图片的偏移坐标的计算就少了些麻烦了。
随机打散这游戏当然是跟电脑玩了,总不能自己打散自己玩吧?但这个打散不能给每个图片一个随机位置,那很可能你永远也拼不回去了。小时拿那种拼图游戏板整人就干过这种事,故意抠下来把头和脚交换再打散,然后跟其他小朋友打赌。所以程序也得守规矩一块一块的移动。
/** * 打散图片 * @param {jQuery} that 容器对象 * @param {int} cols 列 * @param {int} rows 行 * @param {int} rand 打散步数 */ function upset(that, cols, rows, rand) { var v ; var r = Math.floor(Math.random() * cols * rows); var hole = that.children().eq(r).addClass("pt-pix"); var part ; var step = []; var dbug = []; for(var i = 0, j = rand; i < j; i ++) { var x = cols - 1; var y = rows - 1; var z = cols; var rx = r % cols; var ry = Math.floor(r / cols); var rv = []; if (rx > 0 && rx < x) { rv.push(r - 1, r + 1); // 可左右移动 } else if (rx > 0) { rv.push(r - 1); // 可向左移动 } else { rv.push(r + 1); // 可向右移动 } if (ry > 0 && ry < y) { rv.push(r - z, r + z); // 可上下移动 } else if (ry > 0) { rv.push(r - z); // 可向上移动 } else { rv.push(r + z); // 可向下移动 } // 排除来源位置 if (step.length > 0) { v = step[step.length - 1]; v = $.inArray(v, rv); if (v > -1) { rv.splice(v, 1 ); } } // 排除回旋位置 if (step.length > 2 && rv.length > 1) { v = step[step.length - 3]; v = $.inArray(v, rv); if (v > -1) { rv.splice(v, 1 ); } } // 随机方向 r = rv[Math.floor(Math.random()* rv.length)]; v = hole.index(); step.push(v); // 交换位置 part = that.children().eq( r ); if (r < v) { part.insertBefore(hole); hole.insertBefore(that.children().eq(r)); } else { hole.insertBefore(part); part.insertBefore(that.children().eq(v)); } // 调试步骤 if (r == v + 1) { dbug.push("左"); } else if (r == v - 1) { dbug.push("右"); } else if (r > v) { dbug.push("上"); } else if (r < v) { dbug.push("下"); } } // 攻略 dbug = dbug.reverse().join(" "); alert(dbug); console.log( "攻略: "+dbug+" 此非最优解, 仅为随机打散时的逆向步骤, 上下左右为相对缺口的板块, 祝您玩的开心!" ); }
把打散的步骤记录下来,然后反转数组,就是攻略啦。
不过随机时需要避免往回走,否则出现 左->右->左 这类情况就不好玩了;还得避免其他循环,如 上->右->下->左 这样的,这会回到原点,等于什么也没干;但更大的循环没想好怎么处理,暂时不去纠结了。
移动判定移动碎片到缺口,也就是交换碎片与缺口的位置。左右移动很简单,序号大的 insertBefore 序号小的即可。上下移动有个小坑,开始自己没注意,我原本想不管横向还是纵向,没有两次 insertBefore 搞不定的,但是如果 3 和 7 交换位置(3x3, 0~8),3 移动到 7 前,7 再移动到 3 前,此时原来的 3 变成了 6。的确,没有什么是不能两次 insertBefore 解决的,但还得考虑让序号大的先动。
/** * 移动板块 * @param {jQuery} that 容器对象 * @param {int} cols 列数 * @param {int} rows 行数 * @param {jQuery} hole 缺口对象 * @param {jQuery} part 板块对象 */ function mover(that, cols, rows, hole, part) { var move = false ; var i = part.index(); var j = hole.index(); var ix = i % cols; var jx = j % cols; var iy = Math.floor(i / cols); var jy = Math.floor(j / cols); if (iy == jy) { // 在同一行 move = ix == jx + 1 // 可向左边移动 || ix == jx - 1; // 可向右边移动 } else if (ix == jx) { // 在同一列 move = iy == jy + 1 // 可向上移动 || iy == jy - 1; // 可向下移动 } // 互换位置 if (move) { if (i < j ) { part.insertBefore(hole); hole.insertBefore(that.children().eq(i)); } else { hole.insertBefore(part); part.insertBefore(that.children().eq(j)); } } // 判断是否拼图完成 move = true; for (i = 0, j = cols * rows; i < j; i ++) { if (that.children().eq(i).data("idx") != i) { move = false; } } return move; }
判断是否完成就来个笨办法吧,依次遍历所有碎片,只要有一个没对上序号就是还没成功。
未处理滑动事件,以后闲了再加吧。
整合游戏程序上面分散的几个函数用起来还是不太方便,整合成一个 jQuery 插件。
/** * 拼图游戏 * @param {String} src 图片路径 * @param {int} cols 列数 * @param {int} rows 行数 * @param {int} rand 打散步数 */ $.fn.hsPintu = function(src, cols, rows, rand) { var that = $(this); var srz = that.data("src"); var img = that.data("img"); var aw = that.width (); var ah = that.height(); var ew = aw / rows; var eh = ah / cols; // 状态: 0 进行中, 1 成功, 2 结束 that.data("hsPintuStatus", 2); that.data("cols", cols); that.data("rows", rows); /** * img 存在且 src 没变化 * 则不需要再次加载图片 * 直接取出存储好的数据 */ if (img && srz === src) { var ox = that.data("pos_x"); var oy = that.data("pos_y"); console.log("Note: 图片无变化"); split(that, cols, rows, ew, eh, ox, oy, img ); // 未给 rand 则仅拆分而不打散 if (rand === undefined) return; upset(that, cols, rows, rand); that.data("hsPintuStatus", 0); that.trigger("hsPintuLaunch"); } else loadr(src, aw, ah, function(ox, oy) { that.data("src", src ); that.data("img", this); that.data("pos_x", ox); that.data("pos_y", oy); console.log("Note: 载入新图片"); split(that, cols, rows, ew, eh, ox, oy, this); // 未给 rand 则仅拆分而不打散 if (rand === undefined) return; upset(that, cols, rows, rand); that.data("hsPintuStatus", 0); that.trigger("hsPintuLaunch"); }); // 已经初始化过就不要再绑定事件了 if (! that.data("hsPintuInited")) { that.data("hsPintuInited", 1); that.on("click", ".pt-pic:not(.pt-pix)", function() { if (that.data("hsPintuStatus") === 0) { var cols =that.data("cols"); var rows =that.data("rows"); var hole =that.children(".pt-pix"); if (mover(that, cols, rows, hole, $(this))) { that.data("hsPintuStatus", 1); that.trigger("hsPintuFinish"); } } }); } return this; };
用 $("#pt-box").hsPintu(图片URL, 列数, 行数[, 随机步数]); 即可初始化拼图游戏了, 拼图区域需要固定宽高;随机步数参数不提供时,仅拆解不打散。
图片没变化时没必要重新加载,避免下时间损耗。当然了,更好的办法是再判断行、列和区域尺寸,没变化则直接排列好碎片。懒得写了,先这样吧。
选择任意图片上面都是固定的图片,参与感不好,让用户自行“上传”图片岂不更有意思。其实不必真的上传到服务器,既然“缩放”、“裁剪”上面都有了,直接加载本地图片不就好了嘛。
/** * 预载文件 * @param {Function} cal 回调函数 * @returns {jQuery} 当前文件节点 */ $.fn.hsFileLoad = function(cal) { this.each(function() { var that = this; if (window.FileReader) { var fr = new FileReader( ); fr.onloadend = function(e) { cal.call(that, e.target.result); }; cal.call(that); $.each( this.files, function(i, fo) { fr.readAsDataURL( fo ); }); } else if (this.getAsDataURL) { cal.call(that, that.getAsDataURL()); } else { cal.call(that, that.value); } }); return this; };
这段代码也能从我的开源项目内找到 预载文件 方法,此工具包还有些其他的文件上传预览类的方法,这是我对 bootstrap-fileinput 没有图片裁剪功能(与最终服务端处理后的结果一致)而“一气之下”自己写的一点零散代码。
完整的代码及演示可在 这里 看到,有朋友说看不到图,但图片我用的百度图片搜索的缩略图,不清楚怎么回事,看不到可以自己从本地选择图片。只是那个“加载”(Image.onload)和“切片”(Image.cloneNode)比较耗时,比较大的图片请耐心等等。
当然了,也可以光顾我们的活动页玩一把 拼图抽奖。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/49865.html
摘要:最近公司刚好有个活动是要做一版的拼图小游戏,于是自己心血来潮,自己先实现了一把,也算是尝尝鲜了。下面就把大体的思路介绍一下,希望大家都可以做出一款属于自己的拼图小游戏,必须是更炫酷,更好玩来吧,大家一起加油。。。 最近公司刚好有个活动是要做一版 html5的拼图小游戏,于是自己心血来潮,自己先实现了一把,也算是尝尝鲜了。下面就把大体的思路介绍一下,希望大家都可以做出一款属于自己的拼图小...
摘要:最近公司刚好有个活动是要做一版的拼图小游戏,于是自己心血来潮,自己先实现了一把,也算是尝尝鲜了。下面就把大体的思路介绍一下,希望大家都可以做出一款属于自己的拼图小游戏,必须是更炫酷,更好玩来吧,大家一起加油。。。 最近公司刚好有个活动是要做一版 html5的拼图小游戏,于是自己心血来潮,自己先实现了一把,也算是尝尝鲜了。下面就把大体的思路介绍一下,希望大家都可以做出一款属于自己的拼图小...
摘要:最近公司刚好有个活动是要做一版的拼图小游戏,于是自己心血来潮,自己先实现了一把,也算是尝尝鲜了。下面就把大体的思路介绍一下,希望大家都可以做出一款属于自己的拼图小游戏,必须是更炫酷,更好玩来吧,大家一起加油。。。 最近公司刚好有个活动是要做一版 html5的拼图小游戏,于是自己心血来潮,自己先实现了一把,也算是尝尝鲜了。下面就把大体的思路介绍一下,希望大家都可以做出一款属于自己的拼图小...
摘要:基本需求有一个固定区域,被拆分成个同等大小的碎片,拿走其中一块,靠近缺口的块可以向缺口方向移动当拼出原来的图样视为完成。左右移动很简单,序号大的序号小的即可。 先不废话,请看演示。 showImg(https://segmentfault.com/img/bVyoj2);showImg(https://segmentfault.com/img/bVyoj5); 公司要搞这么个微信活动...
阅读 1581·2021-11-16 11:45
阅读 2467·2021-09-29 09:48
阅读 3105·2021-09-07 10:26
阅读 1819·2021-08-16 10:50
阅读 1826·2019-08-30 15:44
阅读 2676·2019-08-28 18:03
阅读 1880·2019-08-27 10:54
阅读 1796·2019-08-26 14:01