【vue動(dòng)態(tài)加載組件】 用render寫一個(gè)popup.js組件

定義方法調(diào)用格式

我發(fā)現(xiàn)在寫項(xiàng)目的時(shí)候航攒,經(jīng)常會(huì)用到 alertconfirm让簿、toast之類的彈出框敬察。但是原生alert對(duì)話框的樣式已經(jīng)定型,和UI設(shè)計(jì)又不一樣拜英,再加上彈出面板真的出現(xiàn)頻率太多了,于是自己動(dòng)手寫了一個(gè)動(dòng)態(tài)彈窗組件琅催。

首先居凶,我先定義了一下,假設(shè)已有此組件藤抡,我要怎么調(diào)用:

    popup({
          type:'alert',
          content: '這是一個(gè)警告框侠碧!'
    })

于是就有了大概想法,首先這個(gè)組件是通過一個(gè)方法調(diào)用的缠黍,其次弄兜,這個(gè)方法得有一個(gè)對(duì)象作為自己的參數(shù),對(duì)象中的type用來定義當(dāng)前面板的類型瓷式。

經(jīng)過一系列測(cè)試后替饿,最終定下來以下類型,并列出此popup應(yīng)該有的功能

/**
* alert  confirm  prompt  toast slot
*/
  1. 默認(rèn)居中贸典、有根元素傳入時(shí)视卢,按照根元素的位置進(jìn)行定位
  2. 可以設(shè)置彈出面板大小及其他樣式
  3. 可以設(shè)置面板css類,修改蒙版的不透明度
  4. 具有confirm和close函數(shù)回調(diào)
  5. 面板內(nèi)容可以自定義廊驼,slot模式時(shí)据过, 可以選擇內(nèi)嵌 組件或者h(yuǎn)tml元素
  6. 當(dāng)面板的自定義內(nèi)容為html元素時(shí),支持綁定事件
調(diào)用confirm
popup({
    type: 'confirm',
    content: '你確定要?jiǎng)h除我嗎妒挎?',
    confirm: function() {
        // TODO
    },
    close: function() {
        // TODO
    }
})
調(diào)用prompt
popup({
    type: 'prompt',
    content: '請(qǐng)輸入內(nèi)容:',
    confirm: function(str) {
        // TODO
        console.log(str);
    },
    close: function() {
        // TODO
    }
})
調(diào)用toast
popup({
    type: 'toast',
    content: '我是一個(gè)定時(shí)提示框绳锅!',
    timeout: 3000  // 不傳 timeout時(shí),默認(rèn)時(shí)長(zhǎng)2秒
})
調(diào)用slot 自定義面板
popup({
    type: 'slot',  // 自定義組件模式
    slot: {
        name: 'myComponent',
        url: 'views/components/myComponent.vue'   //此項(xiàng)可選酝掩,有時(shí)將自動(dòng)加載為全局組件
    }
})

popup({
    type: 'slot', // html模式
    html: true,
    innerHTML : `
    <div>
          <p>這是我定義的html內(nèi)容·</p>
          <button @click="logMsg">綁定一個(gè)方法</button>
    </div>
    `,
    methods: {
          logMsg: function(){
              console.log('this is a event')
          }
    }
})

大概定的調(diào)用方式就是上面這樣了鳞芙,但是除此外還需要幾個(gè)屬性來修飾一下:

popup({
    type: 'alert',
    content: '這是一個(gè)警告框'
    root: document.querySelector('.msg'),  // 面板按照此元素在頁面中的位置進(jìn)行定位
    opacity: 0.1,  // 蒙版的不透明度,當(dāng)為0 時(shí) 完全透明期虾,為1時(shí)背景全黑
    style: {  // 此屬性的值將作用于面板上
        'width': '200px',
        'height': '100px'
    },
    css: ['change1','change2']   //此屬性接收一個(gè)數(shù)組积蜻,數(shù)組值作為class類綁定到面板上,這個(gè)是為了寫復(fù)雜的樣式時(shí)彻消,避免寫過長(zhǎng)的style屬性準(zhǔn)備的
})

莽代碼

先來定義一個(gè)大概代碼框架


(function(global, factory){
    typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
    typeof define === 'function' && define.amd ? define(factory) :
    (global = global || self, global.popup = factory());
})(this,function(){
    /* 以下我們定義了一個(gè)封閉作用域用來編寫我們的popup函數(shù)需要用到的其他變量 判斷和 處理的函數(shù) */   
    let [template, popupData] = ['', {}];
    const [_toString, doc] = [Object.prototype.toString, document.documentElement];

    // 定義工具函數(shù)竿拆,留作判斷備用
    function _isObj(obj){
        return _toString.call(obj).slice(8,-1) === 'Object';
    };
    
    function _isArr(obj){
        return _toString.call(obj).slice(8,-1) === 'Array';
    };
    
    function _isEle(ele){
        return _toString.call(ele).slice(8,12) === 'HTML';
    };
    
    function _isFun(fun){
        return typeof fun === 'function';
    };
    
    function _isErr(err){
        return _toString.call(err).slice(8,-1) === 'Error';
    };
      
    let methods = {
      location: function(ele) {
        // 計(jì)算傳入element元素在頁面上的位置
        ...
      },
      initpopupData: function(options){
        ...
      },
      template: function(type,options){
        // 通過type 創(chuàng)建對(duì)應(yīng)的 template div
        if(type){
            ...
        }
        template = `<div>...</div>`
      },
      rootOffset: function(obj,ele){
        // 計(jì)算 面板位置偏移量
        ...
      },
      judgeData: function(options){
        // 判斷popupData 合法性
      },
      complieEvents: function(ele,options){
        // 編譯元素  查詢綁定事件的DOM
        ...
      },
      addComponent: function(options){
        // 添加Vue popup全局組件
        ...
      }
    }


    const popup = (options) => {
      /* 初始化data */
      methods.initpopupData(options);
    
       /* 判斷是否有data 并查看data中是否有禁止項(xiàng)*/
      methods.judgeData(options);

       /* 創(chuàng)建popup組件 */
       methods.addComponent(options);
    
    return popup;
})

上源碼宾尚!

(function(global, factory){
    typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
    typeof define === 'function' && define.amd ? define(factory) :
    (global = global || self, global.popup = factory());
})(this,function(){
    
    let [template, popupData] = ['' , {}];
    
    const [_toString, doc] = [ Object.prototype.toString, document.documentElement];

    function _isObj(obj){
        return _toString.call(obj).slice(8,-1) === 'Object';
    };
    
    function _isArr(obj){
        return _toString.call(obj).slice(8,-1) === 'Array';
    };
    
    function _isEle(ele){
        return _toString.call(ele).slice(8,12) === 'HTML';
    };
    
    function _isFun(fun){
        return _toString.call(fun).slice(8,-1) === 'Function';
    };
    
    
    function _isErr(err){
        return _toString.call(err).slice(8,-1) === 'Error';
    };
    
    let methods = {
        location: function(t){
            let [ele,left,top] = [t,0,0];
            left = ele.offsetLeft;
            top = ele.offsetTop;
            
            while(ele.offsetParent !== null){
                ele = ele.offsetParent;
                left += ele.offsetLeft;
                top += ele.offsetTop;
            }
            left += t.offsetWidth;
            top += t.offsetHeight;
            return {
                root: t,
                left: left,
                top: top
            };
        },
        initpopupData: function(options){
            popupData = {
                type: options.type,
                promptValue: '',
                res: {},
                html: ('html' in options) ? options['html'] : false,
                innerHTML: ('html' in options)&&('innerHTML' in options) ? 
                            options['innerHTML'] : 
                            '請(qǐng)?jiān)O(shè)置一個(gè)innerHTML屬性丙笋,指向自定義容器內(nèi)容'
                            
            };
        },
        template: function(type,options){
            if(type !== 'slot') {
                if(!('content' in options)) return new Error('不存在content屬性谢澈,請(qǐng)?zhí)砑哟俗侄危侄蔚闹禐轱@示內(nèi)容御板!');
            }
            template = `
            <div class="popup-mask" @click.self="close" id="popup-mask" ref="popup" >
                <div class="popup-content popup-${type}" ref="popupcon">
                    <template v-if="type !== 'slot'">
                        <div class="popup-tips">
                        <p>${options.content}</p>
                        <input class="popup-prompt-input" v-model="promptValue" v-if="type === 'prompt'">
                        </div>
                        <div class="popup-options">
                            <button @click="confirm" v-if="type !== 'toast'" class="popup-btn-confirm">確定</button>
                            
                            <button @click="close" v-if="type === 'confirm' || type === 'prompt'" class="popup-btn-close">取消</button>
                        </div>
                    </template>
                    <template v-else>
                        <div v-html="innerHTML" v-if="html" ref="chtml"></div>
                        
                        <slot name="content" v-if="!html"></slot>
                    </template>
                </div>
            </div>
            `
        },
        rootOffset: function(obj,ele){
            let [bl,bt] = [doc.clientWidth,doc.clientHeight];
            ele.style.position = 'absolute';
            if(obj.top + ele.clientHeight > bt ){
                // 面板底部溢出
                ele.style.bottom = bt - obj.top + obj.root.clientHeight + 'px';
                //doc.scrollTop = doc.clientHeight;
            }else{
                ele.style.top = obj.top + 'px';
                //doc.scrollTop = 0;
            }
            
            if(obj.left + ele.clientWidth > bl){
                // 面板右側(cè)溢出
                ele.style.right = bl - obj.left + obj.root.clientWidth + 'px';
            }else{
                ele.style.left = obj.left + 'px';
            }
        },
        judgeData: function(options){
            if(('data' in options) && _isObj(options['data'])){
                let state = true;
                for(let item in options['data']){
                    for(let i in popupData){
                        if(item === i){
                            console.error('data子屬性:'+item+'為初始化data屬性值锥忿,請(qǐng)更換名字后重試!');
                            state = false;
                            break;
                        }
                    }
                }
                
                if(!state) return false;
            }
            
            return true;
        },
        complieEvents: function(ele,options){

            let eleArr = [];
            
            push(ele);
            
            function push(fragment){
                Array.from(fragment.childNodes).forEach((node)=>{
                    
                    if(node.nodeType === 1 && node.getAttribute('@click')){
                         eleArr.push(node);
                    }
                    
                    if(node.childNodes){
                        push(node);
                    }
                })
            }
            
            if(eleArr.length === 0) return true;
            
            if(!('methods' in options) || !(_isObj(options['methods']))) return false;
            
            for(let item in options['methods']){
                if(!(_isFun(options['methods'][item]))){
                     return false;
                }
            }

            eleArr.forEach(item=>{
                item.addEventListener('click', function(){
                     options['methods'][item.getAttribute('@click')]();
                })
            })
            
            return true;
        },
        addComponent: function(options){
            Vue.component('popup',{
                template:template,
                methods: {
                    close: function(){
                        var popupa = this.$refs.popup;
                        document.body.removeChild(popupa);
                        // callback close
                        if(('close' in options) && _isFun(options.close)){
                            options.close(false);
                        }
                        return false;
                    },
                    confirm: function(){
                        var popupa = this.$refs.popup;
                        document.body.removeChild(popupa);
                        // callback confirm
                        if(('confirm' in options) && _isFun(options.confirm)){
                            if(options.type === 'prompt'){
                                options.confirm(this.promptValue,this.res);
                            }else{
                                options.confirm(true,this.res);
                                
                            }
                        }
                        
                        return true;
                    },
                    __initOptions: function(){
                        // type toast
                        
                        if(options.type === 'toast'){
                            let timeout = 1500;
                            if(('timeout' in options) && !isNaN(Number(options.timeout))){
                                timeout = options.timeout;
                            }
                            
                            let keyframs = [
                                {
                                    opacity: 1,
                                },{
                                    opacity: 0,
                                },
                            ];
                            
                            setTimeout(()=>{
                                this.$refs.popup.animate(keyframs,500);
                                setTimeout(()=>{
                                    this.close();
                                },500);
                            },timeout)
                        }
                        
                        //mask css --- opacity
                        if(('opacity' in options) && !isNaN(options.opacity) && options.opacity >=0 && options.opacity <=1){
                            this.$refs.popup.style.background = 'rgba(0,0,0,'+options.opacity+')';
                        }
                        
                        // content style and css
                        if(('style' in options) && _isObj(options.style)){
                            for(var i in options.style){
                                this.$refs.popupcon.style[i] = options.style[i];
                            }
                        }
                        
                        if(('css' in options) && _isArr(options.css)){
                            options.css.forEach(item=>{
                                this.$refs.popupcon.classList.add(item);
                            })
                        }
                        
                        // content location
                        if(('root' in options) !== null && _isEle(options.root)){
                            let l = methods.location(options.root);
                        
                            methods.rootOffset(l,this.$refs.popupcon);
                        }
                        
                        
                        //content container
                        if(('html' in options) && options['html']){
                            
                        }
                        
                        //bindEvents
                        this.htmlEvent();
                        methods.close = this.close;
                        methods.confirm = this.confirm;
                    },
                    
                    htmlEvent: function(){
                        
                        if(!this.$refs.chtml) return;
                        let state = methods.complieEvents(this.$refs.chtml,options);
                        
                        if(!state){
                            console.error('您未傳入methods對(duì)象,或methods對(duì)象的值有誤');
                        }
                    }
                },
                data: function(){
                    let obj = popupData;
                    
                    if(('data' in options) && _isObj(options['data'])){
                        Object.assign(obj, options['data']);
                    }
                    
                    return obj;
                },
                mounted: function(){
                    this.__initOptions();
                }
            })
        }
    };
    
    
    const popup = (options) => {
        let body = document.body;
        let div = document.createElement('div');
        div.setAttribute('id','popup');
        body.appendChild(div);
        
        /* 初始化data */
        methods.initpopupData(options);
        
        /* 判斷類型 并創(chuàng)建template-html模板 */
        var ee = methods.template(options.type, options);
        if(_isErr(ee)){
            console.error(ee.message);
            return;
        }
        
        /* 判斷是否有data 并查看data中是否有禁止項(xiàng)*/
        methods.judgeData(options);

        /* 創(chuàng)建popup組件 */
        methods.addComponent(options);
        
        if(('slot' in options) && _isObj(options.slot) && options.slot['name'] !== undefined && options.slot['url'] !== undefined){
            
            Vue.component(options.slot['name'], httpVueLoader(options.slot['url']))
            
        }
        
        return new Vue({
            render: function(h){
                return h('popup',{
                    scopedSlots: {
                        content: function(){
                            return h('div',{
                                attrs:{
                                    class: 'popup-section'
                                }
                            },[h(options.slot.name)])
                        }
                    }
                })
            },
            methods: {
                close: function(){
                    methods.close();
                },
                confirm: function(){
                    methods.confirm();
                }
            }
        }).$mount('#popup');
    };
    
    return popup;
})

有點(diǎn)智障的是怠肋,throw new Error ()語法 在寫的時(shí)候?qū)懗闪?return new Error()半天沒反應(yīng)敬鬓。后來跑控制臺(tái)試了下才發(fā)現(xiàn)自己搞錯(cuò)了,原來用的console.error(),感覺不太聰明的樣子笙各,哈哈哈钉答。當(dāng)做新手練手項(xiàng)了,其實(shí)寫完了才發(fā)現(xiàn)可以改的地方還很多杈抢。我唯一擔(dān)心的是数尿,每次調(diào)用slot模式時(shí), 都會(huì)執(zhí)行 Vue.component掛載全局popup方法惶楼, 還有 return了一個(gè) new Vue右蹦。但是我暫時(shí)又不知道怎么改比較好,如果有人看到這篇文章歼捐, 能提出一些改進(jìn)意見就更好啦何陆!最后附上寫的 css代碼:

.popup-mask {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    bottom: 0;
    background-color: rgba(0,0,0,.2);
    display: flex;
    justify-content: center;
    align-items: center;
    z-index: 999;
}

.popup-content {
    border-radius: 10px;
    border: 1px solid #ccc;
    background-color: #eee;
    box-shadow: 0 0 4px #555;
    color: #333;
    padding: 10px;
    box-sizing: border-box;
}


.popup-alert,
.popup-confirm,
.popup-prompt,
.popup-toast {
    width: 360px;
    height: 200px;
}

.popup-prompt-input {
    width: 100%;
    height: 32px;
    line-height: 32px;
    padding: 0 4px;
    box-sizing: border-box;
    border: 1px solid #ccc;
    outline: none;
    border: 4px;
    margin: 6px 0;
}

.popup-slot {
    width: 60%;
    height: 60%;
}

.popup-tips {
    height: calc(100% - 36px);
    padding: 10px;
    box-sizing: border-box;
}

.popup-options {
    display: flex;
    justify-content: flex-end;
    align-items: center;
    height: 36px;
    padding: 0 10px;
}

.popup-options .popup-btn-confirm,
.popup-options .popup-btn-close {
    padding: 4px 8px;
    border-radius: 4px;
    border: 1px solid #ccc;
    outline: none;
    background-color: #eee;
    color: #fff;
    cursor: pointer;
}

.popup-options .popup-btn-confirm {
    background-color: #5CB85C;
    margin-right: 10px;
    color: #fff;
}

.popup-options .popup-btn-close {
    color: #333;
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市豹储,隨后出現(xiàn)的幾起案子甲献,更是在濱河造成了極大的恐慌,老刑警劉巖颂翼,帶你破解...
    沈念sama閱讀 222,378評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件晃洒,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡朦乏,警方通過查閱死者的電腦和手機(jī)球及,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,970評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來呻疹,“玉大人吃引,你說我怎么就攤上這事」舸福” “怎么了镊尺?”我有些...
    開封第一講書人閱讀 168,983評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)并思。 經(jīng)常有香客問我庐氮,道長(zhǎng),這世上最難降的妖魔是什么宋彼? 我笑而不...
    開封第一講書人閱讀 59,938評(píng)論 1 299
  • 正文 為了忘掉前任弄砍,我火速辦了婚禮仙畦,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘音婶。我一直安慰自己慨畸,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,955評(píng)論 6 398
  • 文/花漫 我一把揭開白布衣式。 她就那樣靜靜地躺著寸士,像睡著了一般。 火紅的嫁衣襯著肌膚如雪碴卧。 梳的紋絲不亂的頭發(fā)上弱卡,一...
    開封第一講書人閱讀 52,549評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音螟深,去河邊找鬼谐宙。 笑死烫葬,一個(gè)胖子當(dāng)著我的面吹牛界弧,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播搭综,決...
    沈念sama閱讀 41,063評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼垢箕,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了兑巾?” 一聲冷哼從身側(cè)響起条获,我...
    開封第一講書人閱讀 39,991評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蒋歌,沒想到半個(gè)月后帅掘,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,522評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡堂油,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,604評(píng)論 3 342
  • 正文 我和宋清朗相戀三年修档,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片府框。...
    茶點(diǎn)故事閱讀 40,742評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡吱窝,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出迫靖,到底是詐尸還是另有隱情院峡,我是刑警寧澤,帶...
    沈念sama閱讀 36,413評(píng)論 5 351
  • 正文 年R本政府宣布系宜,位于F島的核電站照激,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏盹牧。R本人自食惡果不足惜实抡,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,094評(píng)論 3 335
  • 文/蒙蒙 一欠母、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧吆寨,春花似錦赏淌、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,572評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至辣卒,卻和暖如春掷贾,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背荣茫。 一陣腳步聲響...
    開封第一講書人閱讀 33,671評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工想帅, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人啡莉。 一個(gè)月前我還...
    沈念sama閱讀 49,159評(píng)論 3 378
  • 正文 我出身青樓港准,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親咧欣。 傳聞我的和親對(duì)象是個(gè)殘疾皇子浅缸,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,747評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容

  • 活動(dòng)主題 軍訓(xùn)季衩椒,你有故事和照片,我有“酒”哮兰,你確定不和我走嗎毛萌?在大一新生軍訓(xùn)期間,為了提高軍訓(xùn)的積極性喝滞,同時(shí)促進(jìn)...
    元?dú)馍倥祖?/span>閱讀 281評(píng)論 0 0
  • 記得這句話是我高中畢業(yè)留言上寫的阁将,沒想到竟然成了讖語,現(xiàn)如今對(duì)我而言囤躁,真的應(yīng)驗(yàn)了冀痕,我...
    E鍵傾心閱讀 233評(píng)論 2 2