日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

11.彈窗原理詳解

 行者花雕 2021-09-12

在平時中,經(jīng)常會用組件來定義彈窗,因為彈窗可以用一個div元素浮在頁面上。在事實上,彈窗可以簡單也可以復(fù)雜,簡單到只有一個標題,一個文本描述;復(fù)雜到可以和一個app一樣,擁有前進后退,可以實現(xiàn)復(fù)雜業(yè)務(wù)邏輯。如果按照往常的思路,就會很難擴展。
在彈窗的日常使用中,主要有如下幾個情況:

  1. 類似廣告彈窗,一直掛在頁面上,除非用戶手動點擊關(guān)閉按鈕;
  2. 類似提示框,它會停留在頁面角落幾秒,然后消失。不管頁面是否切換它都會在,沒有手動關(guān)閉按鈕;
  3. 類似輸入窗口,它會常駐頁面上,往往會有個遮罩,告訴用戶要把這個窗口完成了才能做其他事情,或者取消,它的優(yōu)先級很高,無法通過后退鍵關(guān)閉(有時候后退鍵代表切換頁面),后退只會切換頁面;
  4. 類似上面的情況,它可以通過用后退鍵關(guān)閉彈窗;
  5. 上面的彈窗擁有隨時切換內(nèi)容的功能,可能是一個彈窗,點擊的時候變成一個大表單等;
  6. 彈窗可能會生成彈窗的功能,可以擁有沒有上限的彈窗數(shù),通過關(guān)閉最底層的彈窗來關(guān)閉所有它延伸的彈窗。

上述可能是常見的彈窗例子,還有很多非常規(guī)復(fù)雜的情況,比如用pc端彈窗模擬移動端窗口等。

在這里我把彈窗分為2類:

  1. 它是由App實例對象彈出來的,不依賴當前顯示頁面而存在,用戶點擊后退不會影響到它。它可以通過定時關(guān)閉,比如上面的第二種情況,也可以通過用戶手動關(guān)閉,比如第一種情況。也可以是第三章情況,用戶完成任務(wù)后才關(guān)閉;
  2. 它是由Page實例對象彈出來的,它依賴于當前頁面,與history相關(guān)的,因此它是可以通過按后退鍵進行關(guān)閉的,上面的3,4,5情況。

彈窗都是可以更改里面的內(nèi)容的,因此我們把Page頁面放在彈窗里面,同時它也有不變的部分。因此,它和之前討論的App對象是很像的,而且彈窗里面的頁面切換,也是會影響整體的history。這樣,彈窗就變得靈活性十足。因為Page頁面可以彈出彈窗,所以也滿足了彈窗彈出新彈窗的需求,而且,很容易應(yīng)付除此以外的非常復(fù)雜的情況(因為彈窗對象和App對象繼承于同一個對象,App對象能做的,它都能做);

需求

我們要實現(xiàn)擁有App對象類似功能的PopUp對象,但是它是無法單獨存在的,必須依附App實例對象或Page實例對象,它是為了輔助業(yè)務(wù)開發(fā)而存在。但是它擁有管理頁面、渲染頁面、history對象等功能。

實現(xiàn)思路

我們抽象出一個ReplaceProto對象,它是主要特點就是可以切換頁面。然后把App對象和PopUp對象都繼承于這個對象。在基類中實現(xiàn)了頁面的切換邏輯,頁面緩存等基礎(chǔ)操作,代碼如下

function ReplaceProto(name, staticName, currentName) {
    BaseProto.call(this);
    this.name = name;
    this.history = null; // 無論App還是PopUp都是與History掛鉤
    this.options = {};
    this.currentPage = null; // 當前顯示的Page對象
    this.staticPage = null; // 布局Page對象

    this.changeArea = null;
    this.data = {};
    // 其它屬性
}
// 主要方法
ReplaceProto.prototype = create(BaseProto.prototype, {
    // 渲染頁面
    _show: function (bk) {
        var app = this._getApp(), that = this, len = 2;

        function feeback() {
            // 保證是個異步的過程
            requestAnimationFrame(function () {
                bk(that.staticPage, that.currentPage, app)
            });
        }
        [this.staticName, this.currentName].forEach(function (name, index) {
            app.getPageByName(name, function (outPage, opt) {
                var page = new outPage();
                if (index == 0) that.staticPage = page;
                else that.currentPage = page;

                page.baseUrl = getBaseUrl(opt.js);
                for (var key in opt) {
                    if (["title", "js", "name", "url"].indexOf(key) === -1) 
                        page.data[key] = opt[key];
                }
                if (--len === 0) feeback();
            })
        }))
    },
    // 切換頁面
    render: function (pagename, isReplace, option) {
        if (this.isRender) return false;  // 防止多次渲染
        this.isRender = true;
        var currentPage = this.currentPage, that = this;
        if (currentPage.popUp) {
            currentPage.popUp.hidden(null, function () {
                that._render(pagename, isReplace, option, 
                    that._renderComplete.bind(that));
            })
        }
        else {
            this._render(pagename, isReplace, option, that._renderComplete.bind(this));
        }
    },
});

由此定義一個PopUp對象,代碼如下

function PopUp(name, staticName, currentName) {
    ReplaceProto.call(this, name, staticName, currentName);
    this.history = new HistoryStorage("popup"); // 歷史記錄
    this.isShow = false; // 是否已彈出,防止多次彈出
    this.hideBack = null; // 關(guān)閉后的回調(diào)函數(shù),主要是清理歷史記錄
    this.popDiv = document.createElement("div"); // 彈窗的包圍容器
    this.relativeDom = null; 
    this.showTarget = null;  // 是由哪個目標彈出來的
};
// PopUp的主要方法
PopUp.prototype = create(ReplaceProto.prototype, {
    constructor: PopUp,
    // 顯示彈窗
    show: function (dom, config, target, isDismisBeforeShow) {
        var that = this, popDiv = this.popDiv
        this.relativeDom = dom;
        this.parent = target;
        this._show(function (staticPage, currentPage, app) {
            if (target.constructor === Page) {
                app.GlobalHistory.addPopUp(that); // 轉(zhuǎn)換為Popup的歷史記錄
            }
            staticPage.parent = that;
            currentPage.parent = that;
            that.isShow = true;
            dom.parentNode.appendChild(popDiv);
            staticPage.render(function (html) {
                staticPage.initialize(popDiv, html, null, function () {
                    that.changeArea = staticPage.domList.pageContainer || popDiv;
                    currentPage.render(function (htmlstr) {
                        if (target.constructor === Page) 
                            that.history.replaceState(currentPage, config);
                        currentPage.initialize(changeDom, htmlstr);
                    });
                });
            })
        });
    },
    // 彈窗關(guān)閉
    hidden: function (option, bk) {
        var that = this;
        if (this.isHidden) return; // 防止多次點關(guān)閉
        this.isHidden = true;
        // 如果它有子彈窗,子彈窗先關(guān)閉,再關(guān)閉它, 保證關(guān)閉是一個異步操作
        if (this.currentPage.popUp) {
            this.currentPage.popUp.hidden(null, function () {
                requestAnimationFrame(function () {
                    that._hidden(option, bk);
                });
            });
        }
        else {
            requestAnimationFrame(function () {
                that._hidden(option, bk);
            });
        }
    },
});

因為彈窗只能由Page實例對象和App對象彈出,它們的處理方式不一樣的,代碼如下

  • Page的showPopUp方法

    showPopUp: function (popupName, data, isDismisBeforeShow, bk) {
        data = data || {};
        if (this.isShowPop) { // 防止一個頁面點出多個彈窗
            return false;
        }
        this.isShowPop = true;
        var app = this._getApp(), that = this;
        // 只能通過當前的currentPage彈出
        if (this.parent.currentPage !== this || app.isLock) { 
            this.isShowPop = false;
            return false;
        }
        app.getPopUpByName(popupName, function (popup) {
            var popUp = new popup(data.resetConfig);
            that._showPopUp(app, popUp, data, isDismisBeforeShow, bk);
        });
        return true;
    },
    _showPopUp: function (app, popUp, data, isDismisBeforeShow, bk) {
        var that = this;
        this.isShowPop = false;
        popUp.data = data;
        if (this.popUp) {
            this.popUp.hidden(false, hiddenBack);
        } else {
            hiddenBack();
        }
    
        function hiddenBack() {
            that.popUp = popUp;
            if (popUp.show(app.changeArea || app.staticPage.domList.pageContainer, 
                data.in, that, isDismisBeforeShow)) {
                    
                if (typeof bk === "function") bk(popUp);
                popUp.hideBack = function (bk) {
                    app.removePopUpHistory(bk); // 歷史記錄清除
                    that.popUp.destroy(); // 彈窗內(nèi)部引用清除,待垃圾回收
                    that.popUp = null; // 引用彈窗清除
                }
            }
        }
    },
    
  • App的showPopUp方法

    showPopUp: function (popupName, data, isBack, isDismisBeforeShow, bk) {
        var that = this, data = data || {};
        this.getPopUpByName(popupName, function (popup) {
            var popUp = new popup(data.resetConfig);
            that._showPopUp(popUp, data, isBack, isDismisBeforeShow, bk);
        });
        return true;
    },
    _showPopUp: function (popUp, data, isBack, isDismisBeforeShow, bk) {
        var that = this;
        popUp.data = data;
        if (popUp.show(this.changeArea || this.staticPage.domList.pageContainer, 
            data, this, isDismisBeforeShow)) {
            if (typeof bk === "function") bk(popUp);
            // 彈窗列表中添加
            this.showPopups.push({
                back: isBack,
                popUp: popUp
            }); 
            popUp.hideBack = function (bk) {
                popUp.destroy();
                for (var i = 0; i < that.showPopups.length; i++) {
                    if (popUp === that.showPopups[i].popUp) that.showPopups.splice(i, 1);
                }
                if (typeof bk === "function") bk();
            }
        }
    }
    

    Page實例是否在PopUp中,可以通過isInPopUp方法來判斷。

實際應(yīng)用

與當前頁面交互
可以在頁面中自定義事件,彈窗通過觸發(fā)自定義事件,并且傳遞數(shù)據(jù)進行交互。在頁面上定義

this.attachDiyEvent(eventName, handler);

然后在彈窗的頁面上觸發(fā), this表示當前彈窗的Page對象,parent代表著PopUp對象

this.parent.dispatchEventByName(eventName, data);

彈窗也是按需引入的,因此需要通過配置引入

{
    name: "strdatePicker",
    js: "/public/ui/popup/datePicker/index.js"
}

案例地址

總結(jié)

這里主要介紹了PopUp對象的原理,然而彈窗的創(chuàng)建是最復(fù)雜的,需要一個PopUp對象和兩個Page對象,好在Page對象可以隨意切換,有時候創(chuàng)建一個PopUp對象,可以復(fù)用不同的Page,靈活性十足,在strui框架中,也有很多不錯的彈窗的案例。

推廣

底層框架開源地址:https:///string-for-100w/string
演示網(wǎng)站: https://www./

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多