移動web:Tips消息彈出框

  在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)}
}
View Code

   樣式中有個屬性很重要,這可讓內容容器的文本不換行。

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選項,就是通常的回調了,在彈出框關閉後執行的函數。

  其中有不少能夠改進的地方,發揮想象,能夠作得更多。

相關文章
相關標籤/搜索