?

日志更新

如何用 JavaScript 开发迷宫游戏

3月6日在杭州 Google GTUG 活动上的分享《Maze Game》,本想讲 《Android 手机开发及HTML5在手机开发中的应用》,但由于 Google 邀约得比较晚,来不及准备,就在原先准备在团队内分享的未完成 PPT ——《Maze Game》的基础上,做了补充和完善。

游戏部分思路来源于同事 季札 开发的迷宫游戏:www.sansi.org/static/maze/maze.html

Script 元素 type 属性的妙用

我们经常使用动态创建 JavaScript 的方式来实现 JavaScript 文件的无阻塞(Non-blocking)、并行下载(Parallel )。其实还可以添加自定义的 type 属性(比如 text/cache),达到预加载但不执行的效果:

var doc = document,
    el = doc.createElement("script"),
    head = doc.getElementsByTagName('head')[0];

    el.src = "//yui.yahooapis.com/2.8.2r1/build/yahoo-dom-event/yahoo-dom-event.js";
    el.type = "text/cache";
    head.insertBefore(el, head.firstChild);

注:Firefox 3.6.x 不请求 JavaScript 文件。

扩展阅读:

前端代码 Review 的一些思考

前端代码 Review 的一些思考

查看大图://www.r5qgb.cn/wp-content/uploads/2011/codereview.png

欢迎大家针对其中的问题,一起思考与交流!

扩展阅读:

模拟实现 Range 的 insertNode() 方法

最近对 RangeSelection 比较感兴趣。

基本非 IE 的浏览器都支持 DOM Level2 中的 Range,而 IE 中仅有自己的简单处理方法(Text Rang)。

扩展阅读:

而 IE 下的 Text Rang 主要用来处理文本,并非 DOM 节点,那如何在 IE 下模拟 DOM Level2 中的 Range 呢?

根据规范的 API,我们需要模拟下述属性和方法:

function zRange() {
    // Inital states
    this.startContainer = document;
    this.startOffset = 0;
    this.endContainer = document;
    this.endOffset = 0;
    this.commonAncestorContainer = document; 
    this.collapsed = true; 

    // Range constants
    this.START_TO_START = 0;
    this.START_TO_END = 1;
    this.END_TO_END = 2;
    this.END_TO_START = 3;
}

zRange.prototype = {
    // Public methods
    setStart : function(node, offset){},
    setEnd : function(node, offset){},    
    setStartBefore : function(node){},
    setStartAfter : function(node){},
    setEndBefore : function(node){},
    setEndAfter : function(node){},
    collapse : function(toStart) {},
    selectNode : function(node) {},
    selectNodeContents : function(node){},
    deleteContents : function() {},
    extractContents : function(){},
    cloneContents : function() {},
    surroundContents : function () {},
    insertNode : function(node) {},
    cloneRange : function() {},
    detach : function() {},    
    compareBoundaryPoints : function (how, sourceRange) {},
    constructor : zRange
}

我们还可以看一组使用 Range 方法和属性的统计数据,对于 2/8 原则的实践或许有帮助:

/**
 * Resource reference : //kb.operachina.com/node/147
 *
 * collapse             51,435 
 * setStartBefore       43,138 
 * setStartAfter        40,270 
 * selectNodeContents   37,027 
 * collapsed            12,862 
 * selectNode            4,636 
 * deleteContents        3,935 
 * setStart              3,171
 * startOffset           3,150
 * setEnd                3,086
 * detach                2,732 
 * startContainer        2,659
 * endOffset             2,647 
 * insertNode            2,321 
 * cloneContents         2,261 
 * endContainer          2,236 
 * cloneRange            1,993 
 * setEndAfter           1,911 
 *
 */

下面我们简单讨论一下 Range 的 insertNode() 方法的模拟实现:

The insertNode() method inserts the specified node into the Range’s context tree. The node is inserted at the start boundary-point of the Range, without modifying it.

If the start boundary point of the Range is in a Text node, the insertNode operation splits the Text node at the boundary point. If the node to be inserted is also a Text node, the resulting adjacent Text nodes are not normalized automatically; this operation is left to the application.

The Node passed into this method can be a DocumentFragment. In that case, the contents of the DocumentFragment are inserted at the start boundary-point of the Range, but the DocumentFragment itself is not. Note that if the Node represents the root of a sub-tree, the entire sub-tree is inserted.

从上面的引用得知 insertNode() 方法用来在选区的开头插入节点,固我们先获取 Range 对象的 startContainer(Range 是从那个节点中开始的,即选区中第一个节点的父节点) 和 startOffset(在 startContainer 中 Range 开始的偏移位置) 属性。

var sc = this.startContainer, 
    so = this.startOffset;

注: 如果 startContainer 是文本节点、注释节点或者是 CData 节点,startOffset 是指 Range 开始前的字符数,否则,偏移是 Range 中的第一个节点在其父节点中的索引。

接下来,我们需将情况分为下面两种:

第一种情形:startContainer 节点为文本节点、注释节点或者是 CData 节点?!皊tartOffset 是指 Range 开始前的字符数”。

  • 如果 startOffset 等于 0,则表示 Range 是从 startContainer 起始位置开始,应将 node 插入到 startContainer 节点之前 sc.parentNode.insertBefore(node, sc);
  • 如果 startOffset 大于等于 startContainer 本身的节点长度,则表示 Range 是从 startContainer 末尾位置开始,应将 node 插入到 startContainer 节点之后,即如果存在下一节点,则插入到下一之前 sc.parentNode.insertBefore(node, sc.nextSibling);,如果不存在下一节点,则加入到 startContainer父节点最后 sc.parentNode.appendChild(node);
  • 如果 startOffset 在 startContainer 本身的节点长度之内,我们通过oSplitNode = object.splitText( [iIndex])将节点在startOffset 一分为二 nn = sc.splitText(so);,分为两个节点,则应将 node 插入到新生成的第二个节点之前 sc.parentNode.insertBefore(node, nn);。

    The text node that invokes the splitText method has a nodeValue equal to the substring of the value, from zero to iIndex. The new text node has a nodeValue of the substring of the original value, from the specified index to the value length. Text node integrity is not preserved when the document is saved or persisted.

if (so===0) {
    // At the start of text
    sc.parentNode.insertBefore(node, sc);
} else if (so >= sc.nodeValue.length) {
    // At the end of text
    if (ns) {
        sc.parentNode.insertBefore(node, sc.nextSibling);
    } else {
        sc.parentNode.appendChild(node);
    }
} else {
    // Middle, need to split
    // //msdn.microsoft.com/zh-cn/library/ms536764.aspx
    nn = sc.splitText(so);
    sc.parentNode.insertBefore(node, nn);
}

第二种情形:startContainer 节点为非 第一种情形中的节点?!捌剖?Range 中的第一个节点在其父节点中的索引”?;袢?startContainer 中子节点偏移量为 startOffset 的节点 cn = sc.childNodes[so];,如果存在,则按照 insertNode 方法的定义,应将 node 插入到该节点之前 sc.insertBefore(node, cn);,如果不存在,即 startOffset 大于等于 sc.childNodes.length,则应将 node 插入到 startContainer 的子节点最后sc.appendChild(node);。

if (sc.childNodes.length > 0) {
    cn = sc.childNodes[so];
}

if (cn) {
    sc.insertBefore(node, cn);
} else {
    sc.appendChild(node);
}

详细代码实现如下:

zRange.prototype.insertNode = function(node) {
    var sc = this.startContainer, 
        so = this.startOffset, 
        p = sc.parentNode,
        ns = sc.nextSibling,
        nn, cn;

    // 如果节点是 TEXT_NODE 或者 CDATA_SECTION_NODE
    if ((sc.nodeType === 3 || sc.nodeType === 4 || sc.nodeType === 8 ) && sc.nodeValue) {
        if (so === 0) {
            // At the start of text
            p.insertBefore(node, sc);
        } else if (so >= sc.nodeValue.length) {
            // At the end of text
            if (ns) {
                p.insertBefore(node, ns);
            } else {
                p.appendChild(node);
            }
        } else {
            // Middle, need to split
            // //msdn.microsoft.com/zh-cn/library/ms536764.aspx
            nn = sc.splitText(so);
            p.insertBefore(node, nn);
        }
    } else {
        if (sc.childNodes.length > 0) {
            cn = sc.childNodes[so];
        }

        if (cn) {
            sc.insertBefore(node, cn);
        } else {
            sc.appendChild(node);
        }
    }
}

剩下的方法,大家可以尝试着去模拟一把,其实并不复杂,也许会其乐无穷,呵呵

?
  • 凰家街采:你期待的长江时代 ——凤凰网房产武汉 2018-07-26
  • 反思顾雏军案,营造可预期的法治环境 2018-07-26
  • 广图:神“摄”手初养成计划:摄影师爸爸成长篇 2018-07-26
  • 女性之声——全国妇联 2018-07-25
  • 习近平与人民日报那些事 2018-07-25
  • 新IP--河北频道--人民网 2018-07-25
  • 新时代领导干部应急管理能力提升的实践与思考 2018-07-25
  • 证监会答复投资者信息检索库 2018-07-24
  • 款款粽子情,深深敬老人 2018-07-24
  • 李鹏国的行为是对善良的一种打击,暴露了资本主义的道貌岸然的虚伪本质,女白领的同情心是值得赞扬的,只有不够,没有毛病 2018-07-24
  • 邓紫棋首任明星制作人 吴亦凡身兼二职 2018-07-23
  • 地铁上索座不成 蛮横老汉掌掴大妈 2018-07-23
  • 发现食品安全问题拨打12331投诉 2018-07-23
  • 莆田市:建立“网上大调解中心”经验做法 2018-07-22
  • 火锅店推120元月卡11天被吃垮:8点就排队,一卡全家用 2018-07-22
  • 128| 899| 76| 845| 402| 885| 91| 282| 278| 765|