序
在平時中,經常會用組件來定義彈窗,因為彈窗可以用一個div元素浮在頁面上,在事實上,彈窗可以簡單也可以復雜,簡單到只有一個標題,一個文本描述;復雜到可以和一個app一樣,擁有前進后退,可以實作復雜業務邏輯,如果按照往常的思路,就會很難擴展,
在彈窗的日常使用中,主要有如下幾個情況:
- 類似廣告彈窗,一直掛在頁面上,除非用戶手動點擊關閉按鈕;
- 類似提示框,它會停留在頁面角落幾秒,然后消失,不管頁面是否切換它都會在,沒有手動關閉按鈕;
- 類似輸入視窗,它會常駐頁面上,往往會有個遮罩,告訴用戶要把這個視窗完成了才能做其他事情,或者取消,它的優先級很高,無法通過后退鍵關閉(有時候后退鍵代表切換頁面),后退只會切換頁面;
- 類似上面的情況,它可以通過用后退鍵關閉彈窗;
- 上面的彈窗擁有隨時切換內容的功能,可能是一個彈窗,點擊的時候變成一個大表單等;
- 彈窗可能會生成彈窗的功能,可以擁有沒有上限的彈窗數,通過關閉最底層的彈窗來關閉所有它延伸的彈窗,
上述可能是常見的彈窗例子,還有很多非常規復雜的情況,比如用pc端彈窗模擬移動端視窗等,
在這里我把彈窗分為2類:
- 它是由App實體物件彈出來的,不依賴當前顯示頁面而存在,用戶點擊后退不會影響到它,它可以通過定時關閉,比如上面的第二種情況,也可以通過用戶手動關閉,比如第一種情況,也可以是第三章情況,用戶完成任務后才關閉;
- 它是由Page實體物件彈出來的,它依賴于當前頁面,與history相關的,因此它是可以通過按后退鍵進行關閉的,上面的3,4,5情況,
彈窗都是可以更改里面的內容的,因此我們把Page頁面放在彈窗里面,同時它也有不變的部分,因此,它和之前討論的App物件是很像的,而且彈窗里面的頁面切換,也是會影響整體的history,這樣,彈窗就變得靈活性十足,因為Page頁面可以彈出彈窗,所以也滿足了彈窗彈出新彈窗的需求,而且,很容易應付除此以外的非常復雜的情況(因為彈窗物件和App物件繼承于同一個物件,App物件能做的,它都能做);
需求
我們要實作擁有App物件類似功能的PopUp物件,但是它是無法單獨存在的,必須依附App實體物件或Page實體物件,它是為了輔助業務開發而存在,但是它擁有管理頁面、渲染頁面、history物件等功能,
實作思路
我們抽象出一個ReplaceProto物件,它是主要特點就是可以切換頁面,然后把App物件和PopUp物件都繼承于這個物件,在基類中實作了頁面的切換邏輯,頁面快取等基礎操作,代碼如下
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 = https://www.cnblogs.com/stringWeb/p/{};
// 其它屬性
}
// 主要方法
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; // 關閉后的回呼函式,主要是清理歷史記錄
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); // 轉換為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);
});
});
})
});
},
// 彈窗關閉
hidden: function (option, bk) {
var that = this;
if (this.isHidden) return; // 防止多次點關閉
this.isHidden = true;
// 如果它有子彈窗,子彈窗先關閉,再關閉它, 保證關閉是一個異步操作
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 = https://www.cnblogs.com/stringWeb/p/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(); // 彈窗內部參考清除,待垃圾回收 that.popUp = null; // 參考彈窗清除 } } } }, -
App的showPopUp方法
showPopUp: function (popupName, data, isBack, isDismisBeforeShow, bk) { var that = this, data = https://www.cnblogs.com/stringWeb/p/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方法來判斷,
實際應用
與當前頁面互動
可以在頁面中自定義事件,彈窗通過觸發自定義事件,并且傳遞資料進行互動,在頁面上定義
this.attachDiyEvent(eventName, handler);
然后在彈窗的頁面上觸發, this表示當前彈窗的Page物件,parent代表著PopUp物件
this.parent.dispatchEventByName(eventName, data);
彈窗也是按需引入的,因此需要通過配置引入
{
name: "strdatePicker",
js: "/public/ui/popup/datePicker/index.js"
}
案例地址
總結
這里主要介紹了PopUp物件的原理,然而彈窗的創建是最復雜的,需要一個PopUp物件和兩個Page物件,好在Page物件可以隨意切換,有時候創建一個PopUp物件,可以復用不同的Page,靈活性十足,在strui框架中,也有很多不錯的彈窗的案例,
推廣
底層框架開源地址:https://gitee.com/string-for-100w/string
演示網站: https://www.renxuan.tech/
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/172614.html
標籤:JavaScript
上一篇:10.組件實戰
下一篇:12.其它細節
