关于IE透明度失效的问题

2010-07-27 by zhenn 发布在 html+css | 标签:

近期项目中,发现这个诡异的问题,IE(以下如有特殊说明,指全部IE系列浏览器)下透明滤镜竟然会失效。起因在于,在做一个lightbox时,发现遮罩层透明效果是失效的,在第一时间里,我当然是把问题定位在YUI组建设计的缺陷上,但是当我用IE调试工具看到遮罩层渲染的样式时,我震惊了。如下图:

ss

从渲染的css上看,没有任何问题,百思不得其解,更为让我疑惑的是,在我同事的机器上,遮罩层的透明效果是有的,难道我RP有问题?太不可思议了。

既然问题出了,就要去想办法解决,没有任何回避的理由,经过一番细心的排查,发现我和同事的机器唯一的差别就在于装的操作系统不一样,他的是XP,而我机器上装的是WIN7。这可能就是问题的所在了,但是细想,WIN7不可能不支持filter-alpha-opacity的,这样的结论相信没有人相信,因为我看到我以前写的遮罩层组件是可以正常工作的,所以排除WIN7不支持alpha的可能,那么问题到底出在哪里呢,请看可以正常工作的遮罩层CSS:

ssss

如果你足够细心,我想应该会发现区别在哪儿,没错,就是高度不一样,一个是20000px(失效),一个是1801px(有效)。既然高度会影响filter的渲染,那么肯定会有一个临界点,事实证明,的确如我所料,在WIN7的环境中,如果元素的高度超过4096px,就一定会导致filter-alpha-opacity失效,而这一切在XP环境中并不会发生,真是验证了一句话,在MS的世界里,没有什么不可能!

拓展:在vista系统中,也存在相同的问题,元素高度的临界点为2048px。所谓标准浏览器(firefox、chrome、safari之流)中,opacity渲染的透明度是跨系统的,不会存在失效问题

PS:邪恶的WIN7,邪恶的IE,永远遭我BS.

基于ajax异步请求的选项卡

2010-07-05 by zhenn 发布在 javascript | 标签: ,

关于ajax异步请求的tab技术很常见,在很多门户网站上都可以看到他的身影,特别是在频道首页,这样做的目的是显而易见的,分步请求数据,减缓服务器的压力,带来更为流畅的用户体验。如下图:

ajaxtab

设计起来当然不是什么问题,就是一个特殊的和服务器有通信的tab而已,在首轮tab切换过程中,发送一个XHR,然后再把Server响应的数据(基本上是html字串)塞到相应的容器中即可。这个过程中有个问题需要注意,在每个选项点击第一次之后,已经把所需要的数据加载到客户端,那么再次切换的时候就没有必要重新请求服务器了,这样做的依据就是判断内容容器中的innerHTML是否为空。

诚然,你如果真的反其道而行之,也并无过错,并不会影响网页功能的展现,但却是徒劳无益,反而加重服务器的负担,并且会影响到用户体验,这也就有被于使用ajax tab的初衷了。

同样的,详情请登录 :Demo

js浮点数运算的bug

2010-07-03 by zhenn 发布在 javascript | 标签: ,

老实说在今天之前,我对javascript的浮点数运算毫无认识,觉得应该和实际运算中一样,然而,有些事情往往会出人意料。

先看一组数据:
0.1 + 0.11 = 0.21000000000000002
0.2 * 3 = 0.6000000000000001
0.7 / 10 = 0.06999999999999999

你相信吗?如果不信,那么立即打开firefox浏览器测试吧。有一点可以确信,你看到测试结果的表情,一定先是惊讶,后来是迷惑。

这里有几个demo,再来看看出现浮点数运算bug的概率,猛击之:加法乘法除法

对于这样的结果,我不知道是怎么产生的,但是既然是bug,就一定是可以hack的,思路很简单,先把所有的运算数转成int型(表达不准确,javascript的没有int、float之分),计算出两个运算数的小数点后的位数,再进行适当的运算,就可以得到原始的我们期望看到的结果了。代码如下:

加法运算:

  1. function add(num1,num2){
  2.         var reg = /\./i;
  3.     if(!reg.test(num1) && !reg.test(num2)){
  4.         return num1 * num2;
  5.     }
  6.     var r1 = 0, r2 = 0, m;
  7.     var str1 = num1.toString(), str2 = num2.toString();
  8.     if(str1.indexOf('.')>-1){
  9.         r1 = str1.split('.')[1].length;
  10.     }
  11.     if(str2.indexOf('.')>-1){
  12.         r2 = str2.split('.')[1].length;
  13.     }
  14.     m = Math.pow(10,Math.max(r1,r2));
  15.     return (mul(num1,m) + mul(num2,m)) / m;
  16. }

乘法运算:

  1. function mul(num1,num2){
  2.         var reg = /\./i;
  3.     if(!reg.test(num1) && !reg.test(num2)){
  4.         return num1 * num2;
  5.     }
  6.     var len = 0, str1 = num1.toString(), str2 = num2.toString();
  7.     if(str1.indexOf('.')>=0){
  8.         len += str1.split('.')[1].length;
  9.     }
  10.     if(str2.indexOf('.')>=0){
  11.         len += str2.split('.')[1].length;
  12.     }
  13.     return Number(str1.replace('.','')) * Number(str2.replace('.','')) / Math.pow(10,len);
  14. }

除法运算:

  1. function div(num1,num2){
  2.         var reg = /\./i;
  3.     if(!reg.test(num1) && !reg.test(num2)){
  4.         return num1 * num2;
  5.     }
  6.     var len1 = 0, len2 = 0;
  7.     var str1 = num1.toString(), str2 = num2.toString();
  8.     //计算位数差
  9.     if(str1.indexOf('.')>-1){
  10.         len1 = str1.split('.')[1].length;
  11.     }
  12.     if(str2.indexOf('.')>-1){
  13.         len2 = str2.split('.')[1].length;
  14.     }
  15.     return mul(Number(str1.replace('.','')) / Number(str2.replace('.','')),Math.pow(10,len2-len1));
  16. }

一个关于倒计时的类

2010-06-26 by zhenn 发布在 javascript | 标签:

近期在做排列五的彩票项目,每一期都有购彩时段,即用户打开这个排列五的页面时,会从服务器传来一个remaintime(离本次彩期结束的剩余时间),然后这个时间在客户端递减呈现给用户看,让用户获得本次彩期的剩余时间。

实现原理挺简单的,在此不在赘述,敬请猛击demo

调用方法如下:

  1. new TheTime({
  2.     //容器id
  3.     container: 'remaintime',
  4.     //服务器返回的剩余时间,单位为秒
  5.     remaintime: 10000,
  6.     //倒计时完成时的回调
  7.     callback: function(){
  8.         document.getElementById('remaintime').innerHTML = '已截止';
  9.     }
  10. });

基于yui3的组件开发

2010-05-15 by zhenn 发布在 web开发相关 | 标签:

从拔赤师兄的blog中看到的,对于我这样的YUI新手还是挺有意义的,特转载至此!

原文地址:http://www.uedmagazine.com/ued/comments.php?y=09&m=11&entry=entry091123-095015

悟透事件捕获和冒泡

2010-04-30 by zhenn 发布在 javascript | 标签: ,

对“捕获”和“冒泡”这两个概念,我想我们对冒泡更熟悉一些,因为在我们使用的所有浏览器中,都支持事件冒泡,即事件由子元素向祖先元素传播的,就像气泡从水底向水面上浮一样。而在像firefox,chrome,safari这类所谓的标准浏览器中,事件传播还有个阶段,那就是捕获阶段,这个很少有用武之地,所以被人疏忽遗忘也在所难免了,不常用不代表它不存在,本着科学严谨的态度,我们有必要去看一下它的庐山真面目。

事实上,捕获阶段是一个和冒泡阶段完全相反的过程,即事件由祖先元素向子元素传播,和一个石子儿从水面向水底下沉一样,要说明的是在IE,opera浏览器中,是不存在这个阶段的。从各浏览器提供的注册事件监听的方法中可见一斑,例如适用于ie,opera的attachEvent,有两个参数,attachEvent(”on”+type,fn),而适用于所谓标准浏览器的addEventListener则有三个参数,addEventListener(type,fn,boolean),前面两个参数不用解释,第三个参数boolean,就是决定注册事件发生在捕获阶段还是冒泡阶段,具体参考如下:

  • true : 捕获阶段
  • false : 冒泡阶段

话说,眼见为实,耳听为虚!那么好吧,请看demo,猛击这里

在这个demo中,注册事件监听默认发生在冒泡阶段,由于“元素a”,“元素b”和“元素c”有嵌套关系,为了更好的表现冒泡过程,所以请先点击“元素c”,会看到在各个浏览器中呈现的视觉效果是一致的,即事件是从c向a传播的,这也恰恰证明了所有浏览器都有冒泡这个过程的观点。经过上述的介绍,唯一让你感到吃惊的应该是,从视觉效果上,“元素c”并没有包含在“元素a”和“元素b”里面,而表现出来的效果确实证明”click”事件依次从c经过b传播到a的,反而包含“元素c”的“元素d”却没有响应click事件。事实上,这并不奇怪,因为事件冒泡和视觉上的布局结构是毫无关系的,这个冒泡过程仅仅依赖于dom元素的html结构(即嵌套关系)。效果见下图:

视觉效果

提及事件冒泡,那么不得不提“阻止事件冒泡”这个概念,因为我们最经常处理的任务就在于如何阻止事件的冒泡,来避免一些不必要的麻烦,关于这点,由于在之前的文章中单独讨论过,在此不再赘述,详见:阻止事件冒泡

言归正传,为了更好的看到捕获和冒泡的区别,那么这时候需要你把这个demo另存到本地,然后找到如下图所示的代码片段:

codes

接下来,你要做的就是,把这个代码片段中false改为true,也就是说让注册事件监听在捕获阶段进行,依次ctrl+s,在各个浏览器中打开这个页面,细心的你会发现,在firefox,chrome,safari中表现效果发生了变化(IE,opera中无变化,因为不存在捕获阶段),如下图:

捕获的视觉效果

对比两张图片所显示的视觉效果,应该足以证明本文开头所提出的观点了,如果针对事件捕获和冒泡尚有任何疑问或不解,请留言或者Email我,交流研究!

解决attachEvent中this指向问题

2010-04-29 by zhenn 发布在 javascript | 标签:

给dom元素绑定事件监听,很easy,即使刚入门的javascript编程人员都可以办到,比如obj.onclick = fn就可以很轻松的监听obj的单击事件,这固然是没有问题的,但是问题就在于在这个监听过程中,很难触发N个代码片段,例如:

  1. obj.onclick = fn1;
  2. obj.onclick = fn2;
  3. ...

事实上,如果这么做,fn2会把fn1覆盖,也就是说在单击obj时只会执行fn2中的代码片段,fn1则会被忽略,很显然这样不能满足我们的需求。在考虑到这个问题的时候,JS已经为我们准备了像attachEvnet、addEventListener这样的方法来满足我们的需求,虽然在不同级别的浏览器中方法不尽相同,利用对browser的能力检测还是可以轻松的实现一个兼容的方法:

  1. function bindEvent(elem,type,fn){
  2.     if(elem.attachEvent){
  3.         elem.attachEvent("on"+type,fn);
  4.     }else{
  5.         elem.addEventListener(type,fn,false);
  6.     }
  7. }
  8. //调用
  9. bindEvent(obj,"click",fn1);
  10. bindEvent(obj,"click",fn2);
  11. bindEvent(obj,"mouseover",function(){alert(this)})   //this->window

这样目的就达到了,当单击obj时,依次执行fn1、fn2。解决了这个问题后,细心的淫们会发现在执行的function中this执行是有问题的,始终指向window,又不是一个不小的麻烦。那么有没有办法,来解决这个棘手的问题呢,答案是肯定的,这正是我写这篇文章的意义所在,废话!
其实只需对bindEvent稍作加工即可,如下:

  1. function bindEvent(elem,type,fn){
  2.     if(elem.attachEvent){
  3.         elem.attachEvent("on"+type,function(){
  4.             fn.apply(elem,arguments);
  5.         });
  6.     }else{
  7.         elem.addEventListener(type,fn,false);
  8.     }
  9. }
  10. bindEvent(obj,"mouseover",function(){alert(this)})  //this->elem

理论上,这样的解决方案近乎完美了,但是还有一个问题亟待解决,因为在attachEvent中使用了匿名函数来执行fn,导致在无法使用detachEvent来删除绑定的某个代码片段,也许obj["on"+type] = null可以解决,清空绑定的代码片段,这很粗暴邪恶,同时也违背我们的业务需求。

我想要从根本上解决这些问题,就一定抛弃attachEvent这个方法,事实上我也是这样做的,如下:

  1. //绑定事件
  2. function bindEvent(elem,type,fn){
  3.     if(elem.attachEvent){
  4.         var typeRef = "_" + type;
  5.         if(!elem[typeRef]){
  6.             elem[typeRef] = [];
  7.         }
  8.         for(var i in elem[typeRef]){
  9.             if(elem[typeRef][i] == fn){
  10.                 return;
  11.             }
  12.         }
  13.         elem[typeRef].push(fn);
  14.         elem["on"+type] = function(){
  15.             for(var i in this[typeRef]){
  16.                 this[typeRef][i].apply(this,arguments);
  17.             }
  18.         }   
  19.     }else{
  20.         elem.addEventListener(type,fn,false);
  21.     }
  22. }
  23.  
  24. //移除事件绑定
  25. function removeEvent(elem,type,fn){
  26.     if(elem.detachEvent){
  27.         if(elem["_"+type]){
  28.             for(var i in elem["_"+type]){
  29.                 if(elem["_"+type][i] == fn){
  30.                     elem["_"+type].splice(i,1);
  31.                     break;
  32.                 }
  33.             }
  34.         }
  35.     }else{
  36.         elem.removeEventListener(type,fn,false);
  37.     }
  38. }

这段代码有点饶人,首先根据type类型给elem注册一个相关的属性_type,来存放elem同种类型事件要执行的代码片段,当然在这个过程中要先判断这个属性是否已经存在,如果存在,则跳过这一步。然后要遍历这个存放代码片段的数组,判断是否有和要添加的代码片段相同的代码片段,如果有,直接跳出function。假设在上一步条件不满足的情况下(即代码片段不重复),那么把这个代码片段push到相应的数组中。之后干的事情,我想大家应该很清楚了,没错,就是把相应数组里的代码片段依次apply到elem下执行。当然,这个过程只有在触发事件的时候,才会发生。

依照这个原理,那么要删除绑定的某个代码片段就很容易做到了,只需要从elem["_"+type]这个数组中把相应的代码片段删除即可。

解释一下为什么这个存放需要绑定代码片段的数组设置为elem的一个属性,其实理由很简单,就是不用声明一个全部变量(数组类型),来管理这个二维的数据结构,这样可以保证bindEvent,removeEvent的高度独立性。

最后,希望各位大淫能帮忙测试一下这两个方法的健壮性如何,是否还存在一些bug,如果有,提交给我,zhenn不胜感激之至!

巧用”&&”和”||”

2010-04-24 by zhenn 发布在 javascript | 标签:

对于&&和||运算符,相信大家都用过,分别进行逻辑与运算和逻辑或运算符。如果&&的第一个运算数是false,则不再考虑第二个运算数,直接返回false。如果||的第一个运算数是true,那么也不再考虑第二个运算数。

没有实践就没有发言权,demo如下:

  1. var a = true, b = false;
  2. function test(param){
  3.   alert(param);
  4. }
  5. a && test(1);
  6. b && test(2);
  7. a || test(3);
  8. b || test(4);

执行如上代码,事实上只执行了test(1)和test(4),原理如上所述。

a&&test(1)这种运算,其实我不知道是否有个专业的名词去描述它,在javascript森林群ToFishes同学称之为“惰性运算”,那么暂且叫它“惰性运算”吧,实际上相当于if(a){test(1)}。

其中||运算符,有个很重要的应用就是给function的参数设置默认值,如:

  1. function test(param){
  2.     param = {
  3.         name: param.name || "zhenn",
  4.         age: param.age || 25
  5.     }
  6.     alert(param.name + " is " + param.age + " years old.")
  7. }
  8. test({});

以上内容,针对刚入门javascript的童鞋们,大湿级的淫们可以直接skip.

值传递和地址传递

2010-03-31 by zhenn 发布在 javascript | 标签: ,

一个陷阱,引发一次深刻的思考,代码如下:

  1. var a = 11111;
  2. var b = {
  3.     pro: "test"
  4. }
  5. function fn(a,b){
  6.     a = 2222;
  7.     b.pro = "zhenn";   
  8. }
  9. fn(a,b);
  10. console.log(a)// 11111
  11. console.log(b)// b.pro = "zhenn"

在JavaScript中,除了Object,Array是地址引用,其他数据类型,诸如Number
、Boolean、String等都是值引用,这就是从陷阱中挖出的东西,同一个坑不能跳进去两次啊!杯具…

诡异的delete

2010-03-31 by zhenn 发布在 javascript | 标签:

delete这个操作符呢,在javascript中不是很常用,但是他的特性的确很诡异。

1,删除对象的属性,代码:

  1. var o = {
  2.     a: 1,
  3.     b: 2   
  4. };
  5. delete o.a;
  6. alert(o.a)//undefined

那么到底delete删除的是对象的属性还是对象的属性值呢,我开始觉得删除的应该是值,因为结果是undefined,而没有报错。但是事实上,我的看法是错误的,举例说明:

  1. var o = {};
  2. var a = { 
  3.     pro: "zhenn" 
  4. };
  5. o.c = a;
  6. delete o.c;    //删除对象o的属性a
  7. console.log(o.c);     // undefined
  8. console.log(a.pro);   // zhenn

通过上述代码,不难看出在delete o.c之后呢,并没有删除o.c所指向的值,也就是对象a依然存在,否则a.pro应该是过不了编译这关的。说到这里,可以这么理解delete删除对象的属性,其实相当于删除了对对象中属性值的引用而已,但是这个值依然在对象栈中!

2,对数组的操作,先看代码:

  1. var arr = [1,2,3];
  2. delete arr[2];
  3. console.log(arr.length)// 3
  4. console.log(arr);   // [1,2,undefined]

又一次证明了,delete并没有真正删除元素,只是删除了元素所对应的键值。为了更进一步认清delete的本质,和Array中的pop方法比较一下。如下:

  1. var arr = [1,2,3];
  2. arr.pop();
  3. console.log(arr)// [1,2]
  4. console.log(arr.length)  // 2

这下应该真相大白了。

3,以上对对象和数组的操作,还很好理解,但是对于变量的操作,难免让人琢磨不透,代码如下:

  1. var a = 1;
  2. delete a;
  3. alert(a); // 1
  4.  
  5. function fn(){ return 42; }
  6. delete fn;
  7. alert(fn())// 42
  8.  
  9. b = 2;
  10. delete b;
  11. alert(b)// b is not defined;

很难解释通啊,同样是全局变量,用var声明的竟然删除不了,而直接声明的变量b竟然可以删除,不能不说delete很诡异了,在ECMA给出的解释中,也仅仅是说通过var声明的变量和通过function声明的函数拥有DontDelete特性,无法被删除。