邊做邊學 jQuery 系列 8- 益智拼圖盤
到目前為止,我們已學會了 jQuery 的基本運用技巧,可以開始做有一些有趣的東西。
不知道大家小時候有沒有玩過一種拼圖游戲,一個小盤子上有 8 塊或 15 塊的小圖塊,被放在 3x3 或 4x4 的空間中,由於存在一個空格,因此可以推動空格周圍的圖塊改變排列方式,最後的目標是要將圖塊組合成正確的排列。
沒有見過實體的話,不妨到 Amazon 上輸入 Slide Puzzle 查詢,可以看到一些照片。
這回,我們就來動手用 HTML+jQuery 實作一個 4x4 拼圖盤吧! 拼圖盤用網頁 HTML 實做,其實並不困難。從某個角度來看,HTML DOM 就是個物件導向的架構,一個個元素都是獨立的個體,能自行顯示,也可透過事件會對使用者的點選動作做出反應。圖塊移動的過程,不過就是座標位置的改變。
再進一步構思細節,我們可以想像,小圖塊就是一個個的 <div>,最外框再用一個 <div> 包起來當作拼圖盤,加上 CSS float:left 的設定,就能讓 <div> 排成 4x4 的配置。16 個 <div> 中有一個留白,其餘放入 1/16 大小的 <img> 圖塊。空白 <div> 周圍的 <div> 被點選時,就跟空白 <div> 交換位置,便可摸擬出將圖塊推到空白位置的效果。
經過這番講解,大家是不是覺得這個題材難易適中呢? 為了證明 jQuery 的簡潔,我們訂下一個目標,希望在 100 行內將這個網頁寫出來。
【基本排列】
第一步,我們要做出一個 <div> 內含 16 個 <div>,並讓它們排成 4x4 的配置。我們這裡運用了 append() 功能,在大 <div> 中新增 16 個小 <div>,將外層 div 設定寬高均為 480px,padding 0px,圖塊 div 則設成 118x118 的大小,加上四周各 1px 的框線,剛好變成 120x120,配合 float: left 的設定,480/120 整除為 4,就會變成 4x4 的排列。在框線設定上,我們將上 / 左設為白框線、下 / 右設為灰框線,讓圖塊呈現立體浮起的視覺效果。
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>jQuery Puzzle</title>
<script type="text/javascript" src="js/jquery-1.3.1.js"></script>
<style type="text/css">
#dvPuzzle {
width: 480px; height: 480px;
border: solid 5px blue;
padding: 0px;
}
.PicCell {
width: 118px; height: 118px;
border-top: solid 1px white;
border-left: solid 1px white;
border-right: solid 1px gray;
border-bottom: solid 1px gray;
float: left;
}
</style>
<script type="text/javascript">
$(function() {
//填入 16 張圖
for (var i=0; i<16; i++)
$("#dvPuzzle").append("<div class='PicCell' id='Pic" + i + "'></div>");
//塗上顏色以強化展示效果
$(".PicCell").css("background-color", "yellow");
});
</script>
</head>
<body>
<div id="dvPuzzle"></div>
</body>
</html>
【加上圖檔】
接著,我們準備一張 480x480 的照片,當作拼圖的對象。由於圖片要拆解成 16 個小圖塊,傳統做法可以利用修圖軟體分割照片,存成 16 個檔案。但在 CSS 中,還有其他解決方案。
我們先設定 <div>CSS 的 overflow 為 hidden 並限定 width / height,這樣子 <div> 內 <img> 尺寸較大超出範圍都會被隱藏。而對於 <img> 我們可以將 margin-top / margin-left 設定為負值,就可以自由控制圖檔案顯示的範圍。以下為裁切原則的解說;
因此我們在程式裡為圖塊 <div> 加入 <img> 並控制 margin-top / margin-left 顯示照片的不同部分。
$(function() {
//填入 16 張圖
for (var i=0; i<16; i++)
{
$("#dvPuzzle").append("<div class='PicCell' id='Pic" + i + "'><img src='Building.jpg' /></div>");
var row = parseInt(i / 4);
var col = i % 4;
$("#Pic" + i + " img").css("margin-left", col * -120 + 1).css("margin-top", row * -120 + 1);
}
//將左上角圖塊移除
$("#Pic0 img").remove();
});
除了修改程式 .PicCell 要補上 overflow: hidden 設定,拼圖盤就大致成形了。
【演算邏輯】
接下來就是要加上圖塊點選後的反應,依設計,空格上、下、左、右的相鄰圖塊,在點選後會移到空格所在位置,而其原有位置會變成空格。換句話說,我們也可以想成,各圖塊被點選時,就去檢查其上、下、左、右是否為空格 <div>,若有則與其交換位置,否則不做任何動作。
歸納下來,我們需要預先算出每個圖塊的上、下、左、右的圖塊各是哪一個(如果圖塊位於邊或角,要找的的相鄰圖塊只有三塊或兩塊),於是決定將這部分提出來變成函數。我們將第一列依序編號為 0, 1、2、3、第二列則為 4、5、6、7、其餘類推,則我們用 $("#dvPuzzle div").index (任一圖塊),就可以算出圖塊的位置編號,進了算出所在的列、行數,當作找出上、下、左、右相鄰圖塊的依據。我們用一個物件 posConv 來保存由位置編號轉為列號、行號的速查表。另外,用一個函數 getNearPos(i),傳入任一位置編號,就可得到上、下、左、右相鄰圖塊位置編號組成的陣列。
$(function() {
//將位置轉成座標的換算表
var posConv = { };
//填入 16 張圖
for (var i=0; i<16; i++)
{
$("#dvPuzzle").append("<div class='PicCell' id='Pic" + i + "'><img src='Building.jpg' /></div>");
var row = parseInt(i / 4);
var col = i % 4;
$("#Pic" + i + " img").css("margin-left", col * -120 + 1).css("margin-top", row * -120 + 1);
//第 i 個換成第 row 列第 col 行
posConv[i] = { row:row, col:col };
}
//將左上角圖塊移除
$("#Pic0 img").remove();
//取得四周相鄰的位置
function getNearPos(i) {
var pool = [];
var row = posConv[i].row, col = posConv[i].col;
//toCheck 用來放入待比對的對象
if (row > 0) //上
pool.push((row - 1) * 4 + col);
if (row < 4) //下
pool.push((row + 1) * 4 + col);
if (col > 0) //左
pool.push( i - 1);
if (col < 4) //右
pool.push(i + 1);
return pool;
}
});
我們的準備功夫差不多了,接下來就可以處理圖塊的事件。我們為所有 ".PicCell" 掛上 Click 事件,其中的處理流程是先找出點選對象的位置編號,用 getNearPos() 找到相鄰的圖塊,逐一檢查它是不是空格。如果是 (id == "Pic0"),則利用 after() 進行位置對調。
//點選動作
$(".PicCell").click(function() {
//找尋上下左右有沒有 Pic0,有則可以與它交換位置
//先找出元素是 16 個中第幾個?
var cells = $("#dvPuzzle div");
var i = cells.index(this);
var toCheck = getNearPos(i);
while (toCheck.length > 0) {
var j = toCheck.pop();
if (cells.eq(j).attr("id") == "Pic0") //為空白格,交換位子
{
//排序,必要時對調,讓 i < j
if (i > j) { var k = j; j = i; i = k; }
var ahead = cells.eq(i);
var behind = cells.eq(j);
var behindPrev = behind.prev();
//左右對調
if (Math.abs(i - j) == 1)
behind.after(ahead);
else //上下對調
{
ahead.after(behind);
behindPrev.after(ahead);
}
break;
}
}
});
做到這裡,拼圖盤已經能依我們所想的透過點選將圖檔推至空格,基本操作已然成形。
最後,我們加上一個 <input type="button">,並在其點擊時觸發將拼圖盤弄亂的動作,拼圖盤就完成了!
$("input:button").click(function() {
for (var i = 0; i < 500; i++) {
var cells = $("#dvPuzzle div");
//找出空格所在位置,並取得其相鄰圖塊
var toMove = getNearPos(cells.index($("#Pic0")[0]));
cells.eq(toMove[ //由空格的相鄰圖塊擇一挪動
parseInt(Math.random() * toMove.length)
]).click();
}
});
檢視一下成果,最後可以玩的成品,HTML+CSS+ 程式,並包含程式註解,在 100 行內完成,很不錯吧!
【範例檔案下載】
我們在乎每一位開發人員的感受 : 到 (Twitter) 公開您的想法、到 (論壇) 公開您的想法、寫悄悄話告訴我們
回到首頁