摘要:基本需求有一個固定區域,被拆分成個同等大小的碎片,拿走其中一塊,靠近缺口的塊可以向缺口方向移動當拼出原來的圖樣視為完成。左右移動很簡單,序號大的序號小的即可。
先不廢話,請看演示。
公司要搞這么個微信活動,可現在沒有前端開發,沒辦法,身為打雜總監只好臨時頂下這個空缺了。先找了一些 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)比較耗時,比較大的圖片請耐心等等。
當然了,也可以光顧我們的活動頁玩一把 拼圖抽獎。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/49865.html
摘要:最近公司剛好有個活動是要做一版的拼圖小游戲,于是自己心血來潮,自己先實現了一把,也算是嘗嘗鮮了。下面就把大體的思路介紹一下,希望大家都可以做出一款屬于自己的拼圖小游戲,必須是更炫酷,更好玩來吧,大家一起加油。。。 最近公司剛好有個活動是要做一版 html5的拼圖小游戲,于是自己心血來潮,自己先實現了一把,也算是嘗嘗鮮了。下面就把大體的思路介紹一下,希望大家都可以做出一款屬于自己的拼圖小...
摘要:最近公司剛好有個活動是要做一版的拼圖小游戲,于是自己心血來潮,自己先實現了一把,也算是嘗嘗鮮了。下面就把大體的思路介紹一下,希望大家都可以做出一款屬于自己的拼圖小游戲,必須是更炫酷,更好玩來吧,大家一起加油。。。 最近公司剛好有個活動是要做一版 html5的拼圖小游戲,于是自己心血來潮,自己先實現了一把,也算是嘗嘗鮮了。下面就把大體的思路介紹一下,希望大家都可以做出一款屬于自己的拼圖小...
摘要:最近公司剛好有個活動是要做一版的拼圖小游戲,于是自己心血來潮,自己先實現了一把,也算是嘗嘗鮮了。下面就把大體的思路介紹一下,希望大家都可以做出一款屬于自己的拼圖小游戲,必須是更炫酷,更好玩來吧,大家一起加油。。。 最近公司剛好有個活動是要做一版 html5的拼圖小游戲,于是自己心血來潮,自己先實現了一把,也算是嘗嘗鮮了。下面就把大體的思路介紹一下,希望大家都可以做出一款屬于自己的拼圖小...
摘要:基本需求有一個固定區域,被拆分成個同等大小的碎片,拿走其中一塊,靠近缺口的塊可以向缺口方向移動當拼出原來的圖樣視為完成。左右移動很簡單,序號大的序號小的即可。 先不廢話,請看演示。 showImg(https://segmentfault.com/img/bVyoj2);showImg(https://segmentfault.com/img/bVyoj5); 公司要搞這么個微信活動...
閱讀 1614·2021-11-16 11:45
閱讀 2544·2021-09-29 09:48
閱讀 3269·2021-09-07 10:26
閱讀 1840·2021-08-16 10:50
閱讀 1866·2019-08-30 15:44
閱讀 2698·2019-08-28 18:03
閱讀 1898·2019-08-27 10:54
閱讀 1823·2019-08-26 14:01