一步步教你實現彈出窗口(第2部分)
上部分已給出主要輔助方法css了,有了它我們就可以實現類的實例的樣式共享。另外,我們的類的實現模式是基于prototype,這樣就實現方法共享。現在我們來看看如何渲染它,首先呈上它大體的結構層代碼:
<div id="" class="popups" >
<div class="caption"></div>
<form>
<div class="replaceable"></div>
<div class="submitable">
<a class="negative" href="javascript:void(0)">取消</a>
<a class="positive" href="javascript:void(0)">確認</a>
</div>
</form>
<a class="closebtn" href="javascript:void(0)"></a>
</div>
這結構層代碼比騰訊那個簡單得多了,而且沒用到table來布局,全憑CSS來處理。雖然元素很少,但如果用W3C那一套操作DOM的API來干活,也要十多行。我們不想一味地createElement然后appendChild的話,就要派innerHTML上場了。一個常識是,當DOM超過十個時,就要考察用字符串拼接實現,這樣代碼看起來也直觀一點。我們再來分析一下這結構層。頂層是一個DIV,不用說,它是彈出層,其他東西都直接構筑在它的上面。考察到一個頁面可能存在多個動態生成的彈出窗口,因此我賦給他們一個獨一無二的標識,也就是ID。ID在我的樣式藍圖中沒有用,要實現共享就要用class,我給它一個popups值。接著是標題欄,看上去空蕩蕩,然后它有一個文本節點,與一張背景圖片做icon,還有一個關閉按鈕(我把它放到最底層了class="closebtn")。這里要用絕對定位。然后是一個表單,表單分兩部分,一是替換區(replaceable),為什么這樣叫?因為它視alert,prompt,confirm等功能而重寫。一個是提交區,有兩個按鈕。你們可能奇怪了,為什么不用語義化更強、制定性更好的button標簽來實現呢?這都怪IE6在拖后腿,只有帶href屬性的a標簽才支持hover偽類。之所以用hover偽類,是因為我不想在它們上面多綁定兩個事件(mouseover與mouseout)。而為什么確認按鈕放在取消按鈕之后呢?因為我們稍后要用到向右浮動。整個提交區是位于form的底部的,這個我們可以用bottom:0來實現。
Dialog.prototype = { constructor: Dialog, init: function() { var container = this.container,width = this.width, height = this.height, id = this.id,builder = [], document.body.insertBefore(container,null); container.id = id; container.className = "popups"; builder.push(' '+this.title+' '); builder.push(' '); builder.push(' '); builder.push('取消'); builder.push('確認'); builder.push(' '); builder.push(''); container.innerHTML = builder.join(''); } }
這樣就創建完成了。我們再來看表現層。要做得好看,得花一些功夫。自已用IE8開發人員工具看生成后的CSS代碼吧。它已經使用到CCS3的圓角特征(這在IE8中看不到)。有的人想去掉按下按鈕時出現的虛線框,這里給出一個簡捷方法:outline:0。
接著看如何實現圓角與半透明效果與盒陰影。如果不考慮IE與Opera,只需要多添加三行代碼:
this.css(".popups","background:rgba(104,223,251,.8)");
this.css(".popups","-moz-border-radius:5px;-moz-box-shadow:10px 10px 5px #c0c0c0;");
this.css(".popups","-webkit-border-radius:5px;-webkit-box-shadow:10px 10px 5px #c0c0c0;");
不過要實現兼容IE也不難,就是利用VML生成一個圓角矩形,然后利用二級標記fill與shadow輕易實現上述功能。由于VML元素與HTML元素是位于同一個層的,因此我們插入這些VML時肯定會影響原來的文檔流,因此我的彈出層的大多數對象都是定位元素,這樣誰都影響不了誰。標準瀏覽器就有點麻煩了,本來我費了很大勁用canvas實現圓角矩形,但回頭發現它不住陰影fillShadow等方法。于是改用SVG。我們可以比較一下動態生成后VML與SVG的代碼。
//利用canvas實現圓角矩形 var canvas = document.createElement("canvas"); container.insertBefore(canvas,null); this.attr(canvas,{width:width,height:height,className:"canvas"}); this.css("#"+this.id +" canvas" ,"position:absolute;"); if(canvas.getContext) { var ctx = canvas.getContext('2d'); ctx.fillStyle = "rgba(104,223,251,.5)"; roundedRect(ctx,0, 0, width, height,5); ctx.shadowColor = '#00f'; ctx.shadowOffsetX = 16; ctx.shadowOffsetY = 16; ctx.shadowBlur = 8; ctx.shadowColor = 'rgba(0, 0, 255, 0.25)'; function roundedRect(ctx,x,y,width,height,radius){ ctx.beginPath(); ctx.moveTo(x,y+radius); ctx.lineTo(x,y+height-radius); ctx.quadraticCurveTo(x,y+height,x+radius,y+height); ctx.lineTo(x+width-radius,y+height); ctx.quadraticCurveTo(x+width,y+height,x+width,y+height-radius); ctx.lineTo(x+width,y+radius); ctx.quadraticCurveTo(x+width,y,x+width-radius,y); ctx.lineTo(x+radius,y); ctx.quadraticCurveTo(x,y,x,y+radius); ctx.fill(); } }
<vml:roundrect class="vml" style="position: absolute;width:400px; height:300px;top:0px;left:0px;"> <vml:fill class="vml" opacity="0.8" color="#68DFFB" /> <vml:shadow class="vml" on="t" color="#333" opacity="0.2" offset="10px,10px" /> </vml:roundrect>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" width="410px" height="310px"> <defs> <filter id="drop-shadow"> <feGaussianBlur in="SourceAlpha" result="blur-out" stdDeviation="1.5" /> <feOffset in="blur-out" result="the-shadow" dx="0" dy="2" /> <feBlend in="SourceGraphic" in2="the-shadow" mode="normal" /> </filter> </defs> <rect x="10px" y="10px" width="400px" height="300px" rx="5" fill="#333" style="opacity:0.2" filter="url(#drop-shadow)"/> <rect width="400px" height="300px" rx="5" fill="#68DFFB" style="opacity:0.8" /> </svg>
接著下來就是綁定事件了,字符串拼接有一個不好處,要獲取剛剛生成的DOM對象的引用比較麻煩。不過我打算利用事件代理就另當別論了,因為我們手頭上最明確的對象就是那個DIV元素,我們把所有事件都綁定在它上面就是了。這個留在下次說。