整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          使用javascript中canvas實現拼圖小游戲

          使用javascript中canvas實現拼圖小游戲

          篇文章給大家帶來的內容是關于使用javascript中canvas實現拼圖小游戲 ,有一定的參考價值,有需要的朋友可以參考一下,希望對你有所幫助。

          如果您想要綜合使用javascript中canvas、原生拖拽、本地存儲等多種技術完成一個有趣的項目,那么這篇文章將非常適合您

          1 簡介和源碼

          該項目中的拼圖小游戲使用javascript原創,相比于網站上類似的功能,它使用到的技術點更先進豐富,功能更強大,還包含程序開發中更多先進的思想理念,從該項目中您將能學到:

          • FileReader、Image對象的配合canvas對圖片進行壓縮,切割的技巧。
          • 學習小游戲開發中最常用的碰撞檢測、狀態監控、刷新保持狀態的處理方法。
          • 深入了解拖拽交換元素的細節,學習到動態元素綁定事件、回調函數的處理方式。

          項目源碼-github

          下面是游戲界面的示例圖:

          2 實現思路

          根據游戲界面圖我們可以將完成這么一個小游戲分為以下幾步來實現:

          • 1.拖拽圖片到指定區域,使用FileReader對象讀取到圖片的base64內容,然后添加到Image對象中
          • 2.當Image對象加載完成后,使用canvas對圖片進行等比縮放,然后取到縮略圖的base64內容,添加到另外一個縮略圖Image對象中,并將該縮略圖base64的內容保存到本地存儲(localStorage)中
          • 3.當縮略圖Image對象加載完成后,再次使用canvas對縮略圖進行切割,該游戲中將縮略圖切割成3*4一共12等份,使用本地存儲保存每份切割縮略圖base64內容,將縮略圖順序打亂,使用img標簽顯示在web頁面上
          • 4.當縮略圖切片都添加到web界面上以后,為每一份縮略圖切片添加注冊拖拽事件,使得縮略圖切片可以相互交換,在這個過程當中,添加對縮略圖切片順序狀態的監控,一旦完成拼圖,就直接展示完整的縮略圖,完成游戲

          從以上對小游戲制作過程的分析來看,第4步是程序功能實現的重點和難點,在以上的每個步驟中都有很多小細節需要注意和探討,下面我就詳細分析一下每個步驟的實現細節,說的不好的地方,歡迎大家留言指正。

          3 開發細節詳解

          3.1 圖片內容讀取和加載

          在游戲開發第1步中,我們將圖片拖拽到指定區域后,程序是怎樣得到圖片內容信息的呢?fileReader對象又是怎樣將圖片信息轉化為base64字符串內容的?Image對象拿到圖片的base64內容之后,又是怎樣初始化加載的?帶著這些疑問,我們來研究一下實現項目中實現了第一步的關鍵代碼。

          var droptarget=document.getElementById("droptarget"),

          output=document.getElementById("ul1"),

          thumbImg=document.getElementById("thumbimg");

          //此處省略相關代碼........

          function handleEvent(event) {

          var info="",

          reader=new FileReader(),

          files, i, len;

          EventUtil.preventDefault(event);

          localStorage.clear();

          if (event.type=="drop") {

          files=event.dataTransfer.files;

          len=files.length;

          if (!/image/.test(files[0].type)) {

          alert('請上傳圖片類型的文件');

          }

          if (len > 1) {

          alert('上傳圖片數量不能大于1');

          }

          var canvas=document.createElement('canvas');

          var context=canvas.getContext('2d');

          var img=new Image(), //原圖

          thumbimg=new Image(); //等比縮放后的縮略圖

          reader.readAsDataURL(files[0]);

          reader.onload=function (e) {

          img.src=e.target.result;

          }

          //圖片對象加載完畢后,對圖片進行等比縮放處理。縮放后最大寬度為三百像素

          img.onload=function () {

          var targetWidth, targetHeight;

          targetWidth=this.width > 300 ? 300 : this.width;

          targetHeight=targetWidth / this.width * this.height;

          canvas.width=targetWidth;

          canvas.height=targetHeight;

          context.clearRect(0, 0, targetWidth, targetHeight);

          context.drawImage(img, 0, 0, targetWidth, targetHeight);

          var tmpSrc=canvas.toDataURL("image/jpeg");

          //在本地存儲完整的縮略圖源

          localStorage.setItem('FullImage', tmpSrc);

          thumbimg.src=tmpSrc;

          }

          //此處省略相關代碼......

          EventUtil.addHandler(droptarget, "dragenter", handleEvent);

          EventUtil.addHandler(droptarget, "dragover", handleEvent);

          EventUtil.addHandler(droptarget, "drop", handleEvent);

          }

          這段代碼的思路就是首先獲得拖拽區域目標對象droptarget,為droptarget注冊拖拽監聽事件。代碼中用到的EventUtil是我封裝的一個對元素添加事件、事件對象的兼容處理等常用功能的簡單對象,下面是其添加注冊事件的簡單簡單代碼,其中還有很多其他的封裝,讀者可自行查閱,功能比較簡單。

          var EventUtil={

          addHandler: function(element, type, handler){

          if (element.addEventListener){

          element.addEventListener(type, handler, false);

          } else if (element.attachEvent){

          element.attachEvent("on" + type, handler);

          } else {

          element["on" + type]=handler;

          }

          },

          //此處省略代......

          }

          當用戶將圖片文件拖放到區域目標對象droptarget時,droptarget的事件對象通過event.dataTransfer.files獲取到文件信息,對文件進行過濾(限制只能為圖片內容,并且最多只能有一張圖片)。拿到文件內容以后,使用FileReader對象reader讀取文件內容,使用其readAsDataURL方法讀取到圖片的base64內容,賦值給Image對象img的src屬性,就可以等到img對象初始化加載完畢,使canvas對img進行下一步的處理了。這里有一個重點的地方需要說明:一定要等img加載完成后,再使用canvas進行下一步的處理,不然可能會出現圖片損壞的情況。原因是:當img的src屬性讀取圖片文件的base64內容時,可能還沒有將內容加載到內存中時,canvas就開始處理圖片(此時的圖片是不完整的)。所以我們可以看到canvas對圖片的處理是放在img.onload方法中進行的,程序后邊還會有這種情況,之后就不再贅述了。

          3.2 圖片等比縮放和本地存儲

          在第一步中我們完成了對拖拽文件的內容讀取,并將其成功加載到了Image對象img中。接下來我們使用canvas對圖片進行等比縮放,對圖片進行等比縮放,我們采取的策略是限制圖片的最大寬度為300像素,我們再來看一下這部分代碼吧:

          img.onload=function () {

          var targetWidth, targetHeight;

          targetWidth=this.width > 300 ? 300 : this.width;

          targetHeight=targetWidth / this.width * this.height;

          canvas.width=targetWidth;

          canvas.height=targetHeight;

          context.clearRect(0, 0, targetWidth, targetHeight);

          context.drawImage(img, 0, 0, targetWidth, targetHeight);

          var tmpSrc=canvas.toDataURL("image/jpeg");

          //在本地存儲完整的縮略圖源

          localStorage.setItem('FullImage', tmpSrc);

          thumbimg.src=tmpSrc;

          }

          確定了縮放后的寬度targetWidth和高度targetHeight之后,我們使用canvas的drawImage方法對圖像進行壓縮,在這之前我們最好先使用畫布的clearRect對畫布進行一次清理。對圖片等比縮放以后,使用canvas的toDataURL方法,獲取到縮放圖的base64內容,賦給新的縮放圖Image對象thumbimg的src屬性,待縮放圖加載完畢,進行下一步的切割處理。縮放圖的base64內容使用localStorage存儲,鍵名為"FullImage"。瀏覽器的本地存儲localStorage是硬存儲,在瀏覽器刷新之后內容不會丟失,這樣我們就可以在游戲過程中保持數據狀態,這點稍后再詳細講解,我們需要知道的是localStorage是有大小限制的,最大為5M。這也是為什么我們先對圖片進行壓縮,減少存儲數據大小,保存縮放圖base64內容的原因。關于開發過程中存儲哪些內容,下一小節會配有圖例詳細說明。

          3.3 縮略圖切割

          生成縮略圖之后要做的工作就是對縮略圖進行切割了,同樣的也是使用canvas的drawImage方法,而且相應的處理必須放在縮略圖加載完成之后(即thumbimg.onload)進行處理,原因前面我們已經說過。下面我們再來詳細分析一下源代碼吧:

          thumbimg.onload=function () {

          //每一個切片的寬高[切割成3*4格式]

          var sliceWidth, sliceHeight, sliceBase64, n=0, outputElement='',

          sliceWidth=this.width / 3,

          sliceHeight=this.height / 4,

          sliceElements=[];

          canvas.width=sliceWidth;

          canvas.height=sliceHeight;

          for (var j=0; j < 4; j++) {

          for (var i=0; i < 3; i++) {

          context.clearRect(0, 0, sliceWidth, sliceHeight);

          context.drawImage(thumbimg, sliceWidth * i, sliceHeight * j, sliceWidth, sliceHeight, 0, 0, sliceWidth, sliceHeight);

          sliceBase64=canvas.toDataURL("image/jpeg");

          localStorage.setItem('slice' + n, sliceBase64);

          //為了防止圖片三像素問題發生,請為圖片屬性添加 display:block

          newElement="<li name=\"" + n + "\" style=\"margin:3px;\"><img src=\"" + sliceBase64 + "\" style=\"display:block;\"></li>";

          //根據隨機數打亂圖片順序

          (Math.random() > 0.5) ? sliceElements.push(newElement) : sliceElements.unshift(newElement);

          n++;

          }

          }

          //拼接元素

          for (var k=0, len=sliceElements.length; k < len; k++) {

          outputElement +=sliceElements[k];

          }

          localStorage.setItem('imageWidth', this.width + 18);

          localStorage.setItem('imageHeight', this.height + 18);

          output.style.width=this.width + 18 + 'px';

          output.style.height=this.height + 18 + 'px';

          (output.innerHTML=outputElement) && beginGamesInit();

          droptarget.remove();

          }

          上面的代碼對于大家來說不難理解,就是將縮略圖分割成12個切片,這里我給大家解釋一下幾個容易困惑的地方:

          • 1.為什么我們再切割圖片的時候,代碼如下,先從列開始循環?

          for (var j=0; j < 4; j++) {

          for (var i=0; i < 3; i++) {

          //此處省略邏輯代碼

          }

          }

          這個問題大家仔細想一想就明白了,我們將圖片進行切割的時候,要記錄下來每一個圖片切片的原有順序。在程序中我們使用 n 來表示圖片切片的原有順序,而且這個n記錄在了每一個圖片切片的元素的name屬性中。在后續的游戲過程中我們可以使用元素的getAttribute('name')方法取出 n 的值,來判斷圖片切片是否都被拖動到了正確的位置,以此來判斷游戲是否結束,現在講起這個問題可能還會有些迷惑,我們后邊還會再詳細探討,我給出一張圖幫助大家理解圖片切片位置序號信息n:

          序號n從零開始是為了和javascript中的getElementsByTagName()選擇的子元素坐標保持一致。

          • 2 我們第3步實現的目的不僅是將縮略圖切割成小切片,還要將這些圖片切片打亂順序,代碼程序中這一點是怎樣實現的?
          • 閱讀代碼程序我們知道,我們每生成一個切片,就會構造一個元素節點: newElement="<li name=\"" + n + "\" style=\"margin:3px;\"><img src=\"" + sliceBase64 + "\" style=\"display:block;\"></li>"; 。我們在是在外部先聲明了一個放新節點的數組sliceElements,我們每生成一個新的元素節點,就會把它放到sliceElements數組中,但是我們向sliceElements頭部還是尾部添加這個新節點則是隨機的,代碼是這樣的:

          (Math.random() > 0.5) ? sliceElements.push(newElement) : sliceElements.unshift(newElement);

          我們知道Math.random()生成一個[0, 1)之間的數,所以再canvas將縮略圖裁切成切片以后,根據這些切片生成的web節點順序是打亂的。打亂順序以后重新組裝節點:

          //拼接元素

          for (var k=0, len=sliceElements.length; k < len; k++) {

          outputElement +=sliceElements[k];

          }

          然后再將節點添加到web頁面中,也就自然而然出現了圖片切片被打亂的樣子了。

          • 3.我們根據縮略圖切片生成的DOM節點是動態添加的元素,怎樣給這樣動態元素綁定事件呢?我們的項目中為每個縮略圖切片DOM節點綁定的事件是“拖動交換”,和其他節點都有關系,我們要保證所有的節點都加載后再對事件進行綁定,我們又是怎樣做到的呢?

          下面的一行代碼,雖然簡單,但是用的非常巧妙:

          (output.innerHTML=outputElement) && beginGamesInit();

          有開發經驗的同學都知道 && 和 || 是短路運算符,代碼中的含義是:只有當切片元素節點都添加到

          WEB頁面之后,才會初始化為這些節點綁定事件。

          3.4 本地信息存儲

          代碼中多次用到了本地存儲,下面我們來詳細解釋一下本游戲開發過程中都有哪些信息需要存儲,為什么要存儲?下面是我給出的需要存儲的信息圖示例(從瀏覽器控制臺獲取):

          瀏覽器本地存儲localStorage使用key:value形式存儲,從圖中我們看到我們本次存儲的內容有:

          • FullImage:圖片縮略圖base64編碼。
          • imageWidth:拖拽區域圖片的寬度。
          • imageHeight:拖拽區域圖片的高度。
          • slice*:每一個縮略圖切片的base64內容。
          • nodePos:保存的是當前縮略圖的位置坐標信息。

          保存FullImage縮略圖的信息是當游戲結束后顯示源縮略圖時,根據FullImage中的內容展示圖片。而imageWidth,imageHeight,slice*,nodePos是為了防止瀏覽器刷新導致數據丟失所做的存儲,當刷新頁面的時候,瀏覽器會根據本地存儲的數據加載沒有完成的游戲內容。其中nodePos是在為縮略圖切片發生拖動時存入本地存儲的,并且它隨著切片位置的變化而變化,也就是它追蹤著游戲的狀態,我們在接下來的代碼功能展示中會再次說到它。

          3.5 拖拽事件注冊和監控

          接下來我們要做的事才是游戲中最重要的部分,還是先來分析一下代碼,首先是事件注冊前的初始化工作:

          //游戲開始初始化

          function beginGamesInit() {

          aLi=output.getElementsByTagName("li");

          for (var i=0; i < aLi.length; i++) {

          var t=aLi[i].offsetTop;

          var l=aLi[i].offsetLeft;

          aLi[i].style.top=t + "px";

          aLi[i].style.left=l + "px";

          aPos[i]={left: l, top: t};

          aLi[i].index=i;

          //將位置信息記錄下來

          nodePos.push(aLi[i].getAttribute('name'));

          }

          for (var i=0; i < aLi.length; i++) {

          aLi[i].style.position="absolute";

          aLi[i].style.margin=0;

          setDrag(aLi[i]);

          }

          }

          可以看到這部分初始化綁定事件代碼所做的事情是:記錄每一個圖片切片對象的位置坐標相關信息記錄到對象屬性中,并為每一個對象都注冊拖拽事件,對象的集合由aLi數組統一管理。這里值得一提的是圖片切片的位置信息index記錄的是切片現在所處的位置,而我們前邊所提到的圖片切片name屬性所保存的信息n則是圖片切片原本應該所處的位置,在游戲還沒有結束之前,它們不一定相等。待所有的圖片切片name屬性所保存的值和其屬性index都相等時,游戲才算結束(因為用戶已經正確完成了圖片的拼接),下面的代碼就是用來判斷游戲狀態是否結束的,看起來更直觀一些:

          //判斷游戲是否結束

          function gameIsEnd() {

          for (var i=0, len=aLi.length; i < len; i++) {

          if (aLi[i].getAttribute('name') !=aLi[i].index) {

          return false;

          }

          }

          //后續處理代碼省略......

          }

          下面我們還是詳細說一說拖拽交換代碼相關邏輯吧,拖拽交換的代碼如下圖所示:

          //拖拽

          function setDrag(obj) {

          obj.onmouseover=function () {

          obj.style.cursor="move";

          console.log(obj.index);

          }

          obj.onmousedown=function (event) {

          var scrollTop=document.documentElement.scrollTop || document.body.scrollTop;

          var scrollLeft=document.documentElement.scrollLeft || document.body.scrollLeft;

          obj.style.zIndex=minZindex++;

          //當鼠標按下時計算鼠標與拖拽對象的距離

          disX=event.clientX + scrollLeft - obj.offsetLeft;

          disY=event.clientY + scrollTop - obj.offsetTop;

          document.onmousemove=function (event) {

          //當鼠標拖動時計算p的位置

          var l=event.clientX - disX + scrollLeft;

          var t=event.clientY - disY + scrollTop;

          obj.style.left=l + "px";

          obj.style.top=t + "px";

          for (var i=0; i < aLi.length; i++) {

          aLi[i].className="";

          }

          var oNear=findMin(obj);

          if (oNear) {

          oNear.className="active";

          }

          }

          document.onmouseup=function () {

          document.onmousemove=null; //當鼠標彈起時移出移動事件

          document.onmouseup=null; //移出up事件,清空內存

          //檢測是否普碰上,在交換位置

          var oNear=findMin(obj);

          if (oNear) {

          oNear.className="";

          oNear.style.zIndex=minZindex++;

          obj.style.zIndex=minZindex++;

          startMove(oNear, aPos[obj.index]);

          startMove(obj, aPos[oNear.index], function () {

          gameIsEnd();

          });

          //交換index

          var t=oNear.index;

          oNear.index=obj.index;

          obj.index=t;

          //交換本次存儲中的位置信息

          var tmp=nodePos[oNear.index];

          nodePos[oNear.index]=nodePos[obj.index];

          nodePos[obj.index]=tmp;

          localStorage.setItem('nodePos', nodePos);

          } else {

          startMove(obj, aPos[obj.index]);

          }

          }

          clearInterval(obj.timer);

          return false;//低版本出現禁止符號

          }

          }

          這段代碼所實現的功能是這樣子的:拖動一個圖片切片,當它與其它的圖片切片有碰撞重疊的時候,就和與其左上角距離最近的一個圖片切片交換位置,并交換其位置信息index,更新本地存儲信息中的nodePos。移動完成之后判斷游戲是否結束,若沒有,則期待下一次用戶的拖拽交換。

          下面我來解釋一下這段代碼中比較難理解的幾個點:

          • 1.圖片切片在被拖動的過程中是怎樣判斷是否和其它圖片切片發生碰撞的?這就是典型的碰撞檢測問題。
          • 程序中實現碰撞檢測的代碼是這樣的:

          //碰撞檢測

          function colTest(obj1, obj2) {

          var t1=obj1.offsetTop;

          var r1=obj1.offsetWidth + obj1.offsetLeft;

          var b1=obj1.offsetHeight + obj1.offsetTop;

          var l1=obj1.offsetLeft;

          var t2=obj2.offsetTop;

          var r2=obj2.offsetWidth + obj2.offsetLeft;

          var b2=obj2.offsetHeight + obj2.offsetTop;

          var l2=obj2.offsetLeft;

          `if (t1 > b2 || r1 < l2 || b1 < t2 || l1 > r2)` {

          return false;

          } else {

          return true;

          }

          }

          這段代碼看似信息量很少,其實也很好理解,判斷兩個圖片切片是否發生碰撞,只要將它們沒有發生碰撞的情形排除掉就可以了。這有點類似與邏輯中的非是即否,兩個切片又確實只可能存在兩種情況:碰撞、不碰撞。圖中的這段代碼是判斷不碰撞的情況:if (t1 > b2 || r1 < l2 || b1 < t2 || l1 > r2),返回false, else 返回true。

          2.碰撞檢測完成了之后,圖片切片之間又是怎樣尋找左上角定點距離最近的元素呢?

          代碼是這個樣子的:

          //勾股定理求距離(左上角的距離)

          function getDis(obj1, obj2) {

          var a=obj1.offsetLeft - obj2.offsetLeft;

          var b=obj1.offsetTop - obj2.offsetTop;

          return Math.sqrt(Math.pow(a, 2) + Math.pow(b, 2));

          }

          //找到距離最近的

          function findMin(obj) {

          var minDis=999999999;

          var minIndex=-1;

          for (var i=0; i < aLi.length; i++) {

          if (obj==aLi[i]) continue;

          if (colTest(obj, aLi[i])) {

          var dis=getDis(obj, aLi[i]);

          if (dis < minDis) {

          minDis=dis;

          minIndex=i;

          }

          }

          }

          if (minIndex==-1) {

          return null;

          } else {

          return aLi[minIndex];

          }

          }

          因為都是矩形區塊,所以計算左上角的距離使用勾股定理,這點相信大家都能明白。查找距離最近的元素原理也很簡單,就是遍歷所有已經碰撞的元素,然后比較根據勾股定理計算出來的最小值,返回元素就可以了。代碼中也是使用了比較通用的方法,先聲明一個很大的值最為最小值,當有碰撞元素比其小時,再將更小的值最為最小值,遍歷完成后,返回最小值的元素就可以了。

          • 3.圖片區塊每次交換之后,是怎樣監控判斷游戲是否已經結束的呢?

          答案是回調函數,圖片切片交換函數通過回調函數來判斷游戲是否已經結束,游戲是否結束的判斷函數前面我們已經說過。圖片切片交換函數就是通過添加gameIsEnd作為回調函數,這樣在每次圖片切片移動交換完成之后,就判斷一下游戲是否結束。圖片切片的交換函數還是比較復雜的,有興趣的同學可以研究一下,下面是其實現代碼,大家重點理解其中添加了回調函數監控游戲是否結束就好了。

          //通過class獲取元素

          function getClass(cls){

          var ret=[];

          var els=document.getElementsByTagName("*");

          for (var i=0; i < els.length; i++){

          //判斷els[i]中是否存在cls這個className;.indexOf("cls")判斷cls存在的下標,如果下標>=0則存在;

          if(els[i].className===cls || els[i].className.indexOf("cls")>=0 || els[i].className.indexOf(" cls")>=0 || els[i].className.indexOf(" cls ")>0){

          ret.push(els[i]);

          }

          }

          return ret;

          }

          function getStyle(obj,attr){//解決JS兼容問題獲取正確的屬性值

          return obj.currentStyle?obj.currentStyle[attr]:getComputedStyle(obj,false)[attr];

          }

          function gameEnd() {

          alert('游戲結束!');

          }

          function startMove(obj,json,fun){

          clearInterval(obj.timer);

          obj.timer=setInterval(function(){

          var isStop=true;

          for(var attr in json){

          var iCur=0;

          //判斷運動的是不是透明度值

          if(attr=="opacity"){

          iCur=parseInt(parseFloat(getStyle(obj,attr))*100);

          }else{

          iCur=parseInt(getStyle(obj,attr));

          }

          var ispeed=(json[attr]-iCur)/8;

          //運動速度如果大于0則向下取整,如果小于0想上取整;

          ispeed=ispeed>0?Math.ceil(ispeed):Math.floor(ispeed);

          //判斷所有運動是否全部完成

          if(iCur!=json[attr]){

          isStop=false;

          }

          //運動開始

          if(attr=="opacity"){

          obj.style.filter="alpha:(opacity:"+(json[attr]+ispeed)+")";

          obj.style.opacity=(json[attr]+ispeed)/100;

          }else{

          obj.style[attr]=iCur+ispeed+"px";

          }

          }

          //判斷是否全部完成

          if(isStop){

          clearInterval(obj.timer);

          if(fun){

          fun();

          }

          }

          },30);

          }

          4 補充和總結

          4.1 游戲中值得完善的功能

          我認為該游戲中值得優化的地方有兩個:

          • 1.為拼圖小游戲添加縮略圖,因為縮略圖有利于為玩游戲的用戶提供思路。我們又在瀏覽器本地存儲中保存了縮略圖的base64內容,所以實現起來也很容易。
          • 2.緩存有的時候也讓人很痛苦,就比如說在游戲中有些用戶就想要重新開始,而我們的小游戲只有在游戲完成之后才清空緩存,刷新頁面,游戲才能夠重新開始。這給用戶的體驗很不好,我們可以加一個重置游戲按鈕,清空緩存并優化游戲結束后的一些邏輯。

          這些功能感興趣的小伙伴可以嘗試一下。

          相關推薦:

          用javascript實現web拼圖游戲

          H5的canvas實現貪吃蛇小游戲

          以上就是使用javascript中canvas實現拼圖小游戲的詳細內容,更多請關注其它相關文章!

          更多技巧請《轉發 + 關注》哦!

          、canvas簡介

          1. canvas是HTML5提供的一種新標簽,雙標簽;
          2. HTML5 canvas標簽元素用于圖形的繪制,通過腳本 (通常是JavaScript)來完成;
          3. canvas標簽只是圖形容器,必須使用腳本來繪制圖形;

          Canvas是一個矩形區域的畫布,可以用JavaScript在上面繪畫;


          二、案例目標

          我們今天的目標是使用HTML5畫布技術制作一款拼圖小游戲,要求將圖像劃分為3*3的9塊方塊并打亂排序,用戶可以移動方塊拼成完整圖片。

          效果如下所示:


          三、程序流程

          3.1 HTML靜態頁面布局

          <div id="container">
                      <!--頁面標題-->
                      <h3>HTML5畫布綜合項目之拼圖游戲</h3>
                      <!--水平線-->
                      <hr />
                      <!--游戲內容-->
                      <!--游戲時間-->        
                      <div id="timeBox">
                          共計時間:<span id="time">00:00:00</span>
                      </div>
                      <!--游戲畫布-->
                      <canvas id="myCanvas" width="300" height="300" style="border:1px solid">
                          對不起,您的瀏覽器不支持HTML5畫布API。
                      </canvas>
                      <!--游戲按鈕-->
                      <div>
                          <button onclick="restartGame()">
                              重新開始
                          </button>
                      </div>  
          </div>

          效果如下所示:

          我們可以看到頁面的大致結構是已經顯現出來了,就是骨架已經搭建好了,現在我們要使用css強化樣式;


          3.2 CSS打造頁面樣式

          整體背景設置

          body {
              background-color: silver;/*設置頁面背景顏色為銀色*/
          }

          游戲界面樣式設置

          #container {
              background-color: white;
              width: 600px;   
              margin: auto;
              padding: 20px;
              text-align: center; 
              box-shadow: 10px 10px 15px black;
          }

          游戲時間面板樣式設置

          #timeBox {
              margin: 10px 0;
              font-size: 18px;
          }

          游戲按鈕樣式設置

          button {
              width: 200px;
              height: 50px;
              margin: 10px 0;
              border: 0;
              outline: none;
              font-size: 25px;
              font-weight: bold;
              color: white;  
              background-color: lightcoral;
          }

          鼠標懸浮時的按鈕樣式設置

          button:hover {
              background-color: coral;
          }

          設置好界面整體樣式之后我們得到完整的界面,如下所示:

          可以看到整體的靜態界面已經搭建出來了


          3.3 js構建交互效果

          3.3.1 對象的獲取以及圖片的設置

          目標對象的獲取

          var c=document.getElementById('myCanvas'); //獲取畫布對象
          var ctx=c.getContext('2d'); //獲取2D的context對象

          聲明拼圖的圖片素材來源

          var img=new Image();
          img.src="image/pintu.jpg";
                          
          img.onload=function() { //當圖片加載完畢時
              generateNum(); //打亂拼圖的位置
              drawCanvas(); //在畫布上繪制拼圖
          }

          3.3.2 初始化拼圖

          • 需要將素材圖片分割成3行3列的9個小方塊,并打亂順序放置在畫布上;
          • 為了在游戲過程中便于查找當前的區域該顯示圖片中的哪一個方塊,首先為原圖片上的9個小方塊區域進行編號;


          定義初始方塊位置

          var num=[[00, 01, 02], [10, 11, 12], [20, 21, 22]];


          打亂拼圖的位置

          function generateNum() { //循環50次進行拼圖打亂    
                   for (var i=0; i < 50; i++) {
                //隨機抽取其中一個數據
                      var i1=Math.round(Math.random() * 2);
                      var j1=Math.round(Math.random() * 2);
                //再隨機抽取其中一個數據
                      var i2=Math.round(Math.random() * 2);
                      var j2=Math.round(Math.random() * 2);
                //對調它們的位置
                      var temp=num[i1][j1];
                      num[i1][j1]=num[i2][j2];
                      num[i2][j2]=temp;
             }
          }


          繪制拼圖

          自定義名稱的drawCanvas()方法用于在畫布上繪制亂序后的圖片;

          function drawCanvas() {
              //清空畫布
              ctx.clearRect(0, 0, 300, 300);
              //使用雙重for循環繪制3x3的拼圖
              for (var i=0; i < 3; i++) {
                  for (var j=0; j < 3; j++) {
                      if (num[i][j] !=22) {
                          //獲取數值的十位數,即第幾行
                          var row=parseInt(num[i][j] / 10);
                          //獲取數組的個位數,即第幾列
                          var col=num[i][j] % 10;
                          //在畫布的相關位置上繪圖
                          ctx.drawImage(img, col * w, row * w, w, w, j * w, i * w, w, w); // w:300 / 3=100(小圖寬度)
                      }
                  }
              }
          }

          如下所示:

          3.3.3 事件綁定

          監聽鼠標監聽事件

          c.onmousedown=function(e) {
              var bound=c.getBoundingClientRect(); //獲取畫布邊界
              
              var x=e.pageX - bound.left; //獲取鼠標在畫布上的坐標位置(x,y)
              var y=e.pageY - bound.top;
          
          
              var row=parseInt(y / w); //將x和y換算成幾行幾列
              var col=parseInt(x / w);
          
          
              
              if (num[row][col] !=22) { //如果當前點擊的不是空白區域
                  detectBox(row, col); //移動點擊的方塊
                  drawCanvas(); //重新繪制畫布
                  var isWin=checkWin(); //檢查游戲是否成功
                  
                  if (isWin) { //如果游戲成功
                      clearInterval(timer); //清除計時器
                      ctx.drawImage(img, 0, 0); //繪制完整圖片
                      ctx.font="bold 68px serif"; //設置字體為加粗、68號字,serif
                      ctx.fillStyle="red"; //設置填充色為紅色
                      ctx.fillText("游戲成功!", 20, 150); //顯示提示語句
                  }
              }
          }

          點擊方塊移動

          function detectBox(i, j) {
              //如果點擊的方塊不在最上面一行
              if (i > 0) {
                  //檢測空白區域是否在當前方塊的正上方
                  if (num[i-1][j]==22) {
                      //交換空白區域與當前方塊的位置
                      num[i-1][j]=num[i][j];
                      num[i][j]=22;
                      return;
                  }
              }
              //如果點擊的方塊不在最下面一行
              if (i < 2) {
                  //檢測空白區域是否在當前方塊的正下方
                  if (num[i+1][j]==22) {
                      //交換空白區域與當前方塊的位置
                      num[i+1][j]=num[i][j];
                      num[i][j]=22;
                      return;
                  }
              }
              //如果點擊的方塊不在最左邊一列
              if (j > 0) {
                  //檢測空白區域是否在當前方塊的左邊
                  if (num[i][j - 1]==22) {
                      //交換空白區域與當前方塊的位置
                      num[i][j - 1]=num[i][j];
                      num[i][j]=22;
                      return;
                  }
              }
              //如果點擊的方塊不在最右邊一列
              if (j < 2) {
                  //檢測空白區域是否在當前方塊的右邊
                  if (num[i][j + 1]==22) {
                      //交換空白區域與當前方塊的位置
                      num[i][j + 1]=num[i][j];
                      num[i][j]=22;
                      return;
                  }
              }
          }


          3.3.4 游戲計時

          • 自定義函數getCurrentTime()用于進行游戲計時;
          • function getCurrentTime() {
            s=parseInt(s);
            //將時分秒轉換為整數以便進行自增或賦值
            m=parseInt(m);
            h=parseInt(h);
            s++;
            //每秒變量s先自增1

            if (s==60) {
            s=0;
            //如果秒已經達到60,則歸0
            m++;
            //分鐘自增1
            }
            if (m==60) {
            m=0;
            //如果分鐘也達到60,則歸0
            h++;
            //小時自增1
            }


            //修改時分秒的顯示效果,使其保持兩位數
            if (s < 10)
            s="0" + s;
            if (m < 10)
            m="0" + m;
            if (h < 10)
            h="0" + h;
            time.innerHTML=h + ":" + m + ":" + s;
            //將當前計時的時間顯示在頁面上
            }
          • 在JavaScript中使用setInterval()方法每隔1秒鐘調用getCurrentTime()方法一次,以實現更新效果;var timer=setInterval("getCurrentTime()", 1000);


          3.3.5 游戲成功與重新開始

          游戲成功判定與顯示效果的實現

          • 自定義函數checkWin()用于進行游戲成功判斷;
          function restartGame() {
              clearInterval(timer);  //清除計時器
              s=0; //時間清零
              m=0;
              h=0;
              getCurrentTime();  //重新顯示時間
              timer=setInterval("getCurrentTime()", 1000);
           
              generateNum(); //重新打亂拼圖順序
              drawCanvas(); //繪制拼圖
              
          }
          • 如果成功則使用clearInterval()方法清除計時器。然后在畫布上繪制完整圖片,并使用fillText()方法繪制出“游戲成功”的文字圖樣;if (isWin) { //如果游戲成功
            clearInterval(timer);
            //清除計時器
            ctx.drawImage(img, 0, 0);
            //繪制完整圖片
            ctx.font="bold 68px serif";
            //設置字體為加粗、68號字,serif
            ctx.fillStyle="red";
            //設置填充色為紅色
            ctx.fillText("游戲成功!", 20, 150);
            //顯示提示語句
            }

          3.4 最終效果演示

          靜態效果如上所示,至于游戲成功這里伙計們可以自行操作;


          四、總結

          本次案例我們使用HTML5的新特性canvas畫布標簽打造了簡單的9宮格拼圖游戲,總體來說沒有特別的復雜,主要是圖片的分割方塊移動事件的綁定,以及重新游戲的初始化操作,明確了游戲邏輯之后其實代碼的編寫其實不難。感興趣的小伙伴可以去嘗試一下。

          TML5 CSS3 3D魔方拼圖在線益智小游戲網頁開發。一款基于HTML5和CSS3的3D立方體拼圖應用,一共有8個小立方體組成的3D拼圖,我們可以點擊立方體或者方向鍵完成拼圖,同時我們也可以讓立方體保持旋轉。采用響應式設計,自適應手機移動端,用戶體驗友好。

          ps:推薦一下我的微細公眾號:webqiand,學習前端有不懂的(學習方法,學習路線,如何學習有效率的問題)可以關一下,公眾號有不錯的學習教程,開發工具、電子書籍分享。

          部分源碼展示,由于代碼有點多,超出頭條字數,發布不了

          <!DOCTYPE html>
          <html lang="en">
           
           <head>
           <meta charset="UTF-8">
           <meta name="viewport" content="width=device-width, initial-scale=1">
           <title>HTML5 CSS3 3D魔方拼圖在線益智小游戲網頁開發</title>
           <meta name="keywords" content="HTML5,CSS3,3D魔方,拼圖,在線益智小游戲,網頁開發" />
           <meta name="description" content="HTML5 CSS3 3D魔方拼圖在線益智小游戲網頁開發。一款基于HTML5和CSS3的3D立方體拼圖應用,一共有8個小立方體組成的3D拼圖,我們可以點擊立方體或者方向鍵完成拼圖,同時我們也可以讓立方體保持旋轉。采用響應式設計,自適應手機移動端,用戶體驗友好。" /> 
           <link rel='stylesheet prefetch' >
           <link rel="stylesheet" href="css/style.css">
           </head>
           
           <body>
           <div class="body-wrapper">
           <div class="cubetwo-help-component">
           <div class="cubetwo-row">
           <div class="cubetwo-device-info">
           <i class="material-icons">
           touch_app
           </i>
           <div>
           tap or swipe with fingers
           </div>
           </div>
           <div class="cubetwo-device-info">
           <i class="material-icons">
           mouse
           </i>
           <div>
           click or swipe with mouse
           </div>
           </div>
           <div class="cubetwo-device-info cubetwo-device-info--keyboard">
           <i class="material-icons">
           keyboard
           </i>
           <div>
           keyboard keys
           </div>
           <div class="cubetwo-device-info-groups">
           <div class="cubetwo-device-info-group">
           <div>
           <i class="material-icons">
           keyboard_tab
           </i>
           <i class="material-icons">
           keyboard_arrow_up
           </i>
           </div>
           <div>
           <i class="material-icons">
           keyboard_arrow_left
           </i>
           <i class="material-icons">
           keyboard_arrow_down
           </i>
           <i class="material-icons">
           keyboard_arrow_right
           </i>
           </div>
           </div>
           <div class="cubetwo-device-info-group">
           <div>
           <span>
           q
           </span>
           <span>
           w
           </span>
           <span>
           e
           </span>
           </div>
           <div>
           <span>
           a
           </span>
           <span>
           s
           </span>
           <span>
           d
           </span>
           </div>
           <div>
           <span>
           x
           </span>
           <span>
           y
           </span>
           <span>
           z
           </span>
           </div>
           </div>
           </div>
           </div>
           </div>
           <a  target="_blank" class="cubetwo-github-link">
           github project
           </a>
           </div>
           <div class="cubetwo-menu-component">
           <div class="cubetwo-row">
           <button class="cubetwo-js cubetwo-btn cubetwo-btn-scramble">
           scramble
           </button>
           <button class="cubetwo-js cubetwo-btn cubetwo-btn-spin">
           spin
           </button>
           <button class="cubetwo-js cubetwo-btn cubetwo-btn-solve">
           solve
           </button>
           </div>
           </div>
           <div class="cubetwo-component" id="cubetwo-component-1">
           <div class="cubetwo-rotation-view">
           <div class="cubetwo-cube-group cubetwo-cube-group--1">
           <div class="cubetwo-cube-1" tabindex="0" data-type="cubetwo">
           <div class="cubetwo-cube" data-type="cubetwo-display" data-index="1">
           <div data-type="front">
           <div>
           front
           </div>
           </div>
           <div data-type="up">
           <div>
           up
           </div>
           </div>
           <div data-type="right">
           <div>
           right
           </div>
           </div>
           <div data-type="back">
           <div>
           back
           </div>
           </div>
           <div data-type="down">
           <div>
           down
           </div>
           </div>
           <div data-type="left">
           <div>
           left
           </div>
           </div>
           </div>
           <div class="cubetwo-cube" data-type="cubetwo-touch">
           <div data-type="front">
           touch front
           </div>
           <div data-type="up">
           touch up
           </div>
           <div data-type="left">
           touch left
           </div>
           </div>
           </div>
           <div class="cubetwo-cube-2" tabindex="0" data-type="cubetwo">
           <div class="cubetwo-cube" data-type="cubetwo-display" data-index="2">
           <div data-type="front">
           <div>
           front
           </div>
           </div>
           <div data-type="up">
           <div>
           up
           </div>
           </div>
           <div data-type="right">
           <div>
           right
           </div>
           </div>
           <div data-type="back">
           <div>
           back
           </div>
           </div>
           <div data-type="down">
           <div>
           down
           </div>
           </div>
           <div data-type="left">
           <div>
           left
           </div>
           </div>
           </div>
           <div class="cubetwo-cube" data-type="cubetwo-touch">
           <div data-type="front">
           touch front
           </div>
           <div data-type="up">
           touch up
           </div>
           <div data-type="right">
           touch right
           </div>
           </div>
           </div>
          

          代碼運行效果截圖:

          需要這個項目css、js代碼、圖片的可以找我免費領取。如果大家不怕麻煩可以關注我后私信我“前端學習資料”幾個字 找我領取 24小時在線!


          主站蜘蛛池模板: 久久99热狠狠色精品一区| 亚洲高清偷拍一区二区三区| 国产午夜一区二区在线观看| 亚洲老妈激情一区二区三区| 国产视频一区在线播放| 亚洲国产国产综合一区首页| 国产精品视频一区二区三区不卡| 国产精品熟女视频一区二区| 国模私拍福利一区二区| 无码国产精成人午夜视频一区二区| 国产高清精品一区| 日韩高清一区二区三区不卡| 学生妹亚洲一区二区| 99精品国产一区二区三区2021| 亚洲一区二区三区日本久久九| 国模私拍一区二区三区| 久久精品国产免费一区| 亚洲A∨无码一区二区三区| 国产一区二区精品尤物| 中日韩一区二区三区| 国产午夜精品一区二区三区嫩草| 国产99精品一区二区三区免费| 亚洲一区日韩高清中文字幕亚洲| 国产伦精品一区二区三区精品| 福利一区二区视频| 国产成人精品视频一区| 丰满人妻一区二区三区视频| 亚洲国产综合无码一区二区二三区 | 国产一区二区三区免费在线观看| 日韩在线一区视频| 免费国产在线精品一区| 国产激情一区二区三区成人91| 国产精品福利区一区二区三区四区| 狠狠色婷婷久久一区二区三区| 亚洲AV本道一区二区三区四区| 99无码人妻一区二区三区免费| 欧美激情国产精品视频一区二区| 精品一区二区三区免费毛片| 亲子乱AV视频一区二区| 精品人妻中文av一区二区三区| 无码一区18禁3D|