数独是很好玩的游戏,之前我用jQuery做了一个数独游戏,因为用javaScript来实现drag和drap非常麻烦,jQuery的UI提供了一套非常不错的drag和drap(以后就简称DnD算了),方便我们开发。现在HTML5支持原生的DnD了,那我们来学习下,并且将原先的数独游戏迁移到HTML5的DnD应用来。

使用HTML5的drag&drop做一个数独游戏

 

先简单的了解下HTML5的DnD事件模型,事件发生在源元素(被拖动的元素)和目标元素(被进入的元素)上,为了简单的描述,我们将源元素称为src,目标元素叫des。

 

drag:src[拖动中] dragstart:src[开始拖动]
dragenter:des[进入目标]
dragover:des[在目标移动]
dragleave:des[离开目标]
drop:des[释放拖动]
dragend:src[拖动完成]

所有的事件我们知道肯定都应该给我们一个event对象,帮助我们获得一些信息或我们来设置一些信息,以上事件都可以得到一个event,如果我们的事件函数是function(e)那

 

e.dataTransfer.effectAllowed,只能在dragstart事件设置,值为以下之一:"none", "copy", "copyLink", "copyMove", "link", "linkMove", "move", "all", and "uninitialized"

e.dataTransfer.dropEffect,返回拖来的行为,对应上面的effectAllowed,值是:"none", "copy", "link", and "move"

e.target,可以得到当前事件的dom对象,比如你可以得到e.target.innerHTML,或者设置e.target.classList.add,或者e.target.classList.remove

e.dataTransfer.setData(foramt,value),为拖动赋值,foramt的值是为了描述值的类型,一般有text/plain 和 text/uri-list

e.dataTransfer.getData(foramt),获取被拖来的元素通过setData存储的值

e.stopPropagation,阻止事件冒泡,这样可以防止子元素的拖动处理被带到父元素事件中(触发父元素事件),在IE中可以用e.cancelBubble = true

e.preventDefault,阻止默认事件发生,也可以简单的写return false,在IE中可以用e.returnValue = false

有了上面的基本概念,我们先做一个小小的模型,来测试几个技术要点:监视拖放事件,改变元素在拖放中的样式,传递值和检查值什么的

在body里面,我们声明了10个div元素,并且都标记允许拖放

 


  1. <body> 
  2.     <div style="width: 50px; height: 50px; background-color: Red;" draggable="true"> 
  3.         1 
  4.     </div> 
  5.     <div style="width: 50px; height: 50px; background-color: Yellow;" draggable="true"> 
  6.         2 
  7.     </div> 
  8.     <div style="width: 50px; height: 50px; background-color: Blue;" draggable="true"> 
  9.         3 
  10.     </div> 
  11.     <div style="width: 50px; height: 50px; background-color: Lime;" draggable="true"> 
  12.         4 
  13.     </div> 
  14.     <div style="width: 50px; height: 50px; background-color: Maroon;" draggable="true"> 
  15.         5 
  16.     </div> 
  17.     <div style="width: 50px; height: 50px; background-color: Black;" draggable="true"> 
  18.         6 
  19.     </div> 
  20.     <div style="width: 50px; height: 50px; background-color: Orange;" draggable="true"> 
  21.         7 
  22.     </div> 
  23.     <div style="width: 50px; height: 50px; background-color: Olive;" draggable="true"> 
  24.         8 
  25.     </div> 
  26.     <div style="width: 50px; height: 50px; background-color: Teal;" draggable="true"> 
  27.         9 
  28.     </div> 
  29.     <div style="width: 50px; height: 50px; background-color: Green;" draggable="true"> 
  30.         10 
  31.     </div> 
  32. </body> 

现在我们想做一个应用,只有相互有倍数关系的div之间才可用拖放。

 

首选我们做一个用于输出调式的小工具代码


  1. $.log = function(msg) { 
  2.     console.log(msg); 

 

这个我们可以方便的$.log()输出,而不要写冗长的console.log了

 

第一步,编写dragStart事件函数


  1. function handleDragStart(e) { 
  2.     this.style.opacity = "0.5"
  3.     e.dataTransfer.effectAllowed = "move"
  4.     e.dataTransfer.setData("text/plain", this.innerHTML); 
  5.     //$.log(this.innerHTML); 
  6.     //$.log(e.target.innerHTML); 
  7.     //$.log(e.srcElement.innerHTML); 
  8.     [ ].forEach.call(document.querySelectorAll("div"), 
  9.     function(item) { 
  10.         var a = parseInt(e.target.innerHTML); 
  11.         var b = parseInt(item.innerHTML); 
  12.         if (a % b != 0 && b % a != 0) { 
  13.             item.style.opacity = "0.1"
  14.  
  15.         } 
  16.     }); 

 

以上的代码有几个要点

 

1 对事件来讲this、e.target和e.srcElement都是同一对象

2 在forEach中,this是指item,所以forEach中,我们要用e.target来引用

但是一测试我们就发现虽然元素可以拖拉,但并没有事件**,那是应为我们没有为元素绑定事件,所以现在我们用addEventListener来将元素和事件绑定


  1. $( 
  2. function() { 
  3.     [ ].forEach.call(document.querySelectorAll("div"), 
  4. function(item) { 
  5.      item.addEventListener("dragstart", handleDragStart, false);  
  6. ); 
  7. ); 

 

现在我们可以看到,当任意元素拖动的时候,不和其元素有相互倍数的元素变了很淡了。

 

第二步,当我们拖放完成后,所有div恢复原先颜色,那自然是编写handleDragEnd


  1. function handleDragEnd(e) { 
  2.     if (e.preventDefault) { 
  3.         e.preventDefault(); //不要执行与事件关联的默认动作 
  4.     } 
  5.     [ ].forEach.call(document.querySelectorAll("div"), 
  6.     function(item) { 
  7.         item.style.opacity = "1"
  8.     } 
  9.     ); 

 

记得将上面的事件做绑定哦,应该类似以下代码


  1. $( 
  2. function() { 
  3.     [ ].forEach.call(document.querySelectorAll("div"), 
  4. function(item) { 
  5.      item.addEventListener("dragstart", handleDragStart, false); 
  6.      item.addEventListener("dragend", handleDragEnd, false);  
  7. ); 
  8. ); 

第三步,我们要通知那些互为倍数的元素允许我们做拖入操作

 

我们先需要为目标元素定义些事件函数


  1. function handleDragEnter(e) { 
  2.     $.log(e); 
  3.  
  4. function handleDragOver(e) { 
  5.     if (e.preventDefault) { 
  6.         e.preventDefault(); //不要执行与事件关联的默认动作 
  7.     } 
  8.     if (e.stopPropagation) { 
  9.         e.stopPropagation(); //停止事件的传播 
  10.     } 
  11.     $.log(e); 
  12.     return false; 
  13.  
  14.  
  15. function handleDragLeave(e) { 
  16.     $.log(e); 
  17.  
  18. function handleDrop(e) { 
  19.     if (e.preventDefault) { 
  20.         e.preventDefault(); //不要执行与事件关联的默认动作 
  21.     } 
  22.     if (e.stopPropagation) { 
  23.         e.stopPropagation(); //停止事件的传播 
  24.     } 
  25.     console.log(e); 
  26.     return false; 

 

 

注意我们使用了preventDefault和stopPropagation消除了浏览器默认的一些动作。

显然这些事件不是所有的元素都有的,只有互为倍数才有,所以我们要去修改handleDragStart函数了

修改后的代码大致如下


  1. function handleDragStart(e) { 
  2.     this.style.opacity = "0.5"
  3.     e.dataTransfer.effectAllowed = "move"
  4.     e.dataTransfer.setData("text/plain", this.innerHTML); 
  5.     //$.log(this.innerHTML); 
  6.     //$.log(e.target.innerHTML); 
  7.     //$.log(e.srcElement.innerHTML); 
  8.     //$.log(this.innerHTML); 
  9.     [ ].forEach.call(document.querySelectorAll("div"), 
  10.     function(item) { 
  11.         var a = parseInt(e.target.innerHTML); 
  12.         var b = parseInt(item.innerHTML); 
  13.         if (a % b != 0 && b % a != 0) { 
  14.             item.style.opacity = "0.1"
  15.         } 
  16.         else { 
  17.             item.addEventListener("dragover", handleDragOver, false); 
  18.             item.addEventListener("dragenter", handleDragEnter, false); 
  19.             item.addEventListener("dragleave", handleDragLeave, false); 
  20.             item.addEventListener("drop", handleDrop, false); 
  21.         } 
  22.     }); 

 

现在你可以发现,当我们拖动互为倍数的元素是,视觉效果明显是允许拖入,否则就不允许了。当然记得在handleDragEnd函数中要恢复哦


  1. function handleDragEnd(e) { 
  2.     if (e.preventDefault) { 
  3.         e.preventDefault(); //不要执行与事件关联的默认动作 
  4.     } 
  5.     [ ].forEach.call(document.querySelectorAll("div"), 
  6.     function(item) { 
  7.         item.style.opacity = "1"
  8.         item.removeEventListener("dragover", handleDragOver, false); 
  9.         item.removeEventListener("dragenter", handleDragEnter, false); 
  10.         item.removeEventListener("dragleave", handleDragLeave, false); 
  11.         item.removeEventListener("drop", handleDrop, false); 
  12.     } 
  13.     ); 

第四步,当我们在可以放置的元素上结束拖放后,我们希望两个元素的值累计,并出现在被放置的元素里面,我们需要修改handleDrop函数


  1. function handleDrop(e) { 
  2.     if (e.preventDefault) { 
  3.         e.preventDefault(); //不要执行与事件关联的默认动作 
  4.     } 
  5.     if (e.stopPropagation) { 
  6.         e.stopPropagation(); //停止事件的传播 
  7.     } 
  8.     this.innerHTML = parseInt(this.innerHTML)+parseInt(e.dataTransfer.getData("text/plain")) 
  9.     console.log(e); 
  10.     return false; 

好了,到现在为止,准备知识都差不多了,而且作品我们也可以得意的玩玩,你可以发现div被累计都,再次拖拉的时候,是重新计算相互的倍数的,对不?

 

最后,我感觉黑色的字不好看,我们修改下初始化的函数


  1. $( 
  2. function() { 
  3.     [ ].forEach.call(document.querySelectorAll("div"), 
  4. function(item) { 
  5.     item.addEventListener("dragstart", handleDragStart, false); 
  6.     item.addEventListener("dragend", handleDragEnd, false); 
  7.     item.style.color = "White"
  8. ); 
  9. ); 

 

 

好了,现在这个无聊的拖动作品,至少可以打发下你的时间,对不,欣赏下自己的作品吧,接下来,我们开始做正式做数独游戏了。

第一步,我们先生成一个1-9的数字对象矩形,这个矩形可以拖动。

先设计htmltag


  1. <ol id="numberBox" style="list-style-type: none; width: 90px; height: 90px;"> 
  2. </ol> 

 

 

然后准备些样式


  1. [css] view plaincopy 
  2.  
  3. #numberBox > li   
  4.  
  5. {   
  6.  
  7.     width: 30px;   
  8.  
  9.     height: 25px;   
  10.  
  11.     text-align: center;   
  12.  
  13.     font-size: 20px;   
  14.  
  15.     padding-top: 5px;   
  16.  
  17.     float: left;   
  18.  
  19.     color: White;   
  20.  
  21. }   

最后是脚本


  1. [javascript] view plaincopy 
  2.  
  3.  $(   
  4.  
  5. function() {   
  6.  
  7.     [{ number: 1, bgcolor: "#C71585" }, { number: 2, bgcolor: "#800080" }, { number: 3, bgcolor: "#B8860B" },   
  8.  
  9.     { number: 4, bgcolor: "rgb(0,0,128)" }, { number: 5, bgcolor: "rgb(30,144,255)" },    
  10.  
  11.     { number: 6, bgcolor: "rgb(255,165,0)" },   
  12.  
  13.      { number: 7, bgcolor: "hsl(0,75%,50%)" }, { number: 8, bgcolor: "hsl(30,50%,50%)" },    
  14.  
  15.      { number: 9, bgcolor: "hsl(120,75%,38%)"}].forEach(   
  16.  
  17.      function(key, index) {   
  18.  
  19.      $.log(key);   
  20.  
  21.          var li = $("<li>").html(key.number).css("backgroundColor", key.bgcolor).attr("draggable","true");   
  22.  
  23.          $.log(li);   
  24.  
  25.          li[0].addEventListener("dragstart", function() {   
  26.  
  27.          }, false);   
  28.  
  29.          $("#numberBox").append(li);   
  30.  
  31.      }   
  32.  
  33.      );   
  34.  
  35. }   
  36.  
  37. );   

好,然后你运行下页面,可以看到一个九宫格,并且每一个格子都可以拖动。

第二步,我们要类似的创建我们填数字得的区域了。

首先还是htmlTag


  1. [html] view plaincopy 
  2.  
  3. <ol id="player" style="list-style-type: none; width: 450px; height: 450px;">   
  4.  
  5. </ol>   

然后是样式设定


  1. [css] view plaincopy 
  2.  
  3. #player .default   
  4.  
  5. {   
  6.  
  7.     float: left;   
  8.  
  9.     width: 48px;   
  10.  
  11.     height: 33px;   
  12.  
  13.     border: solid 1px rgb(0,0,0);   
  14.  
  15.     font-size: 20px;   
  16.  
  17.     padding-top: 15px;   
  18.  
  19.     text-align: center;   
  20.  
  21.     background-color: #B8860B;   
  22.  
  23. }   
  24.  
  25. #player .fix   
  26.  
  27. {   
  28.  
  29.     float: left;   
  30.  
  31.     width: 48px;   
  32.  
  33.     height: 33px;   
  34.  
  35.     border: solid 1px rgb(0,0,0);   
  36.  
  37.     font-size: 20px;   
  38.  
  39.     padding-top: 15px;   
  40.  
  41.     text-align: center;   
  42.  
  43.     background-color: #FFFABC;   
  44.  
  45. }   
  46.  
  47. #player .ation   
  48.  
  49. {   
  50.  
  51.     float: left;   
  52.  
  53.     width: 48px;   
  54.  
  55.     height: 33px;   
  56.  
  57.     border: solid 1px rgb(0,0,0);   
  58.  
  59.     font-size: 20px;   
  60.  
  61.     padding-top: 15px;   
  62.  
  63.     text-align: center;   
  64.  
  65.     background-color: #FFA07A;   
  66.  
  67. }   

然后初始化这个区域。数独填字区域有的格子有值有的没有值,我用0表示没有值将来你可以填制,非0表示是固定区不可以改

现在你已经可以在页面上看到一个非常威武的“独”字了!


  1. [javascript] view plaincopy 
  2.  
  3.  $(   
  4.  
  5. function() {   
  6.  
  7.     "500000300090500400004000700051037289302080604008052137035000900609000823080023006".split("").forEach(   
  8.  
  9.     function(item, index) {   
  10.  
  11.         $.log(item);   
  12.  
  13.         var li = $("<li>")   
  14.  
  15.         if (item != "0") {   
  16.  
  17.             li.addClass("fix");   
  18.  
  19.             li[0].innerHTML = item;   
  20.  
  21.         }   
  22.  
  23.         else {   
  24.  
  25.             li[0].classList.add("default");   
  26.  
  27.             li[0].addEventListener("dragenter",   
  28.  
  29.             function(e) {   
  30.  
  31.                 $.log(e);   
  32.  
  33.             }, false);   
  34.  
  35.    
  36.  
  37.             li[0].addEventListener("dragover",   
  38.  
  39.             function(e) {   
  40.  
  41.                 if (e.preventDefault) {   
  42.  
  43.                     e.preventDefault(); //不要执行与事件关联的默认动作   
  44.  
  45.                 }   
  46.  
  47.                 if (e.stopPropagation) {   
  48.  
  49.                     e.stopPropagation(); //停止事件的传播   
  50.  
  51.                 }   
  52.  
  53.                 $.log(e);   
  54.  
  55.                 return false;   
  56.  
  57.             }, false);   
  58.  
  59.    
  60.  
  61.             li[0].addEventListener("dragleave",   
  62.  
  63.             function(e) {   
  64.  
  65.             }, false);   
  66.  
  67.    
  68.  
  69.             li[0].addEventListener("drop",   
  70.  
  71.             function(e) {   
  72.  
  73.                 if (e.preventDefault) {   
  74.  
  75.                     e.preventDefault(); //不要执行与事件关联的默认动作   
  76.  
  77.                 }   
  78.  
  79.                 if (e.stopPropagation) {   
  80.  
  81.                     e.stopPropagation(); //停止事件的传播   
  82.  
  83.                 }   
  84.  
  85.    
  86.  
  87.             }, false);   
  88.  
  89.         }   
  90.  
  91.         $("#player").append(li);   
  92.  
  93.     }   
  94.  
  95.     );   
  96.  
  97. }   
  98.  
  99. );   

第三步:我们拖动数字之后,希望可以填数字的区域有明显的颜色变化并给出提示,同时固定区域不可以拖进去,其他区域可以拖进去,并且拖动的时候要send值。

有了前面的知识,我们马上知道去哪里改事件控制了:dragstart事件


  1. [javascript] view plaincopy 
  2.  
  3.  $(   
  4.  
  5. function() {   
  6.  
  7.     [{ number: 1, bgcolor: "#C71585" }, { number: 2, bgcolor: "#800080" }, { number: 3, bgcolor: "#B8860B" },   
  8.  
  9.     { number: 4, bgcolor: "rgb(0,0,128)" }, { number: 5, bgcolor: "rgb(30,144,255)" },   
  10.  
  11.     { number: 6, bgcolor: "rgb(255,165,0)" },   
  12.  
  13.      { number: 7, bgcolor: "hsl(0,75%,50%)" }, { number: 8, bgcolor: "hsl(30,50%,50%)" },   
  14.  
  15.      { number: 9, bgcolor: "hsl(120,75%,38%)"}].forEach(   
  16.  
  17.      function(key, index) {   
  18.  
  19.          //$.log(key);   
  20.  
  21.          var li = $("<li>").html(key.number).css("backgroundColor", key.bgcolor).attr("draggable", "true");   
  22.  
  23.          //$.log(li);   
  24.  
  25.          li[0].addEventListener("dragstart", function(e) {   
  26.  
  27.              e.dataTransfer.effectAllowed = "copyMove";   
  28.  
  29.              e.dataTransfer.setData("text/plain", this.innerHTML);   
  30.  
  31.              $.log(this.innerHTML);   
  32.  
  33.              [ ].forEach.call(document.querySelectorAll("#player .default"),   
  34.  
  35.          function(item) {   
  36.  
  37.              //$.log(item);   
  38.  
  39.              item.classList.remove("default");   
  40.  
  41.              item.classList.add("ation");   
  42.  
  43.          });   
  44.  
  45.          }, false);   
  46.  
  47.    
  48.  
  49.          li[0].addEventListener("dragend", function() {   
  50.  
  51.              [ ].forEach.call(document.querySelectorAll("#player .ation"),   
  52.  
  53.          function(item) {   
  54.  
  55.              item.classList.remove("ation");   
  56.  
  57.              item.classList.add("default");   
  58.  
  59.          });   
  60.  
  61.          }, false);   
  62.  
  63.    
  64.  
  65.          $("#numberBox").append(li);   
  66.  
  67.      }   
  68.  
  69.      );   
  70.  
  71. }   
  72.  
  73. );   

 

 

现在你可以测试下了,当你拖动数字的时候,有明显的颜色改变,并且不同的区域你的鼠标样式也不同哦。 

第四步,我们接受值,并且判断这个值是否存在行列冲突,如果冲突就提示,否则改写


  1. [javascript] view plaincopy 
  2.  
  3.  $(   
  4.  
  5. function() {   
  6.  
  7.     "500000300090500400004000700051037289302080604008052137035000900609000823080023006".split("").forEach(   
  8.  
  9.     function(item, index) {   
  10.  
  11.         $.log(item);   
  12.  
  13.         var li = $("<li>")   
  14.  
  15.         if (item != "0") {   
  16.  
  17.             li.addClass("fix");   
  18.  
  19.             li[0].innerHTML = item;   
  20.  
  21.         }   
  22.  
  23.         else {   
  24.  
  25.             li[0].classList.add("default");   
  26.  
  27.             li[0].addEventListener("dragenter",   
  28.  
  29.             function(e) {   
  30.  
  31.                 $.log(e);   
  32.  
  33.             }, false);   
  34.  
  35.    
  36.  
  37.             li[0].addEventListener("dragover",   
  38.  
  39.             function(e) {   
  40.  
  41.                 if (e.preventDefault) {   
  42.  
  43.                     e.preventDefault(); //不要执行与事件关联的默认动作   
  44.  
  45.                 }   
  46.  
  47.                 if (e.stopPropagation) {   
  48.  
  49.                     e.stopPropagation(); //停止事件的传播   
  50.  
  51.                 }   
  52.  
  53.                 $.log(e);   
  54.  
  55.                 return false;   
  56.  
  57.             }, false);   
  58.  
  59.    
  60.  
  61.             li[0].addEventListener("dragleave",   
  62.  
  63.             function(e) {   
  64.  
  65.             }, false);   
  66.  
  67.    
  68.  
  69.             li[0].addEventListener("drop",   
  70.  
  71.             function(e) {   
  72.  
  73.                 if (e.preventDefault) {   
  74.  
  75.                     e.preventDefault(); //不要执行与事件关联的默认动作   
  76.  
  77.                 }   
  78.  
  79.                 if (e.stopPropagation) {   
  80.  
  81.                     e.stopPropagation(); //停止事件的传播   
  82.  
  83.                 }   
  84.  
  85.    
  86.  
  87.                 var sendData = e.dataTransfer.getData("text/plain");   
  88.  
  89.                 //获得#player>li矩阵数组   
  90.  
  91.                 var matrix = Array.prototype.slice.call(document.querySelectorAll("#player>li"));    
  92.  
  93.                 var currIndex = matrix.indexOf(this); //获得当前元素的位置   
  94.  
  95.                 var rowIndex = currIndex - currIndex % 9; //行开始的位置   
  96.  
  97.                 var colIndex = currIndex % 9//列开始的位置   
  98.  
  99.                 for (var i = rowIndex; i < rowIndex + 9; i++) {   
  100.  
  101.                     if (i != currIndex && matrix[i].innerHTML == sendData) {   
  102.  
  103.                         alert("对不起行上有数据重复,请小心哦!亲");   
  104.  
  105.                         return;   
  106.  
  107.                     }   
  108.  
  109.                 }   
  110.  
  111.                 for (var i = colIndex; i < 81ii = i + 9) {   
  112.  
  113.                     if (i != currIndex && matrix[i].innerHTML == sendData) {   
  114.  
  115.                         alert("对不起列上有数据重复,请小心哦!亲");   
  116.  
  117.                         return;   
  118.  
  119.                     }   
  120.  
  121.                 }   
  122.  
  123.                 this.innerHTML = sendData;   
  124.  
  125.    
  126.  
  127.             }, false);   
  128.  
  129.         }   
  130.  
  131.         $("#player").append(li);   
  132.  
  133.     }   
  134.  
  135.     );   
  136.  
  137. }   
  138.  
  139. );   
  140.  
  141.   

现在你可以开始玩啦,虽然颜色不怎么好看,但至少可以玩,对不,我们第一个html5的实用游戏。我后期的blog打算再做些类似新浪微博的“你画我猜”还有“接龙游戏”。

附件是完整的代码,你要懒的话,就下载吧