前端新手记录自己在网络上找到的前端练习项目。趁热打铁,再练习一下鼠标拖动相关的事件,这个小游戏是一个不错的选择,玩着有趣,看着也养眼。
项目简介
三个选项代表三幅图片,可以选择任意一个开始游戏,拼图游戏跟我们平时玩的差不多,全部拼正确后会提示完成游戏并显示花费的时间。
Html部分
分成3个部分,上面是3个图片作为选项,中间是开始游戏的按钮,下面是游戏正文部分,因为较为复杂需要通过JavaScript添加。
拼图小游戏
CSS部分
body, ul, li {
margin: 0;
padding: 0;
}
body {
font: 30px/1.5 Tahoma;
background: url(img/bg.png);
text-align: center;
}
#box {
position: relative;
width:410px;
height:570px;
margin: 10px auto;
}
#box li {
float: left;
width: 82px;
height: 190px;
overflow:hidden;
}
#box li img {
width: 82px;
height: 190px;
}
#box li.hig {
width: 78px;
height: 186px;
overflow: hidden;
border: 2px dashed yellow;
}
#box li.hig img {
width: 78px;
height: 186px;
opacity: 0.5;
filter: alpha(opacity=50);
}
#mask {
position: absolute;
top: 0;
left: 0;
width: 410px;
height: 570px;
background: red;
opacity: 0;
filter: alpha(opacity=0);
}
/*设置margin和display来居中,需要父元素text-align:center */
#center {
margin: 0 auto;
display: inline-block;
}
input {
cursor: pointer;
}
#photo {
text-align: center;
margin: 10px 0;
}
#photo img {
width: 100px;
height: 100px;
border-radius: 5px;
margin: 0 5px;
opacity:0.5;
filter: alpha(opacity=50);
cursor: pointer;
}
#photo img.hover {
opacity: 1;
filter: alpha(opacity=100);
}
#photo img.selected {
border: 2px solid yellow;
width: 96px;
height: 96px;
opacity: 1;
filter: alpha(opacity=100);
}
一个值得学习的知识点时display的值inline-block。这里简单对比一下inline、block和inline-block。
-
display:block
- block元素会独占一行,多个block元素会各自新起一行。默认情况下,block元素宽度自动填满其父元素宽度。
- block元素可以设置width,height属性。块级元素即使设置了宽度,仍然是独占一行。
- block元素可以设置margin和padding属性。
-
display:inline
- inline元素不会独占一行,多个相邻的行内元素会排列在同一行里,直到一行排列不下,才会新换一行,其宽度随元素的内容而变化。
- inline元素设置width,height属性无效。
- inline元素的margin和padding属性,水平方向的padding-left, padding-right, margin-left, margin-right都产生边距效果;但竖直方向的padding-top, padding-bottom, margin-top, margin-bottom不会产生边距效果。
-
display:inline-block
- 简单来说就是将对象呈现为inline对象,但是对象的内容作为block对象呈现。之后的内联对象会被排列在同一行内。比如我们可以给一个link(a元素)inline-block属性值,使其既具有block的宽度高度特性又具有inline的同行特性。
JavaScript部分
简单的说一下拼图游戏的逻辑:图片顺序随机打乱,打乱方法是对图片随机排序,然后根据排序后的顺序放到各个位置上去。玩家可以通过拖动图片来交换两个元素的位置,当所有元素的位置都和初始一样的时候就会判定游戏结束。
var zIndex = 1;
window.onload = function() {
//获取元素
var oPhoto = this.document.getElementById("photo");
var aThumb = oPhoto.getElementsByTagName("img");
var oBox = document.getElementById("box");
var aLi = oBox.getElementsByTagName("li");
var oInput = this.document.getElementsByTagName("input")[0];
var i = 0;
var imgPath = 0;//第几个文件夹中的图片
var oDateStart = null;
var aPos = []; //位置
var aData = [];
//0-15数组
for(i = 0; i < 15; i++) {
aData.push(i + 1);
}
//缩略图
for(i = 0; i < aThumb.length; i++) {
aThumb[i].index = i;
aThumb[i].onmouseover = function() {
this.className += " hover";
};
aThumb[i].onmouseout = function() {
this.className = this.className.replace(/\shover/, "");
};
aThumb[i].onclick = function() {
for(i = 0; i < aThumb.length; i++) {
aThumb[i].className = "";
}
this.className = "selected";
imgPath = this.index;
oBox.innerHTML = "";
oInput.value = "开始游戏";
createMask();
aData.sort(function(a, b) {return a - b});//按从小到大排序
GAME(false);
}
}
//创建遮罩层
function createMask() {
var oMask = document.createElement("div");
oMask.id = "mask";
oMask.style.zIndex = zIndex;
oBox.appendChild(oMask);
}
createMask();
function GAME(ran) {
//随机排列数组
ran && aData.sort(function(a, b) {return Math.random() > 0.5 ? -1: 1});
//插入结构,用来将一块块拼图插入
var oFragment = document.createDocumentFragment();
for(i = 0; i < aData.length; i++) {
var oLi = document.createElement("li");
var oImg = document.createElement("img");
oImg.src = "img/girl" + imgPath + "/" + aData[i] + ".png";
oLi.appendChild(oImg);
oFragment.appendChild(oLi);
}
oBox.appendChild(oFragment);
oBox.style.background = "url(img/girl" + imgPath + "/bg.png) no-repeat";
for(i = 0; i < aLi.length; i++) {
aLi[i].index = i;//添加索引
aLi[i].style.top = aLi[i].offsetTop + "px";
aLi[i].style.left = aLi[i].offsetLeft + "px";
aPos.push({"left":aLi[i].offsetLeft, "top":aLi[i].offsetTop});
}
for(i = 0; i < aLi.length; i++) {
aLi[i].style.position = "absolute";
aLi[i].style.margin = "0";
drag(aLi[i]);
}
/**
* 添加拖动的函数
* @param {Element} obj 被拖动的对象
* @param {Element} handle 拖动对象时需要点击的元素
*/
function drag(obj ,handle) {
var handle = handle || obj;
handle.style.cursor = "move";
handle.onmousedown = function(event) {
var event = event || window.event;
var disX = event.clientX - this.offsetLeft;
var disY = event.clientY - this.offsetTop;
var oNear = null;
obj.style.zIndex = zIndex++;
document.onmousemove = function(event) {
var event = event || window.event;
var iL = event.clientX - disX;
var iT = event.clientY - disY;
var maxL = obj.parentNode.clientWidth - obj.offsetWidth;
var maxT = obj.parentNode.clientHeight - obj.offsetHeight;
iL < 0 && (iL = 0);
iT < 0 && (iT < 0);
iL > maxL && (iL = maxL);
iT > maxT && (iT = maxT);
obj.style.left = iL + "px";
obj.style.top = iT + "px";
for(i = 0; i < aLi.length; i++) {
aLi[i].className = "";
}
oNear = findNearest(obj);
oNear && (oNear.className = "hig");
return false;
};
document.onmouseup = function() {
document.onmousemove = null;
document.onmouseup = null;
if(oNear){ //存在最近的碰撞元素
var tIndex = obj.index;
obj.index = oNear.index;
oNear.index = tIndex;
//交换位置
startMove(obj, aPos[obj.index]);
startMove(oNear, aPos[oNear.index], function() {
if(finish()) {
var iHour = iMin = iSec = 0;
var oDateNow = new Date();
var iRemain = parseInt((oDateNow.getTime() - oDateStart.getTime()) /1000);
iHour = parseInt(iRemain / 3600);
iRemain %= 3600;
iMin = parseInt(iRemain/ 60);
iRemain %= 60;
iSec = iRemain;
alert("\u606d\u559c\u60a8\uff0c\u62fc\u56fe\u5b8c\u6210\uff01\n\n\u7528\u65f6\uff1a"
+ iHour + "\u5c0f\u65f6" + iMin + "\u5206" + iSec + "\u79d2");
createMask();
}
});
oNear.className = "";
}
else {
startMove(obj, aPos[obj.index]);
}
handle.releaseCapture && handle.releaseCapture();
};
this.setCapture && this.setCapture();
return false;
};
}
/**
* 找出最近的碰撞元素
* @param {Element} obj
*/
function findNearest(obj) {
var filterLi = [];//存放碰撞的元素
var aDistance = [];//存放和碰撞元素的距离
for(i = 0; i < aLi.length; i++) {
aLi[i] != obj && (isButt(obj, aLi[i]) && (aDistance.push(getDistance(obj, aLi[i])),
filterLi.push(aLi[i])));
}
var minNum = Number.MAX_VALUE;
var minLi = null;
for(i = 0; i < aDistance.length; i++) {
aDistance[i] < minNum && (minNum = aDistance[i], minLi = filterLi[i]);
}
return minLi;
}
}
GAME();
//开始游戏
oInput.onclick = function() {
oDateStart = new Date();
oBox.innerHTML = "";
this.value = "\u91cd\u65b0\u5f00\u59cb"
GAME(true);
};
/**
* 拼图是否完成
*/
function finish() {
var aTemp = [];
var success = true;
aTemp.length = 0;
for(i = 0; i < aLi.length; i++) {
for(var j = 0; j < aLi.length; j++) {
i == aLi[j]["index"] && aTemp.push(aLi[j].getElementsByTagName("img")[0].src.match(/(\d+)\./)[1]);
}
}
for(i = 1; i <= aTemp.length; i++) {
if(i != aTemp[i - 1]) {
success = false;
break;
}
}
return success;
}
};
/**
* 求2个元素中心之间的距离
* @param {Element} obj1
* @param {Element} obj2
*/
function getDistance(obj1, obj2) {
var a = (obj1.offsetLeft + obj2.offsetWidth / 2) - (obj2.offsetLeft + obj2.offsetWidth / 2);
var b = (obj1.offsetTop + obj1.offsetHeight / 2) - (obj2.offsetTop + obj2.offsetHeight / 2);
return Math.sqrt(a * a + b * b);
}
/**
* 碰撞检测
* @param {Element} obj1
* @param {Element} obj2
*/
function isButt(obj1, obj2) {
var l1 = obj1.offsetLeft;
var t1 = obj1.offsetTop;
var r1 = obj1.offsetLeft + obj1.offsetWidth;
var b1 = obj1.offsetTop + obj1.offsetHeight;
var l2 = obj2.offsetLeft;
var t2 = obj2.offsetTop;
var r2 = obj2.offsetLeft + obj2.offsetWidth;
var b2 = obj2.offsetTop + obj2.offsetHeight;
return !(r1 < l2 || b1 < t2 || r2 < l1 || b2 < t1);
}
/**
* 获取最终样式
* @param {Element} obj 元素
* @param {string} attr 属性
*/
function getStyle(obj, attr) {
return parseFloat(obj.currentStyle ? obj.currentStyle[attr] : getComputedStyle(obj, null)[attr]);
}
//运动框架
function startMove(obj, pos, onEnd) {
clearInterval(obj.timer);
obj.timer = setInterval(function() {
doMove(obj, pos, onEnd);
}, 30);
}
/**
* 移动元素
* @param {Element} obj 要移动的元素
* @param {Object} pos 最终位置 {"left":"", "top": ""}
* @param {function} onEnd 移动结束后要执行的函数,可选
*/
function doMove(obj, pos, onEnd) {
var iCurL = getStyle(obj, "left");
var iCurT = getStyle(obj, "top");
var iSpeedL = (pos.left - iCurL) / 5;
var iSpeedT = (pos.top - iCurT) / 5;
iSpeedL = iSpeedL > 0 ? Math.ceil(iSpeedL) : Math.floor(iSpeedL);
iSpeedT = iSpeedT > 0 ? Math.ceil(iSpeedT) : Math.floor(iSpeedT);
if(pos.left == iCurL && pos.top == iCurT) { //如果到达最终位置
clearInterval(obj.timer);
onEnd && onEnd();
}
else { //没到达则继续
obj.style.left = iCurL + iSpeedL + "px";
obj.style.top = iCurT + iSpeedT + "px";
}
}
需要学习以下的知识点。
createDocumentFragment方法
用来创建一个虚拟的节点对象,或者说,是用来创建文档碎片节点。它可以包含各种类型的节点,在创建之初是空的。
DocumentFragment节点不属于文档树,继承的parentNode属性总是null。它有一个很实用的特点,当请求把一个DocumentFragment节点插入文档树时,插入的不是DocumentFragment自身,而是它的所有子孙节点。这个特性使得DocumentFragment成了占位符,暂时存放那些一次插入文档的节点。它还有利于实现文档的剪切、复制和粘贴操作。
另外,当需要添加多个dom元素时,如果先将这些元素添加到DocumentFragment中,再统一将DocumentFragment添加到页面,会减少页面渲染dom的次数,效率会明显提升。
getComputedStyle方法
getComputedStyle()方法。这个方法接受两个参数:要取得计算样式的元素和一个伪元素字符串(例如“:after”)。如果不需要伪元素信息,第二个参数可以是null。getComputerStyle()方法返回一个CSSStyleDeclaration对象,其中包含当前元素的所有计算的样式。
IE中使用的是obj.currentStyle方法, 文中的使用方式可以保证兼容。
不使用obj.style的原因是这个方法只能获取写在style属性中的值(style="…"),而无法获取定义在