jQuery进阶——分析拼图游戏源代码

在网上看到一款拼图游戏游戏,发现它是js写成的,于是想看一下它的实现方法,经过代码去余冗和修改,我们来分析这段代码的精妙。

 

1. HTML部分

复制网页的源代码,去掉与拼图功能无关,并根据CSS文件去掉具体内容标签,得到一个简单的HTML页面

<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
        <title>jQuery拼图</title>
        <script type="text/javascript" src="jquery.min.js"></script>
        <script type="text/javascript" src="func.js"></script>
        <link href="style.css" type="text/css" rel="stylesheet">
    </head>
    <body>
        <div id="play_area"></div>
        <div class="play_menu">
            <a id="play_btn_start" class="play_btn" href="javascript:void(0);" unselectable="on">洗牌</a>
            <a id="play_btn_level" class="play_btn" href="javascript:void(0);" unselectable="on">HARD</a>
            <div class="level_menu" id="play_menu_level">
                <ul>
                    <li>
                        <a href="javascript:void(0);" level="0">EASY</a>
                    </li>
                    <li>
                        <a href="javascript:void(0);" level="1">MIDDLE</a>
                    </li>
                    <li>
                        <a href="javascript:void(0);" level="2">HARD</a>
                    </li>
                </ul>
            </div>
        </div>
    </body>
</html>

CSS定义

html {
    height: 100%;
}
body {
    font-family: "Helvetica Neue", "Hiragino Sans GB", "Segoe UI", "Microsoft Yahei", Tahoma, Arial, STHeiti, sans-serif;
    font-size: 12px;
    background: #fff;
    color: #333;
}
a {
    outline: none;
    -moz-outline: none;
    text-decoration: none;
}
#play_area {
    position: relative;
    width: 300px;
    height: 300px;
    background: #fefefe;
    box-shadow: 0px 0px 8px #09F;
    cursor: default;
}
#play_area .play_cell {
    width: 48px;
    height: 48px;
    border: 1px solid #fff;
    border-radius: 4px;
    position: absolute;
    background-position: 5px 5px;
    cursor: default;
    z-index: 80;
    box-shadow: 0px 0px 8px #fff;
    transition-property: background-position;
    transition-duration: 300ms;
    transition-timing-function: ease-in-out;
}
#play_area .play_cell.hover {
    filter: alpha(opacity=80);
    opacity: .8;
    box-shadow: 0px 0px 8px #000;
    z-index: 90;
}
.play_menu {
    margin-left: 130px;
    font-size: 14px;
    padding-top: 20px;
}
.play_menu a.play_btn {
    display: block;
    margin-bottom: 20px;
    width: 80px;
    height: 28px;
    line-height: 28px;
    text-align: center;
    text-decoration: none;
    color: #333;
    background: #fefefe;
    border: 1px solid #eee;
    border-radius: 2px;
    box-shadow: 1px 1px 2px #eee;
    border-color: #ddd #d2d2d2 #d2d2d2 #ddd;
    outline: none;
    -moz-outline: none;
}
.play_menu a.play_btn:hover {
    background-color: #fcfcfc;
    border-color: #ccc;
    box-shadow: inset 0 -2px 6px #eee;
}
.play_menu a#play_btn_level {
    position: relative;
    margin-bottom: 30px;
}
.level_text {
    margin-left: -10px;
}
.level_menu {
    position: absolute;
    margin: -30px 0 0px 1px;
    display: none;
}
.level_menu ul {
    list-style: none;
}
.level_menu li {
    float: left;
}
.level_menu li a {
    display: block;
    padding: 3px 10px;
    border: 1px solid #e8e8e8;
    margin-left: -1px;
    color: #09c;
}
.level_menu li a:hover {
    background: #09c;
    color: #fefefe;
}
View Code

 

 

2. 脚本func.js事件代码分析

先有一个构造函数,定义游戏中需要的窗口大小信息,和图片CSS信息保存的数据结构,最后调用init初始化拼图格,调用menu定义‘洗牌’和难度选择的事件。

var puzzleGame = function(options) {

    this.img = options;

    this.areaWidth = parseInt($("#play_area").css("width"));
    this.areaHeight = parseInt($("#play_area").css("height"));
    this.offX = $("#play_area").offset().left;
    this.offY = $("#play_area").offset().top;

    this.levelArr = [3, 4, 6];
    this.level = 2;

    this.cellRow = this.levelArr[this.level];
    this.cellCol = this.levelArr[this.level];

    this.cellWidth = this.areaWidth / this.cellCol;
    this.cellHeight = this.areaHeight / this.cellRow;
    
    this.imgArr = []; // 原图序列
    this.ranArr = []; // 随机序列
    this.cellArr = []; // CSS属性的引用
    
    this.thisLeft = 0;
    this.thisTop = 0;
    this.nextIndex
    this.thisIndex

    this.cb_cellDown = $.Callbacks();

    this.init();
    this.menu();
};

接下来为类定义方法,init是按照对格子的划分方法,创建div赋予backgroundPosition的属性,并把每个div的引用存在数组cellArr中。

imgArr为一个0到15的数组,作为正确序列。它在后面被复制随机化后来与正确序列作比较判断是否拼图成功。

当然还有另一种,clip 是 CSS2 中的裁剪属性,用于裁剪绝对定位的元素。

$t = $('.clipped-box');
var amount = 5;
        
var width = $t.width() / amount;
var height = $t.height() / amount;
        
var totalSquares = Math.pow(amount, 2);
        
var html = $t.html();
        
var h = 0;
for(var left = 0; left <= (amount*width); left += width) { 
        $('<div class="clipped" style="clip: rect('+h+'px, '+(left+width)+'px, '+(h+height)+'px, '+left+'px)">'+html+'</div>').appendTo($t);
        if(left === (amount*width)-width)
            h += height, left = -width;
        if(h === (amount*height))
            break;
}

另外就是通过定义backgroundPosition的左上角坐标定位

puzzleGame.prototype = {
    init : function() {
        var _cell;

        for (var i = 0; i < this.cellRow; i++) {
            for (var j = 0; j < this.cellCol; j++) {
                _cell = document.createElement("div");
                _cell.className = "play_cell";
                $(_cell).css({
                    "width" : this.cellWidth - 2,
                    "height" : this.cellHeight - 2,
                    "left" : j * this.cellWidth,
                    "top" : i * this.cellHeight,
                    "background" : "url(" + this.img + ")",
                    "backgroundPosition" : (-j) * this.cellWidth + "px " + (-i) * this.cellHeight + "px"
                });
                this.imgArr.push(i * this.cellCol + j);
                this.cellArr.push($(_cell));
                $("#play_area").append(_cell);
            }
        }
    },
    menu : function() {
        var self = this;

        $("#play_btn_start").click(function() {
            self.play();
        });
        $("#play_btn_level").click(function() {
            $("#play_menu_level").toggle();
        });
        $("#play_menu_level").find("a").click(function() {
            $("#play_menu_level").hide();
            $("#play_btn_level").html($(this).html());

            if (parseInt($(this).attr("level")) !== self.level) {
                self.level = $(this).attr("level");
                self.cellRow = self.levelArr[self.level];
                self.cellCol = self.levelArr[self.level];
                self.cellWidth = self.areaWidth / self.cellCol;
                self.cellHeight = self.areaHeight / self.cellRow;
                self.init();
            }
        })
    }
}

下一段是开始洗牌的事件,它的随机化策略是,用复制一个原数列每次随机选择一个,记录下每次选择并根据cellArr的引用修改对应CSS

play : function() {
        this.randomImg();
        // 定义鼠标事件
        this.bindCell();
    },
// 创建随机序列ranArr并改变cellArr的CSS属性
randomImg : function() {
        // 复制数组
        var arr = this.imgArr.slice();
        for (var i = 0, ilen = arr.length; i < ilen; i++) {
            // 随机删除一个
            var tmp = Math.floor(Math.random() * arr.length);
            this.ranArr.push(arr[tmp]);

            this.cellArr[i].css({
                "backgroundPosition" : (-arr[tmp] % this.cellCol) * this.cellWidth + "px " + (-Math.floor(arr[tmp] / this.cellCol)) * this.cellHeight + "px"
            })
            // 删除
            arr.splice(tmp, 1);
        }
    },

 

3.脚本func.js回调

从play被调用生成随机后的拼图,调用bindCell。

它为回调函数列表添加了cellDown方法,在遇到mousedown事件时,传参窗口/块/类调用cellDown

更多$.callbacks回调函数列表用法:http://www.cnblogs.com/snandy/archive/2012/11/15/2770237.html .

在新版jQuery中,更短的 on(“click”) 用来取代类似 click() 这样的函数。在之前的版本中 on() 就是 bind()。自从jQuery 1.7版本后,on() 附加事件处理程序的首选方法。

 

bindCell : function() {
        var self = this;
        this.cb_cellDown.add(self.cellDown);
        for (var i = 0, len = this.cellArr.length; i < len; i++) {
            this.cellArr[i].on({
                "mouseover" : function() {
                    $(this).addClass("hover");
                },
                "mouseout" : function() {
                    $(this).removeClass("hover");
                },
                "mousedown" : function(e) {
                    self.cb_cellDown.fire(e, $(this), self);
                    return false;
                }
            });
        }
    }

在mouseup时撤销mouseup和mousemove事件监听,清空回调函数列表,每个块不再执行mousedown时cellDown事件

为了等待在移动的块移到对应位置后再启用cellDown监听。

// 放下
    cellDown : function(e, _cell, self) {
        var _x = e.pageX - _cell.offset().left, _y = e.pageY - _cell.offset().top;

        self.thisLeft = _cell.css("left");
        self.thisTop = _cell.css("top");
        self.thisIndex = Math.floor(parseInt(self.thisTop) / self.cellHeight) * self.cellCol;
        self.thisIndex += Math.floor(parseInt(self.thisLeft) / self.cellWidth);

        _cell.css("zIndex", 99);
        $(document).on({
            "mousemove" : function(e) {
                _cell.css({
                    "left" : e.pageX - self.offX - _x,
                    "top" : e.pageY - self.offY - _y
                })
            },
            "mouseup" : function(e) {
                $(document).off("mouseup");
                $(document).off("mousemove");
                self.cb_cellDown.empty();
                if (e.pageX - self.offX < 0 || e.pageX - self.offX > self.areaWidth || e.pageY - self.offY < 0 || e.pageY - self.offY > self.areaHeight) {
                    self.returnCell();
                    return;
                }

                var _tx, _ty, _ti, _tj;
                _tx = e.pageX - self.offX;
                _ty = e.pageY - self.offY;

                _ti = Math.floor(_ty / self.cellHeight);
                _tj = Math.floor(_tx / self.cellWidth);

                self.nextIndex = _ti * self.cellCol + _tj;
                if (self.nextIndex == self.thisIndex) {
                    self.returnCell();
                } else {
                    self.changeCell();
                }
            }
        })
    },

定义交换和返回的动画,修改ranArr的引用,在动画结束后再向回调函数列表添加mousedown的cellDown事件,应对下一次拼图块的移动。

// 交换
    changeCell : function() {
        var self = this, _tc = this.cellArr[this.thisIndex], _tl = this.thisLeft, _tt = this.thisTop, _nc = this.cellArr[this.nextIndex], _nl = (this.nextIndex % this.cellCol) * this.cellWidth, _nt = Math.floor(this.nextIndex / this.cellCol) * this.cellHeight;

        _nc.css("zIndex", 98);

        this.cellArr[this.nextIndex] = _tc;
        this.cellArr[this.thisIndex] = _nc;

        this.ranArr[this.nextIndex] = this.ranArr[this.nextIndex] + this.ranArr[this.thisIndex];
        this.ranArr[this.thisIndex] = this.ranArr[this.nextIndex] - this.ranArr[this.thisIndex];
        this.ranArr[this.nextIndex] = this.ranArr[this.nextIndex] - this.ranArr[this.thisIndex];

        _tc.animate({
            "left" : _nl,
            "top" : _nt
        }, 500, function() {
            _tc.removeClass("hover");
            _tc.css("zIndex", "");
        })

        _nc.animate({
            "left" : _tl,
            "top" : _tt
        }, 500, function() {
            _nc.removeClass("hover");
            _nc.css("zIndex", "");
            if (self.ranArr.join() == self.imgArr.join()) {
                alert("ok");
            }

            if (!self.cb_cellDown.has(self.cellDown))
                self.cb_cellDown.add(self.cellDown);
        })
    },
    // 返回
    returnCell : function() {
        var self = this;
        this.cellArr[this.thisIndex].animate({
            "left" : self.thisLeft,
            "top" : self.thisTop
        }, 500, function() {
            $(this).removeClass("hover");
            $(this).css("zIndex", "");
            if (!self.cb_cellDown.has(self.cellDown))
                self.cb_cellDown.add(self.cellDown);
        });
    }

 

posted @ 2014-02-17 22:21  匡时@下一站.info  阅读(559)  评论(0编辑  收藏  举报