日志更新

Google DevFest 2010 Beijing 之行

非常荣幸作为嘉宾参加了 Google DevFest 2010 Beijing,感谢 Google 胡坤的邀请,感谢 Google 寒蕊的合作,简单的接触,能够感受到 Google 的开放与细腻,或许这就是 Google 特有的品质。

Google DevFest 2010 Beijing 嘉宾卡

下午《如何用 HTML5 做应用开发》的话题,Google 寒蕊 主要分享了 HTML5 的 What ,而我主要针对 HTML5 的 Where、Why 和 How 做了简单的演示。

PPT:HTML5 in Action

分享《如何用 HTML5 做应用开发》

《如何用 HTML5 做应用开发》Q&A 环节

《HTML5 in Action》PPT 地址 :http://www.planabc.net/demo/html5/ppt.html ,PPT 基于 HTML5 开发,为了最佳效果,建议使用 Chrome 浏览器,小键盘左右键前后翻页。由于原来在本地演示,跨域演示的环境已改变,可以自己模拟。

寒蕊的《HTML5下一代的 Web 开发标准》PPT地址:http://look-into-html5.appspot.com

会议结束后所有的同事一起合了张影:

Taobao 同事合影

另外的收获:

  1. 见到了认识了几年但一直未曾谋面的 Opera Zibin,从他那里也获知了他正在向 W3C 申请 HTML5中文兴趣小组,期待好消息。
  2. 从这次的举办中学到了许多不错的会议细节,相信下次 D2 前端技术论坛 的举办,我们将会做得更好。
  3. 见到了熟悉的老朋友 Tencent 梁璟彪,约定明年 D2 前端技术论坛 将与 WebReBuild 联手合作,明年注定属于前端年。

浅谈 Mousewheel 事件

当需要制作转动鼠标滚轮放大页面字体这样的交互效果时,会用到 Mousewheel 事件。其实在大多数浏览器(IE6, IE7, IE8, Opera 10+, Safari 5+)中,都提供了 “mousewheel” 事件。但杯具的是 Firefox 3.5+ 却不支持此事件,不过庆幸 Firefox 3.5+ 中提供了另外一个等同的事件:”DOMMouseScroll” (事件和事件属性的测试案例)。

OK,我们现在已经知道了不同浏览器之间实现的差别,兼容代码如下:

var addEvent = (function(){
        if (window.addEventListener) {
            return function(el, sType, fn, capture) {
                el.addEventListener(sType, fn, (capture));
            };
        } else if (window.attachEvent) {
            return function(el, sType, fn, capture) {
                el.attachEvent("on" + sType, fn);
            };
        } else {
            return function(){};
        }
    })(),
    // isFirefox 是伪代码,大家可以自行实现
    mousewheel = isFirefox ? "DOMMouseScroll" : "mousewheel";

// object 也是伪代码,你需要注册 Mousewheel 事件的元素
addEvent(object, mousewheel, function(event){
    event = window.event || event;
    // todo something
}, false);

我们再回到要实现的交互效果上,现在还有其他一些问题需要来解决:

  1. 页面字体到底是放大还是缩小呢? ==> 鼠标滚轮是向上滚动还是向下滚动呢?
  2. 页面字体缩放的倍数到底是多少呢? ==> 鼠标滚轮滚动的幅度大小是多少呢?

还好,我们可以通过 event 的某些属性值得到这些信息:

  1. “mousewheel” 事件中的 “event.wheelDelta” 属性值:返回的值,如果是正值说明滚轮是向上滚动,如果是负值说明滚轮是向下滚动;返回的值,均为 120 的倍数,即:幅度大小 = 返回的值 / 120。
  2. “DOMMouseScroll” 事件中的 “event.detail” 属性值:返回的值,如果是负值说明滚轮是向上滚动(与 “event.wheelDelta” 正好相反),如果是正值说明滚轮是向下滚动;返回的值,均为 3 的倍数,即:幅度大小 = 返回的值 / 3。
  3. “mousewheel” 事件在 Opera 10+ 中却是个特例,既有 “event.wheelDelta” 属性,也有 “event.detail” 属性。

注:上面第三点,在《The onmousewheel event of JavaScript》一文中有这样一段批注:

In Opera, “detail” returns the same value as it does in FF, so for the big O you should rely on “detail” instead of “wheelDelta”, which depending on the Opera version may return a different value than in IE’s.

但经测试, Opera 9+ 和 Opera 10+ 中的 event.wheelDelta 属性与其他浏览器中的表现完全一致,未发现异常与错误,从接口角度来说,代码中应优先使用 “event.wheelDelta” 属性。

此时代码如下:

var addEvent = (function(){
        if (window.addEventListener) {
            return function(el, sType, fn, capture) {
                el.addEventListener(sType, fn, (capture));
            };
        } else if (window.attachEvent) {
            return function(el, sType, fn, capture) {
                el.attachEvent("on" + sType, fn);
            };
        } else {
            return function(){};
        }
    })(),
    stopEvent: function(event) {
        if (event.stopPropagation) {
            event.stopPropagation();
        } else {
            event.cancelBubble = true;
        }
        
        if (event.preventDefault) {
            event.preventDefault();
        } else {
            event.returnValue = false;
        }
    },
    zoomIn = function(){},
    zoomOut = function(){},
    // isFirefox 是伪代码,大家可以自行实现
    mousewheel = isFirefox ? "DOMMouseScroll" : "mousewheel";

// object 是伪代码,你需要注册 Mousewheel 事件的元素
addEvent(object, mousewheel, function(event){
    var delta = 0;
    event = window.event || event;
    stopEvent(event);

    delta = event.wheelDelta ? (event.wheelDelta / 120) : (- event.detail / 3); 
    // zoomIn, zoomOut 是伪代码,需要实现的缩放事件
    delta > 0 ? zoomIn(delta): zoomOut(Math.abs(delta));
} , false);

事件和事件属性的测试案例:http://www.planabc.net/demo/event/mousewheel.html

扩展阅读:

在线 Base64 编/解码小工具

抽时间做了一个简单的基于 JavaScript 的在线 Base64 编/解码小工具:

http://www.planabc.net/lab/tools/base64.html

原理详见上篇文章:《JavaScript 中的 Base64 编码(一):Encoding 》

注:对于下面的代码:

TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBvbmx5IGJ5IGhpcyByZWFzb24sIGJ1dCBieSB0aGlz
IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbmltYWxzLCB3aGljaCBpcyBhIGx1c3Qgb2Yg
dGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2Ugb2YgZGVsaWdodCBpbiB0aGUgY29udGlu
dWVkIGFuZCBpbmRlZmF0aWdhYmxlIGdlbmVyYXRpb24gb2Yga25vd2xlZGdlLCBleGNlZWRzIHRo
ZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=

各浏览器原生的解码方法处理不太一致,Opera 比较智能能够处理此情形,但 Firefox、Safari 和 Chrome 却无法处理。

出现上面代码的原因为:规则约定 Base64 编码过程每76个字符加一个换行符

Firefox、Safari 和 Chrome 下的修复非常简单(对输入的字符去除换行符等):

text = text.replace(/\s*/g, ''); //Fix Firefox/Safari/Chrom bug: MultiLine

如有 BUG 欢迎随时联系:blankzheng#gmail.com

JavaScript 中的 Base64 编码(一):Encoding

除了IE浏览器,其他所有主流的浏览器均支持原生的 Base64 编码:

  • btoa(text) – base64 encodes text.
  • atob(text) – base64 decodes text.

而对于 IE 我们可以根据已知的 Base64 编码原理进行编写:

Base64 编码将每三个 8Bit 的字节(注:由于要求输入的字符为 8Bit 字节,故范围应该在 ASCII 字符范围内,即:\u0000-\u00ff)转换为四个 6Bit 的字节(3*8 = 4*6 = 24),然后在每个 6Bit 字节前添两位高位 0,组成四个 8Bit 的字节,最后再将每个 8Bit 字节转换成十进制的数字,对应 Base64 编码表(为了保证所输出的编码为可读字符,Base64制定了一个编码表,以便进行统一转换,编码表的大小为 2^6=64,即 Base64 名称的由来)输出编码后的字符。

如果原字节不足 3 的倍数,则用 0 填充,输出字符使用“=”,因此编码后输出的文本末尾可能会出现 1 或 2 个“=”(余数 = 原文字节数 MOD 3 ,如果余数为 1,则要补 2 个“=”,为 2,则补 1 个“=”)。

Base64 编码表
Value Char   Value Char   Value Char   Value Char
0 A 16 Q 32 g 48 w
1 B 17 R 33 h 49 x
2 C 18 S 34 i 50 y
3 D 19 T 35 j 51 z
4 E 20 U 36 k 52 0
5 F 21 V 37 l 53 1
6 G 22 W 38 m 54 2
7 H 23 X 39 n 55 3
8 I 24 Y 40 o 56 4
9 J 25 Z 41 p 57 5
10 K 26 a 42 q 58 6
11 L 27 b 43 r 59 7
12 M 28 c 44 s 60 8
13 N 29 d 45 t 61 9
14 O 30 e 46 u 62 +
15 P 31 f 47 v 63 /

比如:

字符:               f        2         e
ASCII:              102      50        101
3个8Bit字节:        01100110 00110010  01100101
4个6Bit字节:          011001   100011    001001    100101
高位补0:            00011001 00100011  00001001  00100101
十进制:             25       35        9         37
对应码表值:         Z        j         J         l

最终: btoa('f2e') = ZjJl
字符:              b        a        s        e       
ASCII:             98       97       115      101
3个8Bit字节:       01100010 01100001 01110011 01100101 00000000 00000000
4个6Bit字节:         011000   100110   000101   110011   011001   010000    000000   000000
高位补0:           00011000 00100110 00000101 00110011 00011001 00010000 00000000 00000000
十进制:            24       38       5        51       25       16         
对应码表值:        Y        m        F        z        Z        Q        =        =

最终: btoa('base') = YmFzZQ==

如果将上面的 Base64 编码原理换成接近于编程的思维,过程大致如下(以f2e为例):

注:Base64 编码表我们可以简化为字符串,并通过其进行位置索引:

table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

第一步:

  1. 第一个字符 f(102 -> 01100110)通过右移 2 位(first >> 2)获得第一个目标字符(00011001 -> 25),索引编码表中对应的目标字符 Z(table.charAt(first >> 2))。

第二步:

  1. 将第一个字符 f(102 -> 01100110)与 3(00000011)进行 AND 运算(first & 3)):
        01100110
    AND 00000011
    ------------
        00000010
  2. 第一个字符运算后(00000010)再左移 4 位((first & 3) << 4),得:00100000。
  3. 将第二个字符 2 (50 -> 00110010)右移 4 位(second >> 4),得:00000011。
  4. 最后将运算后的两个字符进行 OR 运算((first & 3) << 4 | second >> 4):
        00100000
    OR  00000011
    ------------
        00100011

    获得第二个目标字符(00100011 -> 35),索引编码表中对应的目标字符 j(table.charAt((first & 3) << 4 | second >> 4))。

第三步:

  1. 将第二个字符 2 (50 -> 00110010)与 15(00001111,十六进制:0x0f)进行 AND 运算(second & 0x0f):
        00110010
    AND 00001111
    ------------
        00000010
    
  2. 第二个字符运算后(00000010)再左移 2 位((second & 0x0f) << 2),得:00001000。
  3. 第三个字符 e(101 -> 01100101)右移 6 位(third >> 6),得:00000001。
  4. 最后将运算后的两个字符进行 OR 运算((second & 0x0f) << 2 | third >> 6):
        00001000
    OR  00000001
    ------------
        00001001

    即获得第三个目标字符(00001001 -> 9),索引编码表中对应的目标字符 J(table.charAt((second & 0x0f) << 2 | third >> 6))。

第四步:

  1. 取第三个字符 e(101 -> 01100101)的右 6 位,即与 63 (00111111,16进制:0x3f)进行 AND 运算(third & 0x3f):
        01100101
    AND 00111111
    ------------
        00100101

    获得第四个目标字符(00100101 -> 37),索引编码表中对应的目标字符 l(table.charAt(third & 0x3f))。

异常情况:

  1. 当第二个字符不存在时(即:余数 = 原文字节数 MOD 3 ,余数为 1),截止至第二步的第 2 小步,然后在最终输出的目标字符后添加两个“=”。
  2. 当第三个字符不存在时(即:余数 = 原文字节数 MOD 3 ,余数为 2),截止至第三步的第 2 小步,然后在最终输出的目标字符后添加两个“=”。

代码实现如下:

if(!window.btoa) {
    window.btoa  = function(text) {
        if (/([^\u0000-\u00ff])/.test(text)) return;
        var table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",
            i = 0,
            cur, prev, byteNum,
            result=[];    
            
        while(i < text.length){
            cur = text.charCodeAt(i);
            byteNum = (i+1) % 3;

            switch(byteNum){
                case 1: //first byte
                    result.push(table.charAt(cur >> 2));
                    break;
    
                case 2: //second byte
                    result.push(table.charAt((prev & 3) << 4 | (cur >> 4)));
                    break;
    
                case 0: //third byte
                    result.push(table.charAt((prev & 0x0f) << 2 | (cur >> 6)));
                    result.push(table.charAt(cur & 0x3f));
                    break;
            }
    
            prev = cur;
            i++;
        }

        if (byteNum == 1){
            result.push(table.charAt((prev & 3) << 4));
            result.push("==");
        } else if (byteNum == 2){
            result.push(table.charAt((prev & 0x0f) << 2));
            result.push("=");
        }
  
        return result.join("");
    } 
} 

-----------------------------------------------------------------------------

下篇:《JavaScript 中的 Base64 编码(二):Decoding》

-----------------------------------------------------------------------------

扩展阅读: