前端新手项目练习之拼图游戏

2018-12-02  本文已影响13人  简单一点点

前端新手记录自己在网络上找到的前端练习项目。趁热打铁,再练习一下鼠标拖动相关的事件,这个小游戏是一个不错的选择,玩着有趣,看着也养眼。

项目简介

三个选项代表三幅图片,可以选择任意一个开始游戏,拼图游戏跟我们平时玩的差不多,全部拼正确后会提示完成游戏并显示花费的时间。


拼图游戏.gif

Html部分

分成3个部分,上面是3个图片作为选项,中间是开始游戏的按钮,下面是游戏正文部分,因为较为复杂需要通过JavaScript添加。

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <title>拼图小游戏</title>
        <link rel="stylesheet" href="jigsawPuzzle.css" type="text/css" />
    </head>
    <body>
        <div id="photo">
            <img src="img/girl0/girl.jpg" class="selected" />
            <img src="img/girl1/girl.jpg" />
            <img src="img/girl2/girl.jpg" />
        </div>
        <div id="center">
            <input type="button" value="开始游戏" />
        </div>
        <ul id="box"></ul>
    </body>
    <script type="text/javascript" src="jigsawPuzzle.js"></script>
</html>

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。

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="…"),而无法获取定义在<style type="text/css">里面的属性。

offsetTop与style.top的差别

offsetTop 可以获得 HTML 元素间隔上方或外层元素的地位,style.top 也是可以的,二者的差别是:

  1. offsetTop 返回的是数字,而 style.top 返回的是字符串,除了数字外还带有单位:px。
  2. offsetTop 只读,而 style.top 可读写。
  3. 若是没有给 HTML 元素指定过 top 样式,则 style.top 返回的是空字符串。

总结

感觉一个很不错的小项目,可以根据自己的喜好更换使用的图片。项目源码请查看https://github.com/yunkai123/WebProjectStudy

上一篇 下一篇

猜你喜欢

热点阅读