日志更新

如何跨浏览器使用连续字符的换行

由于连续的字符(字母、符号、数字)在默认情况下是不换行的,可能会破坏整个界面布局。

那如何解决这个问题呢?

在 IE 和 Safari 1.3+ 下相对比较容易解决,使用 CSS 属性 word-wrap: break-word;。有朋友会问为什么不是使用 word-break:break-all; ,因为有一个特殊的情况,该属性无法解决换行——连续的英文标点符号。

注:word-break 主要使用在 CJK 文本,即:中文(Chinese)、日文(Japanese)、韩文(Korean)。

而 Firefox 和 Opera 浏览器,无法识别 word-wrap: break-word; 和 word-break:break-all; 属性。可以通过脚本给连续字符的每个字符之间插入 \ u8203 的字符(该字符在非 IE 浏览下不占据空间),使连续变为了不连续,达到了换行的效果。

breakWord = function(dEl){
    var dWalker = document.createTreeWalker(dEl, NodeFilter.SHOW_TEXT, null, false);
    var node,s,c = String.fromCharCode('8203');
    while (dWalker.nextNode()){
        node = dWalker.currentNode;
        s = trim( node.nodeValue ) .split('').join(c);
        node.nodeValue = s;
    }
    return true;
}

详细的脚本,可参看 Hedger Wang 整理的脚本《Cross Browser Word Breaker》

对于 Firefox ,你还可以通过 XBL bindings 来实现此效果:《Emulating CSS word-wrap for Mozilla/Firefox》

解决 IE6 内存泄露的另类方法

Hedger Wang 在国内 blog 上得到的方法:使用 try … finally 结构来使对象最终为 null ,以阻止内存泄露。

其中举了个例子:

function createButton() {
    var obj = document.createElement("button");
    obj.innerHTML = "click me";
    obj.onclick = function() {
        //handle onclick
    }

    obj.onmouseover = function() {
        //handle onmouseover
    }
    return obj;//return a object which has memory leak problem in IE6
}

var dButton = document.getElementsById("d1").appendChild(createButton());
//skipped....

对于 IE6 中,引起内存泄露的原因,可看《Understanding and Solving Internet Explorer Leak Patterns》一文。

上面的例子,应该属于上文中的 “Closures”原因。

Circular Reference with Closures

再看下用 try … finally 的解决方法:

/**
     * Use the try ... finally statement to resolve the memory leak issue
*/
 function createButton() {
    var obj = document.createElement("button");
    obj.innerHTML = "click me";
    obj.onclick = function() {
        //handle onclick
    }
    obj.onmouseover = function() {
        //handle onmouseover
    }

    //this helps to fix the memory leak issue
    try {
        return obj;
    } finally {
        obj = null;
    }
}

var dButton = document.getElementsById("d1").appendChild(createButton());
//skipped....

可能大家有疑问: finally 是如何解析的呢?

答案是:先执行 try 语句再执行 finally 语句。

例如:

function foo() {
    var x = 0;
    try {
        return print("call return " + (++x));
    } finally {
        print("call finally " + (++x));
    }
}

print('before');
print(foo());
print('after');

返回的结果为:
print » before
print » call return 1
print » call finally 2
print » true
print » after

更多详细的演示:《Finally, the alternative fix for IE6’s memory leak is available》

相关的一些讨论:《Is “finally” the answer to all IE6 memory leak issues?》

两个不太常用的 CSS Hack

1、仅 Safari 和 Opera 识别的 Hack

@media all and (min-width: 0px) {
/* Safari and Opera  rules here */
}

或者

@media screen and (-webkit-min-device-pixel-ratio:0) {
/* Safari and Opera rules here */
}

注:这里所指代的 Safari 和 Opera 一般为最新版本。

2、仅 Firefox 3 和 IE7 识别的 Hack

selector, x:-moz-any-link, x:default {
/* Firefox 3  and IE7 rules here */
}

注:由于 Firefox 2 和 Firefox 3 共存,且两个版本对 CSS 的解析有部分不一致,或许用得着这个 Hack 。

如何使用 JavaScript 创建可维护的幻灯片效果

下面是幻灯片效果的 样例

  • K
  • F
  • C

显然,效果很实用。对于这个效果,我们并不解释如何去使用效果库,而是讲解如何创建类似的效果,并保持他的可用性,分离式(unobtrusive),可维护性(让未来的维护者,在不需要修改你的脚本的情况下,修改图片,外观或文本标签)。

无 JavaScript 状态下,用户将看到下面的效果:

  • K
  • F
  • C

添加和移除图片、改变图片的顺序以及添加标题,这些在 HTML 中都很容易做到。并且最后的解决并不意味着维护者需要懂任何 JavaScript 或者在源码中搜索在哪里修改 CSS 的 class, id 或者文本标签。

你有没有准备好花费一些时间去一步一步的尝试创建这个效果?

第一步:分析问题(Analizing the problem)

创建一个好的脚本,第一步应该是去分析哪些是你要完成的:我们想要创建一个照片的幻灯片效果,并且我们想要保持维护的方便。

如何创建一个幻灯片效果

在一个网站上拥有幻灯片有几种方法:

  • 在文档中包含所有的图片。

    当他运行在无 JavaScript 状态,这是一个安全的选择。而且,当页面被载完,所有的图片也会将被载完。然而,这个方式只适用于少量的图片。

  • 在文档中包含第一张图片,并且有一个创建幻灯片功能的服务器端脚本。

    这也是相当安全的,但是对于终端用户来说,这是非常令人厌烦的——因为我不想加载整个页面,仅想得到下一张照片。但对页面展示和广告点击比较有效,这也是为什么大量的新闻站点使用这个方法。

  • 在文档中包含第一张图片,并按需加载其他图片。

    这个方法令你厌烦的是,必须依赖于 JavaScript ,并且要有一个维护照片列表的 JavaScript 数组。你还需提供一个加载指示器,用来显示用户一些正在发生的事情。

在我们的案例中,我们采取下面的图片列表,用向前和向后的按钮把他变成一个幻灯片效果,并且一个指示器告诉我们,照片总数中的哪张照片是当前显示的。

<ul id="slideshow">
    <li><img src="img/flat1.jpg" alt="Hallway" /></li>
    <li><img src="img/flat2.jpg" alt="Hob" /></li>
    <li><img src="img/flat3.jpg" alt="Bathroom" /></li>
    <li><img src="img/flat4.jpg" alt="Living Room" /></li>
    <li><img src="img/flat5.jpg" alt="Bedroom" /></li>
</ul>

最后的输出会看起来像例子中的幻灯片效果。

依赖关系检查

我们这里有一些元素依赖于 JavaScript 生成:文字指示器和向前和向下的链接。为了保持我们解决方法的可用性,我们需要确保一些事情:

  1. 仅当 JavaScript 可用(用户信赖我们提供给他们使用的功能)时,这些元素应该出现。一个链接,不能做任何违反用户对我们的信任的事情。
  2. 不论输入设备(让我们不要依赖用户是否有鼠标),交互式元素都应该可用。
  3. 图片不应该被隐藏,除非使用者能再次访问他们。在技术上,仅显示第一张图片,且没有向前和向后的链接是预留退路的做法,但是为什么要用户已下载所有的图片仅只看到第一张?

第二步:规划脚本(Planning the Script)

一旦你已经评估了问题,并挑选出你想使用的解决方法,你便可以开始规划脚本。本质上,我们的脚本应该做这些:

  • 检查幻灯片列表是否存在,并且包含一些图片(有理由为一张图片创建一个幻灯片效果吗?)。
  • 隐藏所有的照片,但不是第一个。
  • 创建向前和向后的链接,和一个显示我们在哪的指示器。
  • 添加事件处理程序,使链接增加或减少当前显示的图片编号。
  • 确保幻灯片效果没有超出范围,当图片编号小于 0 ,他应该变为最后一张图片,反过来类似。

不同的功能处理

我们有一些方法处理这个问题。其中之一是使用 DOM 遍历每个 LI 条目并隐藏他。在这个事件监听函数,我们先隐藏先前显示的 LI (如果有的话),并显示当前的这个。

注:显示和隐藏代替图片的 LI 更有意义,因为他允许维护者在每个幻灯片上添加其他的元素,比如,一些标题。

这个方法的问题在于,我们在 JavaScript 中做必要的样式改变,这意味着如果有需要比刚才我们脚本中改变 display 从 block 到 none 更复杂的样式改变,将使脚本变得更杂乱(没有从行为中分离表现)。

样式留给 CSS 解析器

更简洁的方法是将所有的外观改变(在所有列表项下载完之后隐藏某些)都留给浏览器的 CSS 解析器。在我们的例子中,我们可以在幻灯片中使用一个 CSS 规则很容易地隐藏所有的列表项,并用一个特定的 class 重写当前条目的样式。

HTML:

<ul id="slideshow">
    <li><img src="img/flat1.jpg" alt="Hallway" /></li>
    <li><img src="img/flat2.jpg" alt="Hob" /></li>
    <li><img src="img/flat3.jpg" alt="Bathroom" /></li>
    <li><img src="img/flat4.jpg" alt="Living Room" /></li>
    <li><img src="img/flat5.jpg" alt="Bedroom" /></li>
</ul>

CSS:

#slideshow li{
    display:none;
}
#slideshow li.current{
    display:block;
}

唯一的问题是,如果我们使 CSS 和 JavaScript 不可用,访客将永远不能访问到其他图片。因此,我们需要仅当 JavaScript 可用时,应用这些样式。技巧是,当 JavaScript 可用,在幻灯片的 UL 上应用 class ,例如名为 js 。这允许我们仅当 JavaScript 可用时,显示效果,通过在 CSS 中简单的修改:

CSS:


#slideshow.js li{
    display:none;
}
#slideshow.js li.current{
    display:block;
}

这个 class 的钩子(hook)也能被用来对幻灯片的静态和动态版本提供一个完全不同的外观。

我们所有的脚本需要做的是,通过移除或添加 current 的 class 来显示和隐藏当前及以前的照片。

为了确保我们的脚本将不会影响同一页面上的其他脚本,我们将创建一个主要的对象,并在其上构造所有的方法和属性。这可以确保我们的 init() 函数将不会被覆盖或覆盖其他任何相同名字的函数。

JavaScript:

slideshow = {
    current:0, // 当前幻灯片编码
    init:function(){
        // 初始化和设置事件处理函数
    },
    show:function(e){
        // 事件监听器
    }
}

第三步、基本的工具方法( Essential Tools)

现在,我们有了规划和建立我们脚本的框架。是时候思考我们需要完成这个功能的一些工具方法。在其最低要求的情况下,DOM 脚本的帮助库应该包括:

  • 一个注册事件处理函数的方法,我们目前将使用 John Resig 的 addEvent() 方法。
  • 添加和移除 CSS 样式名的方法。
  • 一个覆盖 HTML 元素默认行为的方法。我们不希望出现链接的目标页,而仅是执行脚本。

我们添加这些工具方法到主要的对象上,并准备开始:

JavaScript:

slideshow = {
    current:0, // 当前幻灯片编码
    init:function(){
        // 初始化和设置事件处理函数
    },
    show:function(e){
        // 事件监听器
    },
    addEvent:function( obj, type, fn ) {
        if ( obj.attachEvent ) {
            obj['e'+type+fn] = fn;
            obj[type+fn] = function(){
                obj['e'+type+fn]( window.event );
            }
            obj.attachEvent(’on’+type, obj[type+fn] );
        } else
            obj.addEventListener( type, fn, false );
    },
    removeClass:function(o,c){
        var rep=o.className.match(’ ‘+c)?’ ‘+c:c;
        o.className=o.className.replace(rep,”);
    },
    addClass:function(o,c){
        var test = new RegExp(”(^|\\s)”+c+”(\\s|$)”).test(o.className);
        if(!test){o.className+=o.className?’ ‘+c:c;}
    },
    cancelClick:function(e){
        if (window.event){
            window.event.cancelBubble = true;
            window.event.returnValue = false;
        }
        if (e && e.stopPropagation && e.preventDefault){
            e.stopPropagation();
            e.preventDefault();
        }
    }
}

当文档完全载完,第一件事情就是需要执行 init() 方法:

JavaScript:

slideshow = {
    current:0, // 当前幻灯片编码
    init:function(){
        // 初始化和设置事件处理函数
    },
    show:function(e){
        // 事件监听器
    },
    addEvent:function( obj, type, fn ) {
        if ( obj.attachEvent ) {
            obj['e'+type+fn] = fn;
            obj[type+fn] = function(){
                obj['e'+type+fn]( window.event );
            }
            obj.attachEvent(’on’+type, obj[type+fn] );
        } else
            obj.addEventListener( type, fn, false );
    },
    removeClass:function(o,c){
        var rep=o.className.match(’ ‘+c)?’ ‘+c:c;
        o.className=o.className.replace(rep,”);
    },
    addClass:function(o,c){
        var test = new RegExp(”(^|\\s)”+c+”(\\s|$)”).test(o.className);
        if(!test){o.className+=o.className?’ ‘+c:c;}
    },
    cancelClick:function(e){
        if (window.event){
            window.event.cancelBubble = true;
            window.event.returnValue = false;
        }
        if (e && e.stopPropagation && e.preventDefault){
            e.stopPropagation();
            e.preventDefault();
        }
    }
}

slideshow.addEvent(window,’load’,slideshow.init);

第四步:脚本(The Script)

现在,在适当的位置我们有所有的方法工具,以及当窗口载完时被调用的 init() ,我们可以开始具体化此方法。

注:这仅是 init() 方法,而不是整个脚本。因为有行号,复制并粘贴脚本将会导致错误。

 1: init:function(){
 2:     if(document.getElementById && document.createTextNode){
 3:         var list = document.getElementById(' ');
 4:         if(list){
 5:             slideshow.items = list.getElementsByTagName('li');
 6:             slideshow.all = slideshow.items.length;
 7:             if(slideshow.all > 1){
 8:                 slideshow.addClass(list, 'js');
 9:                 slideshow.createNav(list);
10:            }
11:         }
12:         slideshow.show();
13:     }
14: },
  1. 第2行,检测 DOM 是否被支持。
  2. 第3和4行,尝试检索 ID 为 slideshow 的元素,如果没有被定义则不执行余下的方法。
  3. 第5和6行,检索列表项及列表项的个数,并分别储存在属性 items 和 all 里。
  4. 第7行,检测是否超多一个列表项,如果不超多则不执行余下的。
  5. 第8行,添加 js 样式类名到列表上,从而隐藏列表项和应该不同的样式。
  6. 第9行,调用 createNav(),并提供这个列表作为参数。
  7. 第12行,调用 show() 用来显示预定义了 current 属性的滑动门。

createNav() 方法使用 DOM 脚本创建幻灯片正常工作所需的 HTML。

 1: createNav:function(o){
 2:     var p = document.createElement('p');
 3:     slideshow.addClass(p, 'slidenav');
 4:     slideshow.prev = document.createElement('a');
 5:     slideshow.prev.setAttribute('href', '#');
 6:     var templabel = document.createTextNode('<<');
 7:     slideshow.prev.appendChild(templabel);
 8:     slideshow.addEvent(slideshow.prev, 'click', slideshow.show);
 9:     p.appendChild(slideshow.prev);
10:     slideshow.count = document.createElement('span');
11:     templabel = document.createTextNode( (slideshow.current+1) + ' / ' + slideshow.all);
12:     slideshow.count.appendChild(templabel);
13:     p.appendChild(slideshow.count);
14:     slideshow.next = document.createElement('a');
15:     slideshow.next.setAttribute('href', '#');
16:     var templabel = document.createTextNode('>>’);
17:     slideshow.next.appendChild(templabel);
18:     slideshow.addEvent(slideshow.next, ‘click’, slideshow.show);
19:     p.appendChild(slideshow.next);
20:     o.parentNode.insertBefore(p, o);
21: },
  1. 第2和3行,刚开始创建一个 P 元素,用来包含整个幻灯片导航,并应用一个名为 slidenav 的 class。
  2. 第4和5行,创建一个新的链接元素,储存在叫 prev 的属性中,设置 href 属性为 #。使链接显示为一个真正的链接且键盘可用,是有必要的。
  3. 第6行,创建一个新的文本标签。
  4. 第7行,将文本标签添加到链接上。
  5. 第8行,添加一个事件处理函数,指向 show() 监听方法。
  6. 第9行,将新的链接添加到 段落上。
  7. 第10行,开始计数器,我们创建一个 SPAN 元素,并用 count 属性储存他。
  8. 第11行,创建一个新的文本节点,显示当前幻灯片在总数中的位置。我们需要给当前的属性增加 1,因为人类计数是从 1 开始而非从 0。
  9. 第12行,将文本作为新的子节点,添加至 SPAN 上 。
  10. 第13行,将 SPAN 元素 添加到段落上。
  11. 第14至19行,基本上是复制 4 到 9 行,这次重新创建链接唯一不同的是文本标签,他储存在 next 属性上。
  12. 第20行,将最近创建的段落插入到文档中初始的图片列表前。

这些被创建的所有标记都是必要的,最后剩下的是去定义一个当链接被点击时调用的监听方法 show() 。

 1: show:function(e){
 2:     if(this === slideshow.next || this === slideshow.prev){
 3:         slideshow.removeClass(slideshow.items[slideshow.current], ‘current’);
 4:         var addto = (this === slideshow.next) ? 1 : -1;
 5:         slideshow.current = slideshow.current + addto;
 6:         if(slideshow.current < 0){
 7:             slideshow.current = (slideshow.all-1);
 8:         }
 9:         if(slideshow.current > slideshow.all-1){
10:             slideshow.current = 0;
11:         }
12:     }
13:     var templabel = document.createTextNode((slideshow.current+1) + ‘ / ‘ + slideshow.all);
14:     slideshow.count.replaceChild(templabel, slideshow.count.firstChild);
15:     slideshow.addClass(slideshow.items[slideshow.current], ‘current’);
16:     slideshow.cancelClick(e);
17: },
  1. 第1行,得到作为参数 e 的当前事件对象,这是稍后调用的 cancelClick() 唯一需要。
  2. 第2行,检测点击的元素是否是向下或者向前链接(this 由 addEvent() 返回)。
  3. 第3行,从当前显示的幻灯片上移除 current 的 class。由于现在有一个被点击的链接,这将成为可能。
  4. 第4行,通过比较 this 和 next 属性,决定 current 的计数器是应该增加还是减少。
  5. 第5行,修正计数器。
  6. 第6到11行,确定计数器将永远不会超出范围,当你在第一幻灯片并点击了向前的链接,将设置他为最后一个,而当你在最后一个幻灯片,点击了向后的链接,将设置为第一个。
  7. 第13和14行,生成一个新的计数器文本并替代旧的。
  8. 第15行,通过设置名为 current 的 class,显示新的当前幻灯片。
  9. 第16行,通过调用 cancelClick() 阻止链接的默认行为。

这些是脚本的所有内容。现在这个脚本可以工作,但仍不是真正可维护的。

第五步:轻松维护(Easing Maintenance)

脚本功能齐全,分离式而且无懈可击。真正的问题是,现在并不方便维护。

脚本应用的最大的问题大概是,并不是所有的维护者都懂 JavaScript 和愿意在你的脚本中寻找需要修改的部分。

为了避免维护者做这些,最安全的方法就是把脚本和 CSS 中使用的命名和 ID 从你的脚本功能中分离出来。此外,从使用的脚本中分离出文本标签也是个好点子,因为他们可能会改变。例如,当脚本使用其他语言本地化时。

工具方法的复用

第一件要做的事情就是,从主要脚本中分离出其他脚本也可以再用的工具函数。这也许是大部分 JavaScript 库的开始。

tools.js

/* 辅助方法 */
tools = {
    addEvent:function( obj, type, fn ) {
        if ( obj.attachEvent ) {
            obj['e'+type+fn] = fn;
            obj[type+fn] = function(){
                obj['e'+type+fn]( window.event );
            }
            obj.attachEvent( ‘on’+type, obj[type+fn] );
        } else
            obj.addEventListener( type, fn, false );
        },
    removeClass:function(o,c){
        var rep=o.className.match(’ ‘+c)?’ ‘+c:c;
        o.className=o.className.replace(rep,”);
    },
    addClass:function(o,c){
        var test = new RegExp(”(^|\\s)” + c + “(\\s|$)”).test(o.className);
        if(!test){o.className+=o.className?’ ‘+c:c;}
    },
    cancelClick:function(e){
        if (window.event){
            window.event.cancelBubble = true;
            window.event.returnValue = false;
        }
        if (e && e.stopPropagation && e.preventDefault){
            e.stopPropagation();
            e.preventDefault();
        }
    }
}

CSS 的 class 和 ID —— 外观

下一步要做的是,分离外观的 class 和 ID 到一个单独的包含文件。保证他们在 slideshow 命名空间里是安全的,因为其他脚本不太可能用到他们。也不会妨碍写一个简短的说明注释。

slideshow-css.js

slideshow.css = {
    /*
    这些都是幻灯片效果中使用到的 classe 和 ID。
    你可以在这里修改他们中的任何一个。
    务必请使用引号包围名称,用逗号结尾(除了最后一个)。
    */

    showID               :'slideshow',
    dynamicClass         :'js',
    slideNavigationClass :'slidenav',
    currentClass         :'current'
}

文本标签(Text labels)—— 解释给终端用户

最后但不是最不重要的,让我们将文本标签放到一个单独的包含文件,再次使用 slideshow 命名空间。

slideshow-labels.js

slideshow.labels = {
    /*
    这些都是幻灯片效果中使用到文本标签。
    你可以在这里修改他们中的任何一个。
    务必请使用引号包围名称。
    最后一个结尾不用逗号。
    */

    previous       : '<<',
    next           : '>>’,
    counterDivider : ‘ of ‘
}

改变的主要脚本

然后,我们需要修改主要脚本使用此信息,而不是依赖嵌入式的数据。没有太多的改变,很容易用搜索加替换就能做到。

slideshow.js

slideshow = {
    current:0,
    init:function(){
        if(document.getElementById && document.createTextNode){
            var list =document.getElementById(slideshow.css.showID);
            if(list){
                slideshow.items = list.getElementsByTagName('li');
                slideshow.all = slideshow.items.length;
                if(slideshow.all > 1){
                    tools.addClass(list, slideshow.css.dynamicClass);
                    slideshow.createNav(list);
                }
            }
            slideshow.show();
        }
     },
    createNav:function(o){
        var p = document.createElement('p');
        tools.addClass(p, slideshow.css.slideNavigationClass);
        slideshow.prev = document.createElement('a');
        slideshow.prev.setAttribute('href', '#');
        var templabel = document.createTextNode(slideshow.labels.previous);
        slideshow.prev.appendChild(templabel);
        tools.addEvent(slideshow.prev, 'click', slideshow.show);
        p.appendChild(slideshow.prev);
        slideshow.count = document.createElement('span');
        templabel =document.createTextNode((slideshow.current+1) + slideshow.labels.counterDivider + slideshow.all);
        slideshow.count.appendChild(templabel);
        p.appendChild(slideshow.count);
        slideshow.next = document.createElement('a');
        slideshow.next.setAttribute('href', '#');
        var templabel = document.createTextNode(
        slideshow.labels.next);
        slideshow.next.appendChild(templabel);
        tools.addEvent(slideshow.next, 'click', slideshow.show);
        p.appendChild(slideshow.next);
        o.parentNode.insertBefore(p, o);
    },
    show:function(e){
        if(this === slideshow.next || this === slideshow.prev){
            tools.removeClass(slideshow.items[slideshow.current],
            slideshow.css.currentClass);
            var addto = this === slideshow.next ? 1 : -1;
            slideshow.current = slideshow.current + addto;
            if(slideshow.current < 0){
                slideshow.current = (slideshow.all-1);
            }
            if(slideshow.current > slideshow.all-1){
                slideshow.current = 0;
            }
        }
        var templabel = document.createTextNode((slideshow.current+1) + slideshow.labels.counterDivider + slideshow.all);
        slideshow.count.replaceChild(templabel, slideshow.count.firstChild);
        tools.addClass(slideshow.items[slideshow.current], slideshow.css.currentClass);
        tools.cancelClick(e);
    }
}
tools.addEvent(window,’load’,slideshow.init);

这些所有文件是确保将来维护者不用麻烦你就可以使用你的脚本工作所需要的。文件名应该很明显,是什么就是什么,并能随着时间的推移,成为一个标准的脚本:

原作者:Christian Heilmann   译者:怿飞

原文:《A detailed explanation how to create a maintainable dynamic slide show in JavaScript》

针对 IE8 的 hack

前言:对于 Hack 的使用,一直以来不提倡,应该尽可能的知道为什么,从根本上去解决问题,而不要遇到问题就 Hack,我们应该对代码“不作恶”,尽少使用,甚至不用。

对于 IE8 beta1 可以尝试下面的 Hack:

/*/ selector { … } /**/

此规则仅 IE8 beta1 识别,而其他 A-grade 浏览器都不识别。

不过这个 Hack 也许会在 IE 的 beta2 版本或正式版中被修复,暂可作为 IE8 beta1 调试之用。

innerHTML 的一些问题

innerHTML 属性的使用非常流行,因为他提供了简单的方法完全替代一个 HTML 元素的内容。另外一个方法是使用 DOM Level 2 API(removeChild, createElement, appendChild)。但很显然,使用 innerHTML 修改 DOM tree 是非常容易且有效的方法。然而,你需要知道 innerHTML 有一些自身的问题:

  1. 当 HTML 字符串包含一个标记为 defer 的 script 标签(<script defer>…</script>)时,如 innerHTML 属性处理不当,在 Internet Explorer 上会引起脚本注入攻击。
  2. 设置 innerHTML 将会破坏现有的已注册了事件处理函数的 HTML 元素,会在某些浏览器上引起内存泄露的潜在危险。

还有几个其他次要的缺点,也值得一提的:

  1. 你不能得到刚刚创建的元素的引用,需要你手动添加代码才能取得那些引用(使用 DOM APIs)。
  2. 你不能在所有浏览器的所有 HTML 元素上设置 innerHTML 属性(比如,Internet Explorer 不允许你在表格的行元素上设置innerHTML 属性)。

我更关注与使用 innerHTML 属性相关的安全和内存问题。很显然,这不是新问题,已经有能人围绕这些中的某些问题想出了方法。

Douglas Crockford 写了一个清除函数,该函数负责中止由于 HTML 元素注册事件处理函数引起的一些循环引用,并允许垃圾回收器(garbage collector)释放与这些 HTML 元素关联的内存。

从 HTML 字符串中移除 script 标签并不像看上去那么容易。一个正则表达式可以达到预期效果,虽然很难知道是否覆盖了所有的可能性。这里是我的解决方案:

/<script[^>]*>[\S\s]*?<\/script[^>]*>/ig

现在,让我们将这两种技术结合在到一个单独的 setInnerHTML 函数中,并将 setInnerHTML 函数绑定到 YUI 的 YAHOO.util.Dom 上:


YAHOO.util.Dom.setInnerHTML = function (el, html) {
    el = YAHOO.util.Dom.get(el);
    if (!el || typeof html !== 'string') {
        return null;
    }

    // 中止循环引用
    (function (o) {

        var a = o.attributes, i, l, n, c;
        if (a) {
            l = a.length;
            for (i = 0; i < l; i += 1) {
                n = a[i].name;
                if (typeof o[n] === 'function') {
                    o[n] = null;
                }
            }
        }

        a = o.childNodes;

        if (a) {
            l = a.length;
            for (i = 0; i < l; i += 1) {
                c = o.childNodes[i];

                // 清除子节点
                arguments.callee(c);

                // 移除所有通过YUI的addListener注册到元素上所有监听程序
                YAHOO.util.Event.purgeElement(c);
            }
        }

    })(el);

    // 从HTML字符串中移除script,并设置innerHTML属性
    el.innerHTML = html.replace(/<script[^>]*>[\S\s]*?<\/script[^>]*>/ig, "");

    // 返回第一个子节点的引用
    return el.firstChild;
};

如果此函数还应有其他任何内容或者在正则表达式中遗漏了什么,请让我知道

很明显,在网页上还有很多其他注入恶意代码的方法。setInnerHTML 函数仅能在所有 A-grade 浏览器上规格化 <script> 标签的执行行为。如果你准备注入不能信任的 HTML 代码,务必首先在服务器端过滤,已有许多库可以做到这点。

原文:Julien Lecomte《The Problem With innerHTML》

如何在本地使用 Yahoo! BrowserPlus

对于 Yahoo! BrowserPlus 的使用是有限制的(源自《How can I use BrowserPlus on my site?》):

During the “sneak peek,” BrowserPlus can only used on sites owned by Yahoo! or its partners. Our first full public release will make BrowserPlus available for use by 3rd party developers.

同时也包括限制本地的文件,对于想先睹为快的我们来说,很失落。不过现在通过 Skylar WoodwardL 的 hack 方法,修改下本地文件,就可以让轻松突破原本的本地文件限制,尝试一下吧,嘿嘿……

找到系统上 Permissions 文件夹下的 Permissions 文件:

  • Mac:/Users/[you]/Library/Application Support/Yahoo!/BrowserPlus/
  • Windows XP:c:\\Documents And Settings\[you]\Local Settings\Application Data\Yahoo!\BrowserPlus\
  • Windows Vista:c:\Users\[you]\AppData\Local\Yahoo!\BrowserPlus\

修改文件中的

"whitelist" : [
	"^http(s?)://(.*)\\.yahoo\\.com$",
	"^http(s?)://(.*)\\.yahoo\\.com:[0-9]+$"
],

"whitelist" : [
	"^http(s?)://(.*)\\.yahoo\\.com$",
        "^http(s?)://(.*)\\.yahoo\\.com:[0-9]+$",
	"^file://$"
],

然后关闭所有打开的浏览器,或者从进程中删除 BrowserPlus 进程,就可生效了,够简单吧。

Yahoo! BrowserPlus

BrowserPlus 到底是什么,又能做什么?

BrowserPlus 是 Yahoo! 最近刚发布一个 Web 扩展的平台:终端用户需安装他,而开发者可以通过一个小的 JavaScript 库使用他的特性。平台现有的一些属性包括:

  1. 从桌面的拖拽;
  2. 用户断图片的处理(裁切,翻转和滤镜);
  3. 桌面通知。

BrowserPlus 最独特的特性是他能在运行中更新和添加服务,而无需重新启动浏览器或甚至加载页面。对于用户,这意味着没有更多的中断或安装运行,而 Yahoo! 去处理软件分发的复杂性以及升级。对于开发者,这意味着能够通过一个简单的函数引用检查和激活新的服务(当然待用户批准)。

目前支持哪些平台:

  • Windows XP
  • Windows Vista
  • Mac OS X 10.4, Tiger (intel only)
  • Mac OS X 10.5, Leopard (intel only)

目前支持哪些浏览器:

  • 在 Mac 上支持 Firefox 2 (或高于) 和 Safari 3
  • 在 Windows 上支持 Internet Explorer 7 和 Firefox 2 (或更高)

了解更多:《Frequently Asked Questions》

今天大体看了一些 范例,感觉很方便,而且不用额外的学习新语言,额外的使用新工具,仅需要熟悉服务的 API 即可。

看个简单的样例——“使用服务”:

<script class="javascript" src="http://bp.yahooapis.com/2.0.4/browserplus-min.js"></script>
<script class="javascript">
// the "service specification" that we'll activate
var ttsService = {
    service: "TextToSpeech",
    version: "1",
    minversion: "1.0.2"
};  

// check for the presence of TextToSpeech, and dump results in the
// specified div
function checkForTextToSpeech(divName) {
    YAHOO.bp.isServiceActivated(
        ttsService,
        function() {
            var _divName = divName;
            return function(haveIt) {
                var d = document.getElementById(_divName);
                d.innerHTML = haveIt;
            };
        }()
    );
}  

YAHOO.bp.init(function(res) {
    if (res.success) {
        checkForTextToSpeech("before");
        YAHOO.bp.require({
            services: [ ttsService ]},
            function(r) {
                checkForTextToSpeech(”after”);
                if (r.success) {
                    YAHOO.bp.TextToSpeech.Say(
                        { utterance: “text to speech is activated” },
                        function() {}
                    );
                }
            }
        );
    }
});
</script>

样例代码(Sample Code):http://browserplus.yahoo.com/docs/samples/

服务检测(Service Explorer):http://browserplus.yahoo.com/services/

如何让 Firefox 2 和 Firefox 3 版本并存

最新的 Firefox 2 版本为 Firefox 2.0.0.14, Firefox 3 版本为 Firefox 3.0rc1。主流依然是 Firefox 2.0.0.14,但由于在不久的将来 Firefox 2 会升级到 Firefox 3,对于我们前端是好消息(更好更优的功能),也是坏消息(提前进入“作战”状态,现在就应该开始测试网站在 Firefox 3 下的兼容性了!)。

大家都知道 IE 有多版本集成的软件(比如:IETester),而 Firefox 的多版本如何解决呢?

下面提供两个方案:

1、针对Windows的用户:《How to Run both Firefox 2 and Firefox 3 Together》,不爱看或者看不懂英文的,还可以《如何让Firefox2和Firefox3在Windows下共存?》

2、针对其他平台的用户(aoao 提供的方案):Firefox 装 3.0 的,再安装个 Flock1.x(内核为Firefox 2),就可以并存两个版本,某些插件不支持新版可装 Nightly Tester Tools 来解决。

图片垂直居中的使用技巧

在曾经的 淘宝UED 招聘 中有这样一道题目:

“使用纯CSS实现未知尺寸的图片(但高宽都小于200px)在200px的正方形容器中水平和垂直居中。”

当然出题并不是随意,而是有其现实的原因,垂直居中是 淘宝 工作中最常遇到的一个问题,很有代表性。

题目的难点在于两点:

  1. 垂直居中;
  2. 图片是个置换元素,有些特殊的特性。

至于如何解决,下面是一个权衡的相对结构干净,CSS简单的解决方法:

.box {
	/*非IE的主流浏览器识别的垂直居中的方法*/
	display: table-cell;
	vertical-align:middle;

	/*设置水平居中*/
	text-align:center;

	/* 针对IE的Hack */
	*display: block;
	*font-size: 175px;/*约为高度的0.873,200*0.873 约为175*/
	*font-family:Arial;/*防止非utf-8引起的hack失效问题,如gbk编码*/

	width:200px;
	height:200px;
	border: 1px solid #eee;
}
.box img {
	/*设置图片垂直居中*/
	vertical-align:middle;
}

<div class="box">
	<img src="http://pics.taobao.com/bao/album/promotion/taoscars_180x95_071112_sr.jpg" />
</div>

当然还有其他的解决方法,在此不深究,有兴趣的可以阅读下:

最新评论

  • 考试中国: 期待8月份的beta2...
  • ipoping: 谢谢你啦。好东西哦,正在学...
  • 我de艾蜜莉: 这样的解决方法我测试了一下,不能...
  • Zehee: 我觉得其实不用“给连续字符的每个...
  • Lunatic Sun: 如果不嫌麻烦,也可以使用soft hyphen...
  • MacJi: 真是的,人家就用别的语言评论,你...
  • MacJi: 居然你自己做沙发了。。...
  • 怿飞: 对于提到的 XBL bindings ,也可以看类似...
  • 怿飞: 对于不太重要的内容,连续字符在FF...
  • 怿飞: @xling 不知道为什么你的评论有乱码,...

管理登录