近来要做一个LightBox的效果(也有的叫Windows关机效果),不过不用那么复杂,能显示一个内容框就行了。 这个效果很久以前就做过,无非就是一个覆盖全屏的层,加一个内容显示的层。不过showbo教了我position:fixed这个新特性,决定重写一遍。
“定位效果”的意思是屏幕滚动也能固定位置。
程序说明: 要实现一个简单的LightBox效果,主要有两个部分:覆盖层和高亮层。
【跨浏览器的固定定位】
首先要先说说这个东西position:fixed,它的作用是跨浏览器的固定定位。
摘自详解定位与定位应用 : “如让一个元素可能随着网页的滚动而不断改变自己在浏览器的位置。而现在我可以通过CSS中的一个定位属性来实现这样的一个效果,这个元素属性就是曾经不被支持的position:fixed; 他的含义就是:固定定位。这个固定与绝对定位很像,唯一不同的是绝对定位是被固定在网页中的某一个位置,而固定定位则是固定在浏览器的视框位置。”
程序中很多地方利用了这个css,ie7、ff都支持这个css,但ie6不支持,程序中只能是在ie6模拟这个效果。 【覆盖层】
覆盖层的作用是把焦点限制在高亮层上,原理是通过一个绝对定位的层(通常使用div),设置它的宽度和高度以致能覆盖整个屏幕(包括缩放和滚动浏览器的情况下),再给它设置一个比较高的zIndex来层叠在原有内容之上(但要比高亮层小),这样用户就只能点到这个层之上的内容了。 如果初始化时没有提供覆盖层对象,程序中会自动创建:
this .Lay = $( this .options.Lay) || document.body.insertBefore(document.createElement( " div " ), document.body.childNodes[ 0 ]);
其中由于document.body.appendChild()导致IE已终止操作 bug,所以用了insertBefore。。
【覆盖屏幕】
覆盖层的关键就是如何做到覆盖整个屏幕(锁定整个页面),支持position:fixed的话很简单:
with ( this .Lay.style){ display = " none " ; zIndex = this .zIndex; left = top = 0 ; position = " fixed " ; width = height = " 100% " ; }
这样能把浏览器的视框覆盖了,其中使用了fixed样式,这里的意思是定位在浏览器的视框,并100%覆盖。 注意不要理解错为这个层覆盖了整个页面,它只是把浏览器可视的部分覆盖了来达到效果。 ie6不支持怎么办?有几个方法: 1,做一个覆盖视框的层,并在onscroll时相应移动,在onresize时重新设大小; 2,做一个覆盖视框的层,在样式上模拟fixed效果; 3,做一个层覆盖了整个页面的层,并在onresize时重新设大小; 方法1的缺点是滚动时很容易露出马脚,而且不好看;方法2的缺点是需要页面结构的改动和body样式的修改,绝对不是好的架构;而我用的是方法3,有更好的方法欢迎提出。
用这个方法只要把position设为absolute,并使用一个_resize方法来设置width和height即可:
this .Lay.style.position = " absolute " ; this ._resize = Bind( this , function (){ this .Lay.style.width = Math.max(document.documentElement.scrollWidth, document.documentElement.clientWidth) + " px " ; this .Lay.style.height = Math.max(document.documentElement.scrollHeight, document.documentElement.clientHeight) + " px " ; }); 要注意的是scrollHeight和clientHeight的区别(用Height容易测试),顺带还有offsetHeight,手册上的说明: scrollHeight:Retrieves the scrolling height of the object . clientHeight:Retrieves the height of the object including padding, but not including margin, border, or scroll bar. offsetHeight:Retrieves the height of the object relative to the layout or coordinate parent, as specified by the offsetParent property.
我的理解是: scrollHeight是对象的内容的高度; clientHeight是对象的可视部分高度; offsetHeight是clientHeight加上border和滚动条本身高度。
举个例子吧,先说说clientHeight和offsetHeight的区别(在ie7中测试):
测的是外面的div,offsetHeight和clientHeight相差17(分别是83和100),这个相差的就是那个滚动条本身的高度。
再看看clientHeight和scrollHeight的区别(下面是模拟在ie中的情况):
可以看到clientHeight不受内容影响,都是83,即内容有没有超过对象高度都不受影响,但scrollHeight会受内容高度影响,而且从测试可以意识到:
当有滚动条时,覆盖层的高度应该取scrollHeight(内容高度);当没有滚动条时,覆盖层的高度应该取clientHeight(视框高度)。
而恰好两个情况都是取两者中比较大的值,所以就有了以下程序:
Math.max(document.documentElement.scrollHeight, document.documentElement.clientHeight) + " px " ;
设宽度时是不包括滚动条部分的而documentElement一般也没有border,所以不需要offsetWidth。
上面可以看到我用的是documentElement而不是body,手册上是这样说的: Retrieves a reference to the root node of the document. 意思是整个文档的根节点,其实就是html节点(body的上一级),注意这是在XHTML的标准下。上面可以看到我们取值的对象是整个文档而不只是body,所以这里用documentElement。
要注意的是在window的onresize事件中scrollWidth和clientWidth的值会产生变化,程序中在onresize中使用_resize方法重新设置宽度高度:
if (isIE6){ this ._resize(); window.attachEvent( " onresize " , this ._resize); }
【覆盖select】
自定义的层给select遮挡住是一个老问题了,不过可喜的是ie7和ff都已经支持select的zIndex,只要给层设定高的zIndex就能覆盖select了,可惜对于ie6这个问题还是需要解决。
覆盖select据我所知有两个比较好的方法: 1,显示层时,先隐藏select,关闭层时再重新显示; 2,用一个iframe作为层的底,来遮住select。
方法1应该都明白,方法2就是利用iframe可以覆盖select的特性,只要把一个iframe作为层的底部就可以覆盖下面的select了,程序中是这样使用的:
this .Lay.innerHTML = ' <iframe style="position:absolute;top:0;left:0;width:100%;height:100%;filter:alpha(opacity=0);"></iframe> '
可以看出这个透明的iframe也以同样覆盖整个页面,如果是有内容显示的页面最好设置z-index:-1;确保iframe在层的底部。
个人觉得使用方法2比较好,但始终是改变了页面结构,有时会比较难控制,至于方法1就比较容易方便。
【高亮层】 高亮层就是用来显示内容的层,没什么看头,所以特意加了些效果在上面,吸引一下眼球。 有兴趣的话可以结合拖放效果 和渐变效果 ,做出更好的效果。
【固定定位】
这里“固定定位”的意思是当滚动滚动条时,高亮层依然保持在浏览器对应的位置上,把Fixed设为true就会开启这个效果。
同样对于支持fixed的浏览器很简单,只要把position设为fixed就行了,这个样式本来就是这样使用的,但可怜的ie6只能模拟了。
ie6模拟的原理是在onscroll事件中,不断根据滚动的距离修正top和left。 首先设置position为absolute,要注意的是position要在覆盖层显示之前显示,否则计算覆盖层宽高时会计算偏差(例如把页面撑大)。 再给onscroll事件添加定位函数_fixed来修正滚屏参数:
this .Fixed && window.attachEvent( " onscroll " , this ._fixed);
定位函数_fixed是这样的:
this ._fixed = Bind( this , function (){ this .Center ? this .SetCenter() : this .SetFixed(); });
可以看出在_fixed中,当设置了居中显示时会执行SetCenter程序(后面会说明),否则就执行SetFixed程序。 先说说SetFixed程序的原理,就是把当前scrollTop减去_top值(上一个scrollTop值)再加上当前的offsetTop,就得到要设置的top值了:
this .Box.style.top = document.documentElement.scrollTop - this ._top + this .Box.offsetTop + " px " ; this .Box.style.left = document.documentElement.scrollLeft - this ._left + this .Box.offsetLeft + " px " ; this ._top = document.documentElement.scrollTop; this ._left = document.documentElement.scrollLeft; 【居中显示】
“居中显示”的意思是高亮层位于视框左右上下居中的位置。 实现这个有两个方法: 1,视框宽度减去高亮层宽度的一半就是居中需要的left值; 2,先设置left值为50%,然后marginLeft设为负的高亮层宽度的一半。
方法1相对方法2需要多一个视框宽度,而且方法2在缩放浏览器时也能保持居中,明显方法2是更好,不过用margin会影响到left和top的计算,必须注意(例如SetFix修正的地方)。这里我选择了方法2,还要注意offsetWidth和offsetHeight需要在高亮层显示之后才能获取,所以定位程序需要放到高亮层显示之后:
this .Box.style.top = this .Box.style.left = " 50% " ; if ( this .Fixed){ this .Box.style.marginTop = - this .Box.offsetHeight / 2 + " px " ; this .Box.style.marginLeft = - this .Box.offsetWidth / 2 + " px " ; } else { this .SetCenter(); } 其中如果不是固定定位,需要用SetCenter程序来修正滚屏参数,SetCenter程序是这样的:
this .Box.style.marginTop = document.documentElement.scrollTop - this .Box.offsetHeight / 2 + " px " ; this .Box.style.marginLeft = document.documentElement.scrollLeft - this .Box.offsetWidth / 2 + " px " ; 【比较文档位置】
在ie6当不显示覆盖层时需要另外隐藏select,这里使用了“覆盖select”的方法1,值得留意的是这里加了个select是否在高亮层的判断:
this ._select.length = 0 ; Each(document.getElementsByTagName( " select " ), Bind( this , function (o){ if ( ! Contains( this .Box, o)){ o.style.visibility = " hidden " ; this ._select.push(o); } })) 其中Contains程序是这样的:
var Contains = function (a, b){ return a.contains ? a != b && a.contains(b) : !! (a.compareDocumentPosition(b) & 16 ); }
作用是返回a里面是否包含b,里面用到了两个函数,分别是ie的contains和ff(dom)的compareDocumentPosition。 其中contains手册里是这样写的: Checks whether the given element is contained within the object. 意思是检测所给对象是否包含在指定对象里面。注意如果所给对象就是指定对象本身也会返回true,虽然这样不太合理。 而ff的compareDocumentPosition功能更强大。
参考Comparing Document Position 看下表: 从NodeA.compareDocumentPosition(NodeB)返回的结果:
Bits Number Meaning 000000 0 Elements are identical. 000001 1 The nodes are in different documents (or one is outside of a document). 000010 2 Node B precedes Node A. 000100 4 Node A precedes Node B. 001000 8 Node B contains Node A. 010000 16 Node A contains Node B. 100000 32 For private use by the browser.
从这里可以看出NodeA.compareDocumentPosition(NodeB) & 16的意思是当第5位数是“1”时才返回16,也就是只有NodeA包含NodeB时返回16(&是位与运算)。
ps:为什么不直接a.compareDocumentPosition(b)
== 16,我也不清楚。 程序代码:
var isIE = (document.all) ? true : false ; var isIE6 = isIE && ([ / MSIE (\d)\.0 / i.exec(navigator.userAgent)][ 0 ][ 1 ] == 6 ); var $ = function (id) { return " string " == typeof id ? document.getElementById(id) : id; }; var Class = { create: function () { return function () { this .initialize.apply( this , arguments); } } } var Extend = function (destination, source) { for ( var property in source) { destination[property] = source[property]; } } var Bind = function (object, fun) { return function () { return fun.apply(object, arguments); } } var Each = function (list, fun){ for ( var i = 0 , len = list.length; i < len; i ++ ) { fun(list[i], i); } }; var Contains = function (a, b){ return a.contains ? a != b && a.contains(b) : !! (a.compareDocumentPosition(b) & 16 ); } var OverLay = Class.create(); OverLay.prototype = { initialize: function (options) { this .SetOptions(options); this .Lay = $( this .options.Lay) || document.body.insertBefore(document.createElement( " div " ), document.body.childNodes[ 0 ]); this .Color = this .options.Color; this .Opacity = parseInt( this .options.Opacity); this .zIndex = parseInt( this .options.zIndex); with ( this .Lay.style){ display = " none " ; zIndex = this .zIndex; left = top = 0 ; position = " fixed " ; width = height = " 100% " ; } if (isIE6){ this .Lay.style.position = " absolute " ; // ie6设置覆盖层大小程序 this ._resize = Bind( this , function (){ this .Lay.style.width = Math.max(document.documentElement.scrollWidth, document.documentElement.clientWidth) + " px " ; this .Lay.style.height = Math.max(document.documentElement.scrollHeight, document.documentElement.clientHeight) + " px " ; }); // 遮盖select this .Lay.innerHTML = ' <iframe style="position:absolute;top:0;left:0;width:100%;height:100%;filter:alpha(opacity=0);"></iframe> ' } }, // 设置默认属性 SetOptions: function (options) { this .options = { // 默认值 Lay: null , // 覆盖层对象 Color: " #fff " , // 背景色 Opacity: 50 , // 透明度(0-100) zIndex: 1000 // 层叠顺序 }; Extend( this .options, options || {}); }, // 显示 Show: function () { // 兼容ie6 if (isIE6){ this ._resize(); window.attachEvent( " onresize " , this ._resize); } // 设置样式 with ( this .Lay.style){ // 设置透明度 isIE ? filter = " alpha(opacity: " + this .Opacity + " ) " : opacity = this .Opacity / 100 ; backgroundColor = this .Color; display = " block " ; } }, // 关闭 Close: function () { this .Lay.style.display = " none " ; if (isIE6){ window.detachEvent( " onresize " , this ._resize); } } }; var LightBox = Class.create(); LightBox.prototype = { initialize: function (box, options) { this .Box = $(box); // 显示层 this .OverLay = new OverLay(options); // 覆盖层 this .SetOptions(options); this .Fixed = !! this .options.Fixed; this .Over = !! this .options.Over; this .Center = !! this .options.Center; this .onShow = this .options.onShow; this .Box.style.zIndex = this .OverLay.zIndex + 1 ; this .Box.style.display = " none " ; // 兼容ie6用的属性 if (isIE6){ this ._top = this ._left = 0 ; this ._select = []; this ._fixed = Bind( this , function (){ this .Center ? this .SetCenter() : this .SetFixed(); }); } }, // 设置默认属性 SetOptions: function (options) { this .options = { // 默认值 Over: true , // 是否显示覆盖层 Fixed: false , // 是否固定定位 Center: false , // 是否居中 onShow: function (){} // 显示时执行 }; Extend( this .options, options || {}); }, // 兼容ie6的固定定位程序 SetFixed: function (){ this .Box.style.top = document.documentElement.scrollTop - this ._top + this .Box.offsetTop + " px " ; this .Box.style.left = document.documentElement.scrollLeft - this ._left + this .Box.offsetLeft + " px " ; this ._top = document.documentElement.scrollTop; this ._left = document.documentElement.scrollLeft; }, // 兼容ie6的居中定位程序 SetCenter: function (){ this .Box.style.marginTop = document.documentElement.scrollTop - this .Box.offsetHeight / 2 + " px " ; this .Box.style.marginLeft = document.documentElement.scrollLeft - this .Box.offsetWidth / 2 + " px " ; }, // 显示 Show: function (options) { // 固定定位 this .Box.style.position = this .Fixed && ! isIE6 ? " fixed " : " absolute " ; // 覆盖层 this .Over && this .OverLay.Show(); this .Box.style.display = " block " ; // 居中 if ( this .Center){ this .Box.style.top = this .Box.style.left = " 50% " ; // 设置margin if ( this .Fixed){ this .Box.style.marginTop = - this .Box.offsetHeight / 2 + " px " ; this .Box.style.marginLeft = - this .Box.offsetWidth / 2 + " px " ; } else { this .SetCenter(); } } // 兼容ie6 if (isIE6){ if ( ! this .Over){ // 没有覆盖层ie6需要把不在Box上的select隐藏 this ._select.length = 0 ; Each(document.getElementsByTagName( " select " ), Bind( this , function (o){ if ( ! Contains( this .Box, o)){ o.style.visibility = " hidden " ; this ._select.push(o); } })) } // 设置显示位置 this .Center ? this .SetCenter() : this .Fixed && this .SetFixed(); // 设置定位 this .Fixed && window.attachEvent( " onscroll " , this ._fixed); } this .onShow(); }, // 关闭 Close: function () { this .Box.style.display = " none " ; this .OverLay.Close(); if (isIE6){ window.detachEvent( " onscroll " , this ._fixed); Each( this ._select, function (o){ o.style.visibility = " visible " ; }); } } }; 使用说明: 首先要有一个高亮层:
< style > .lightbox{ width : 300px ; background : #FFFFFF ; border : 1px solid #ccc ; line-height : 25px ; top : 20% ; left : 20% ; } .lightbox dt{ background : #f4f4f4 ; padding : 5px ; } </ style > < dl id ="idBox" class ="lightbox" > < dt id ="idBoxHead" >< b > LightBox </ b > </ dt > < dd > 内容显示 < br />< br /> < input name ="" type ="button" value =" 关闭 " id ="idBoxCancel" /> < br />< br /> </ dd > </ dl > 至于覆盖层一般不需要另外设了,接着就可以实例化一个LightBox:
var box = new LightBox( " idBox " );
打开和关闭LightBox分别是Show()和Close()方法,
其中LightBox有下面几个属性: 属性:默认值//说明 Over:true,//是否显示覆盖层 Fixed:false,//是否固定定位 Center:false,//是否居中 onShow:function(){}//显示时执行
还有OverLay属性是覆盖层对象,它也有几个属性: 属性:默认值//说明 Lay:null,//覆盖层对象 Color:"#fff",//背景色 Opacity:50,//透明度(0-100) zIndex:1000//层叠顺序
留言评论