近來要做一個LightBox的效果(也有的叫Windows關機效果),不過不用那麼複雜,能顯示一個内容框就行了。
這個效果很久以前就做過,無非就是一個覆寫全屏的層,加一個内容顯示的層。不過showbo教了我position:fixed這個新特性,決定重寫一遍。
先看效果:
覆寫select測試
ps:“定位效果”的意思是螢幕滾動也能固定位置。
程式說明:
要實作一個簡單的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]);
【覆寫螢幕】
覆寫層的關鍵就是如何做到覆寫整個螢幕(鎖定整個頁面),支援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功能更強大。
從NodeA.compareDocumentPosition(NodeB)傳回的結果:
Bits
Number
Meaning
000000
Elements are identical.
000001
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 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.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用的屬性
this._top = this._left = 0; this._select = [];
this._fixed = Bind(this, function(){ this.Center ? this.SetCenter() : this.SetFixed(); });
Over: true,//是否顯示覆寫層
Fixed: false,//是否固定定位
Center: false,//是否居中
onShow: function(){}//顯示時執行
//相容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();
}
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();
this.OverLay.Close();
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" />
</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//層疊順序
<a href="http://files.cnblogs.com/cloudgamer/LightBox.rar">完整執行個體下載下傳</a>