zoukankan      html  css  js  c++  java
  • 移动web:Tips消息弹出框

      在web开发中经常会用到像alert这样的弹出消息,每个浏览器自带的消息弹出框都不相同。为了统一外观,实现自定义的功能,动手写一个弹出框插件。

      对弹出框的实现要求如下:

      1. 仿照IOS系统弹出外观

      2. 自定义按钮文字

      3. 宽高自适应(有最小宽、最大宽限制)

      4. 实现弹出框关闭(前、后)的回调 (重点)由于很多操作都是在弹出框显示时操作的 

      先看下效果:

      alert弹出(一个按钮)   

      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;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;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选项,就是一般的回调了,在弹出框关闭后执行的函数。

      其中有很多可以改进的地方,发挥想象,可以做得更多。

      

  • 相关阅读:
    俄罗斯方块源码解析(带下载)[2]
    gridView基本操作
    俄罗斯方块源码解析 系列 更新
    俄罗斯方块源码解析(带下载)[1]
    《CLR Via C#》 学习心得一 CLR基础知识
    《CLR Via C#》 学习心得之二 类型基础
    《CLR Via C#》 学习心得之三 基元类型、引用类型和值类型
    观《大话设计模式》之——简单工厂模式
    观Fish Li《细说 Form (表单)》有感
    总结做了八个月asp.net程序员
  • 原文地址:https://www.cnblogs.com/eyeear/p/4987769.html
Copyright © 2011-2022 走看看