在web開發中常常會用到像alert這樣的彈出消息,每一個瀏覽器自帶的消息彈出框都不相同。爲了統一外觀,實現自定義的功能,動手寫一個彈出框插件。javascript
對彈出框的實現要求以下:css
1. 仿照IOS系統彈出外觀html
2. 自定義按鈕文字java
3. 寬高自適應(有最小寬、最大寬限制)web
4. 實現彈出框關閉(前、後)的回調 (重點)因爲不少操做都是在彈出框顯示時操做的 ajax
先看下效果:瀏覽器
alert彈出(一個按鈕) app
ide
confirm彈出(兩個按鈕)函數
open彈出(沒有按鈕)
看完了效果,如今來實現這個消息彈出框。
先貼上代碼,僅供參考:
/** * LBS iTips (仿iPhone界面版) * Date: 2015-10-25 * ==================================================================== * 1. 寬高自適應(maxWidth:300 minWidth:250) 引入iTips.css iTips.js (建議js css 放在同一目錄) * ==================================================================== * 2. 調用方式1: Tips.alert(option) //顯示(肯定)按鈕 Tips.confirm(option) //顯示(肯定 取消)按鈕 option.after(boolean) boolean布爾值 肯定true 取消false Tips.open(option) //無顯示按鈕 可設置定時關閉 默認不自動關閉需手動關閉 Tips.close() //手動調用關閉 (方式1/方式2均可以調用) * Tips.show(text) // 顯示加載提示框 text爲彈出文本 默認加載中 * Tips.hide() // 隱藏加載提示框 * ==================================================================== * 3. 調用方式2: Tips.alert(content,fn) //content內容 fn彈出框關閉後執行函數 至關於option.after Tips.confirm(content,fn) //fn(boolean) boolean布爾值 肯定true 取消false Tips.open(content, time) //time自動關閉時間(單位秒) 默認不自動關閉需手動關閉 * ==================================================================== * 4. option選項: content:內容(可帶html標籤自定義樣式) before: 點擊肯定按鈕 關閉彈出框前 執行函數 (Tips.alert Tips.confirm中有效) 若是函數返回false 則不會執行(關閉彈出框)和(after) 通常用於作一些檢測 after: 點擊肯定按鈕 關閉彈出框後 執行函數 (Tips.alert Tips.confirm中有效) time: 自動關閉時間(單位秒) time 秒後關閉 (Tips.open中有效) define: 定義肯定按鈕的文本 (Tips.alert Tips.confirm中有效) cancel: 定義取消按鈕的文本 (Tips.confirm中有效) * ==================================================================== * Tips.BG //遮罩層 * Tips.Box //彈出框 * Tips.define //肯定按鈕 * Tips.cancel //取消按鈕 * ==================================================================== **/ ;(function() { window.Tips = { _create: function() { if (!this.Box) { var body = document.getElementsByTagName('body')[0], html = '<div id="tips_content"></div><div id="tips_foot"><a href="javascript:;" id="tips_cancel">取消</a><a href="javascript:;" id="tips_define">肯定</a></div>'; this.BG = document.createElement('div'); this.BG.id = 'tips_mask'; this.Box = document.createElement('div'); this.Box.id = 'tips_box'; this.Box.innerHTML = html; body.appendChild(this.BG); body.appendChild(this.Box); this.content = this.$('#tips_content'); this.foot = this.$('#tips_foot'); this.define = this.$('#tips_define'); this.cancel = this.$('#tips_cancel'); } }, minWidth: 250, maxWidth: 300, _show: function() { this._fix = true; this.BG.style.display = 'block'; this.Box.style.display = 'block'; this._css(); this._bind(); }, _hide: function() { this._fix = false; this.BG.style.display = 'none'; this.Box.style.display = 'none'; this._unbind(); }, _pos: function() { var d = document, doc = d.documentElement, body = d.body; this.pH = doc.scrollHeight || body.scrollHeight; this.sY = doc.scrollTop || body.scrollTop; this.wW = doc.clientWidth; this.wH = doc.clientHeight; if (document.compatMode != "CSS1Compat") { this.pH = body.scrollHeight; this.sY = body.scrollTop; this.wW = body.clientWidth; this.wH = body.clientHeight; } }, _css: function() { this._pos(); this.BG.style.height = Math.max(this.pH, this.wH) + 'px'; this.Box.style.width = 'auto'; this.content.style.cssText = 'float:left'; var cW = this.content.offsetWidth; this.content.style.cssText = ''; // width max:300 min:200 if (cW < this.minWidth) cW = this.minWidth; if (cW > this.maxWidth) { cW = this.maxWidth; // this.content.style.whiteSpace = ''; this.content.style.whiteSpace = 'normal'; } this.Box.style.width = cW + 'px'; // absolute // this.Box.style.left = (this.wW - cW) / 2 + 'px'; // this.Box.style.top = this.sY + (this.wH - this.Box.offsetHeight) / 2 + 'px'; // fixed 1 // this.Box.style.marginLeft = -(cW / 2) + 'px'; // this.Box.style.marginTop = -(this.Box.offsetHeight / 2) + 'px'; // fixed 2 this.Box.style.marginLeft = -(cW / 2) + 'px'; this.Box.style.top = (this.wH - this.Box.offsetHeight) / 2 + 'px'; }, _fixSize: function() { var _this = this, time = +new Date(); this._timeid && clearInterval(this._timeid); this._timeid = setInterval(function() { if (+new Date() - time > 1000) { clearInterval(_this._timeid); _this._timeid = null; return false; } _this._css(); }, 250); }, _define: function(option) { var _this = this; this.define.onclick = function(e) { e.stopPropagation(); if (typeof option === 'function') { _this._hide(); _this.Bool = true; option && option(_this.Bool); return; } var before = option.before && option.before(); var bool = false; before === false && (bool = true); if (bool) { e.stopPropagation(); return false; } _this._hide(); _this.Bool = true; option.after && option.after(_this.Bool); }; }, _cancel: function(option) { var _this = this; this.cancel.onclick = function(e) { e.stopPropagation(); _this._hide(); _this.Bool = false; if (typeof option === 'function') { option && option(_this.Bool); return; } option.after && option.after(_this.Bool); }; }, _bind: function() { this.Box.focus(); this._setEvent(); }, _unbind: function() { this.Box.blur(); this.define.onclick = null; this.cancel.onclick = null; this.define.innerText = '肯定'; this.cancel.innerText = '取消'; this._timer && clearTimeout(this._timer); this._timer = null; this._timeid && clearInterval(this._timeid); this._timeid = null; }, _setEvent: function() { var _this = this; this.on(this.BG, 'touchmove', function(e) { e.preventDefault(); }); this.on(this.Box, 'touchmove', function(e) { e.preventDefault(); }); this.on(this.define, 'touchstart', function(e) { _this.define.className.indexOf('tips_hover') < 0 && (_this.define.className += ' tips_hover'); }); this.on(this.define, 'touchend', function(e) { _this.define.className = _this.define.className.replace('tips_hover', '').trim(); }); this.on(this.cancel, 'touchstart', function(e) { _this.cancel.className.indexOf('tips_hover') < 0 && (_this.cancel.className += ' tips_hover'); }); this.on(this.cancel, 'touchend', function(e) { _this.cancel.className = _this.cancel.className.replace('tips_hover', '').trim(); }); this.on(window, 'resize', function(e) { if (!_this._fix) return; _this._fixSize(); }); }, _setBtn: function(n, option) { this.foot.style.display = 'block'; this.define.style.display = ''; this.cancel.style.display = ''; switch (parseInt(n)) { case 1: this.define.className = 'tips_define'; this.cancel.style.display = 'none'; if (typeof option === 'function') { this.define.innerText = '肯定'; this._define(function() { option && option(); }); } else { this.define.innerText = option.define || '肯定'; this._define(option); } break; case 2: this.define.className = ''; if (typeof option === 'function') { this.define.innerText = '肯定'; this.cancel.innerText = '取消'; this._define(function(b) { option && option(b); }); this._cancel(function(b) { option && option(b); }); } else { this.define.innerText = option.define || '肯定'; this.cancel.innerText = option.cancel || '取消'; this._define(option); this._cancel(option); } break; case 0: this.foot.style.display = 'none'; this.define.style.display = 'none'; this.cancel.style.display = 'none'; break; } }, _setContent: function(html) { this.content.innerHTML = html+''; }, _setOption: function(option, n, fn) { var content = ''; this._create(); if (typeof option === 'string' || typeof option === 'number') { content = option || ''; this._setBtn(n, function(b) { fn && fn(b); }); } else { option = option || {}, content = option.content || ''; this._setBtn(n, option); } this._setContent(content); this._show(); }, _setTime: function(option, t) { var time = 0, _this = this; time = (typeof option === 'string' ? t : option.time); if (parseInt(time) > 0) { this._timer = setTimeout(function() { _this._hide(); }, time * 1000); } }, on: function(el, type, handler) { el.addEventListener(type, handler, false); }, off: function(el, type, handler) { el.removeEventListener(type, handler, false); }, $: function(s) { return document.querySelector(s); }, alert: function(option, fn) { this._setOption(option, 1, fn); }, confirm: function(option, fn) { this._setOption(option, 2, function(b) { fn && fn(b); }); }, open: function(option, t) { this._setOption(option, 0); this._setTime(option, t); }, close: function() { this._hide(); } }; }());
第一步建立彈出框基本的html標籤,並設定樣式。
_create: function() { if (!this.Box) { var body = document.getElementsByTagName('body')[0], html = '<div id="tips_content"></div><div id="tips_foot"><a href="javascript:;" id="tips_cancel">取消</a><a href="javascript:;" id="tips_define">肯定</a></div>'; this.BG = document.createElement('div'); this.BG.id = 'tips_mask'; this.Box = document.createElement('div'); this.Box.id = 'tips_box'; this.Box.innerHTML = html; body.appendChild(this.BG); body.appendChild(this.Box); this.content = this.$('#tips_content'); this.foot = this.$('#tips_foot'); this.define = this.$('#tips_define'); this.cancel = this.$('#tips_cancel'); } }
很簡單的一個標籤佈局結構:
<div id="tips_mask"></div> <div id="tips_box"> <div id="tips_content"></div> <div id="tips_foot"> <a href="javascript:;" id="tips_cancel">取消</a> <a href="javascript:;" id="tips_define">肯定</a> </div> </div>
相關樣式:
#tips_mask, #tips_box *{margin:0;padding:0; -webkit-tap-highlight-color: rgba(0,0,0,0); color: #333; } /* absolute */ /* #tips_mask{display:none;position:absolute;top:0;left:0;z-index:99999;width:100%;height: 100%; background-color:rgba(0,0,0,.3);} #tips_box{display:none;position:absolute; z-index:100000; background:#FFF; font-family: Helvetica,arial,sans-serif; border-radius:10px; animation: bounceIn 0.2s both; -webkit-animation: bounceIn 0.2s both;} */ /* fixed 1*/ /* #tips_mask{display:none;position:fixed;top:0;left:0;z-index:99999;width:100%;height: 100%; background-color:rgba(0,0,0,.3);} #tips_box{display:none;position:fixed; left: 50%; top:50%; z-index:100000; background:#FFF; font-family: Helvetica,arial,sans-serif; border-radius:10px; animation: bounceIn 0.2s both; -webkit-animation: bounceIn 0.2s both;} */ /* fixed 2*/ #tips_mask{display:none;position:fixed;top:0;left:0;z-index:99999;width:100%;height: 100%; background-color:rgba(0,0,0,.5);} #tips_box{display:none;position:fixed; left: 50%; top:0; z-index:100000; background:#FFF; font-family: Helvetica,arial,sans-serif; border-radius:10px; animation: bounceIn 0.2s both; -webkit-animation: bounceIn 0.2s both;} #tips_content{padding:25px 20px; text-align:center; overflow: hidden; font-size:16px; line-height:1.5; white-space: nowrap; /* 不換行 */ border-radius:10px 10px 0 0; } #tips_foot{position:relative; overflow: hidden; height:48px; font-size: 0; line-height:48px; text-align:center; border-top:1px solid #EBEBEB; border-radius:0 0 10px 10px;} #tips_foot a{position:relative; display:inline-block; width:50%; text-align:center; font-size:18px; text-decoration: none; outline: none; color: #0FA5F5; } #tips_foot a:first-child{border-radius:0 0 0 10px; } #tips_foot a:last-child{ border-radius:0 0 10px 0; } #tips_foot a.tips_define{ width: 100%; background: #FFF; border-radius:0 0 10px 10px;} #tips_foot a.tips_hover{ background: #e8e8e8; } #tips_foot:before{content:'';position:absolute; width:1px; height:48px; margin-left: -1px; left:50%; top:0; background:#EBEBEB;} -webkit-keyframes bounceIn{ 0%{opacity:0;-webkit-transform:scale(.5);transform:scale(.5)} 100%{opacity:1;-webkit-transform:scale(1);transform:scale(1)} } @keyframes bounceIn{ 0%{opacity:0;-webkit-transform:scale(.5);transform:scale(.5)} 100%{opacity:1;-webkit-transform:scale(1);transform:scale(1)} }
樣式中有個屬性很重要,這可讓內容容器的文本不換行。
white-space: nowrap;
在PC端版本時不限制寬度頗有用,移動端因爲設置了最大寬度,當內容超過最大寬度時,設置自動換行。
if (cW > this.maxWidth) { this.content.style.whiteSpace = 'normal'; }
第二步設定內容,並更新位置。
從一個簡單的alert彈出消息開始:
// 調用方式1 Tips.alert({ content: 'alert彈出一條消息' }); // 調用方式2 Tips.alert('alert彈出一條消息');
這個彈出通過一個怎樣的流程呢?
1. 把內容賦給內容容器
_setContent: function(html) { this.content.innerHTML = html+''; }
2. 修正定位
先獲取內容容器的寬度
this.content.style.cssText = 'float:left'; var cW = this.content.offsetWidth; this.content.style.cssText = '';
這裏採起把內容容器設爲浮動,而後再取消浮動,這種方式得到的寬度是比較實際的,有多少內容就有多少寬,PC端版本採起這種方式也會全兼容。(自適應寬必定要設置內容不換行)
而後是定位,讓彈出框在瀏覽器窗口中垂直居中。
彈出框定位也有幾種方式,絕對定位,固定定位。
絕對定位:瀏覽器滾動條的滾動的距離(scrollTop) 瀏覽器窗口可視的寬高(clientWidth clientHeight) 彈出框的高(offsetHeight)
固定定位:瀏覽器窗口可視的寬高(clientWidth clientHeight) 彈出框的高(offsetHeight) (彈出框的寬是內容容器的寬)
// ... this.Box.style.width = 'auto'; // ... this.Box.style.width = cW + 'px'; // absolute // this.Box.style.left = (this.wW - cW) / 2 + 'px'; // this.Box.style.top = this.sY + (this.wH - this.Box.offsetHeight) / 2 + 'px'; // fixed 1 // this.Box.style.marginLeft = -(cW / 2) + 'px'; // this.Box.style.marginTop = -(this.Box.offsetHeight / 2) + 'px'; // fixed 2 this.Box.style.marginLeft = -(cW / 2) + 'px'; this.Box.style.top = (this.wH - this.Box.offsetHeight) / 2 + 'px';
第三步設定按鈕,並綁定事件。
這個彈出框插件設定了三種彈出方法,兩種調用方式(方法名稱能夠改爲本身想要的名稱)。
調用方式1: Tips.alert(option) //顯示(肯定)按鈕 Tips.confirm(option) //顯示(肯定 取消)按鈕 Tips.open(option) //無顯示按鈕 調用方式2: Tips.alert(content,fn) Tips.confirm(content,fn) Tips.open(content, time)
設置按鈕顯示和隱藏
_setBtn: function(n, option) { this.foot.style.display = 'block'; this.define.style.display = ''; this.cancel.style.display = ''; switch (parseInt(n)) { case 1: // ... this.cancel.style.display = 'none'; // ... break; case 2: // ... break; case 0: this.foot.style.display = 'none'; this.define.style.display = 'none'; this.cancel.style.display = 'none'; break; } }
設置按鈕事件
_define: function(option) { var _this = this; this.define.onclick = function(e) { e.stopPropagation(); if (typeof option === 'function') { _this._hide(); _this.Bool = true; option && option(_this.Bool); return; } var before = option.before && option.before(); var bool = false; before === false && (bool = true); if (bool) { e.stopPropagation(); return false; } _this._hide(); _this.Bool = true; option.after && option.after(_this.Bool); }; }, _cancel: function(option) { var _this = this; this.cancel.onclick = function(e) { e.stopPropagation(); _this._hide(); _this.Bool = false; if (typeof option === 'function') { option && option(_this.Bool); return; } option.after && option.after(_this.Bool); }; }
這裏綁定的事件在彈出框顯示時有效,彈出框隱藏時會把事件註銷掉, 事件綁定和解綁用了偷懶的寫法。
xx.onclick = function(){}; xx.onclick = null;
定義好綁定事件的方法,在設置按鈕時調用。
_setBtn: function(n, option) { // ... switch (parseInt(n)) { case 1: // ... if (typeof option === 'function') { this.define.innerText = '肯定'; this._define(function() { option && option(); }); } else { this.define.innerText = option.define || '肯定'; this._define(option); } break; case 2: // ... if (typeof option === 'function') { this.define.innerText = '肯定'; this.cancel.innerText = '取消'; this._define(function(b) { option && option(b); }); this._cancel(function(b) { option && option(b); }); } else { this.define.innerText = option.define || '肯定'; this.cancel.innerText = option.cancel || '取消'; this._define(option); this._cancel(option); } break; case 0: // ... break; } }
到此彈出框已經基本完成:建立相應的html標籤結構,設置樣式;根據要彈出的內容更正位置;對按鈕綁定事件。
如今看看頂部的 confirm彈出: 購買確認 confirm彈出: 領取紅包 這2個彈出是怎樣調用的(其實,寫這個插件主要目的就是爲了實現這2種的調用)。
購買確認:
Tips.$('#alertBtn_06').onclick = function(e){ var price = 100; //單價 var quantity = 500; // 數量 var total = price * quantity; // 總額 Tips.confirm({ content: '<h3>肯定要購買嗎?</h3><p>您這次購買份數爲<strong>'+ quantity +'</strong>份,每份價格<strong>'+ price +'</strong>元,總需<strong>'+ total +'</strong>元</p><div><input type="checkbox" checked="checked" id="isread" name="isread"><label for="isread">我已閱讀並贊成</label> <a href="http://m.jd.com/" class="a_gmxy">《購買協議》</a><p id="iserror"></p></div>', define: '肯定購買', cancel: '不買了', before: function(){ Tips.$('#iserror').innerText = ''; if(!Tips.$('#isread').checked ){ Tips.$('#iserror').innerText = '你尚未贊成購買協議'; return false; // 返回false 阻止彈出框關閉 也不會執行after } }, after: function(b){ if(b){ Tips.show('正在處理中'); // 開啓加載框 // 在此能夠發送ajax請求 // 模擬ajax setTimeout(function(){ Tips.hide(); // ajax請求成功後 關閉加載框 // ajax請求成功後 彈出其餘消息 Tips.alert('<h3>恭喜您,購買成功</h3>',function(){ Tips.alert({ define: '知道了,我會跳轉的', content: '跳轉到交易記錄頁面' }); // location.href = 'http://m.jd.com/'; }); },1000); } } }); };
領取紅包:
Tips.$('#alertBtn_07').onclick = function(e){ // 調用方式1 Tips.confirm({ content: '<h3>輸入口令領取紅包</h3><div><input type="text" id="str_code" placeholder="請輸入口令" style="height:40px;" /><p id="str_error"><p></div>', before: function(){ if( !/^\d{6}$/.test(Tips.$('#str_code').value) ){ Tips.$('#str_error').innerHTML = '口令爲6位數字'; Tips.$('#str_code').focus(); return false; } // 發送ajax Tips.show('正在處理中'); //1 顯示加載框 // $.ajax({ }); //2 發送ajax請求 // Tips.hide(); // 3 ajax返回結果 隱藏加載框 // Tips.alert('恭喜你,得到<strong>100</strong>元紅包!'); //3-1 提示領取成功 // Tips.$('#str_error').innerHTML = '口令有誤,請從新輸入'; // 3-2 提示領取失敗 // 模擬領取成功 setTimeout(function(){ Tips.hide(); Tips.alert('恭喜你,得到<strong>100</strong>元紅包!'); },1500); // 模擬領取失敗 // setTimeout(function(){ // Tips.hide(); // Tips.$('#str_error').innerHTML = '口令有誤,請從新輸入'; // },1500); return false; // 始終返回fasle 不讓彈出框關閉 } }); // 調用方式2 不支持關閉前 before 執行 };
這兩種調用都用到了 before 選項,這個是在點擊肯定按鈕時、關閉隱藏彈出框前執行函數,代碼中定義以下:
_define: function(option) { var _this = this; this.define.onclick = function(e) { e.stopPropagation(); if (typeof option === 'function') { _this._hide(); _this.Bool = true; option && option(_this.Bool); return; } var before = option.before && option.before(); var bool = false; before === false && (bool = true); if (bool) { e.stopPropagation(); return false; } _this._hide(); _this.Bool = true; option.after && option.after(_this.Bool); }; }
一個函數執行時,假如沒有返回值,通常默認爲undefined.
function aa(){} var a_bool = aa(); alert(a_bool) // undefined function bb(){ return false; } var b_bool = bb(); alert(b_bool) // false
利用這一點實現了before選項的功能。
var before = option.before && option.before(); var bool = false; before === false && (bool = true); if (bool) { e.stopPropagation(); return false; }
after選項,就是通常的回調了,在彈出框關閉後執行的函數。
其中有不少能夠改進的地方,發揮想象,能夠作得更多。