如何使用vue.js構造modal(彈窗)組件?
按照示例使用vue.component 構造一個modal組件,包括模版,還有一些方法
但只能在父層模版里預先加上&& 標籤,才能正確渲染如果是動態添加&標籤是沒有效果問題就是,構造一個modal組件,如何在需要的時候動態非同步的插入到dom里
為什麼一定要非同步插入?
其實以前也有一些用戶跟我糾結過這個問題,他們覺得一定要在需要的時候創建這個組件才是符合他們思維的做法。在我看來,這是沒有理解『狀態驅動的界面』的一種表現。
傳統的命令式 (Imperative) 的思維寫出來的代碼:
$(".open-modal").on("click", function () {
var modal = new Modal()
modal.$appendTo("body")
modal.open()
})
// 在 modal 內部還要處理關閉、銷毀自身的邏輯
狀態驅動的思維寫出來的代碼:
this.showModal = true
// 關掉
this.showModal = false
哪個乾淨,哪個容易理解、容易測試、容易維護?
從模板的角度來看:在父模板里直接寫入 &
題主可能會覺得總是渲染 &
另外一個情況是,我們可能需要在一個嵌套了很多層的子組件裡面觸發 modal。這種情況下,你應該把 modal 放在根組件裡面,然後從子組件觸發一個事件上去。
最後我得說,其實『狀態驅動』才是 Vue 的精髓所在。
彈出型的組件,無論是 Dialog、Notification、Toast 還是其它的,都有一些共通的問題需要解決:
1. 多個彈層同時出現時,z-index 的順序
2. 多個彈層同時出現時,共享遮罩層3. 是否可以自我管理,典型的,定時或者點擊遮罩層關閉彈層。4. 有些彈層需要保留狀態,比如你編輯某篇文章,不小心點叉了,再打開還在;而有些需要每次打開都是新的,比如後台中創建某個條目的彈出式表單,你創建過一個條目,下一次再開這個彈層,不應該保留上一個條目的信息5. 組件放在哪,是否單例,這個和第4點放一起說6. 如何支持快捷方式,比如喜聞樂見的掛在一個全局方法上,用時創建一個,關閉後銷毀7. 模態,阻止滾動這些都是之前自己寫組件庫時遇到的問題,分享一點自己的實踐~
1. z-index 的管理
通過設置 position 為 fixed 或 absolute 的方式製作彈層,多個彈層同時打開時,默認是以 dom 樹中的位置來確定遮照次序的。當然,我們可以手動設置 z-index,但這麻煩不說,還容易出錯。
我的做法是設置一個彈層基類組件,比如叫 Popup,更上層的 Dialog、Toast 之類都是基於它開發的。這個 Popup 組件保持一個單例計數器,比如這樣&
這樣一來所有基於 Popup 的組件的 z-index 都是自動計算的,後打開的總是在前面。
2. 共享遮罩層
如果所有對話框實例都有自己的半透明遮照,同時打開後層層疊加會很難看(不過,也許有的設計師就喜歡這種效果?)。為了解決這個問題,可以在 Popup 上設置一個單例遮照層。也就是說,若兩個以上 Dialog 打開時,Popup 判斷已經有一個遮罩層了,那麼自動將後面 Dialog 的透明度設為 0。
這裡需要注意的是,並不是所有基於 Popup 的彈層式組件都有相同的透明度(比如有的是0.4,有的是 0.8),這裡應該設置一個單例變數 maxOpacity,記錄並設置為最深的遮照。
3. 外部觸發與自我觸發的折中
拿最常見的需求來說,Dialog 需要能夠點擊遮罩層關閉。Vue 1.0 中通過 sync 修飾符很容易做到內外部共享狀態,但 2.0 中已經取消了 sync,可能是為了更清晰的狀態變化管理,但我覺得 sync 在一些特殊場合確實很有用。
那麼現在,一個典型的做法是完全交由外部來控制 Dialog 的開關,比如父組件通過 prop 設置一個布爾值 open,Dialog 發覺遮照被點擊時,可以向上拋出個 click-mask 事件,由父組件來決定是否可以關閉。
其實通過 v-model 也可以實現類似 sync 的功效,我不確定語義上是否一定準確,點擊遮照關閉對話框也算是用戶輸入,觸發一個 input 事件是合理的,但類似 Toast 2 秒後關閉,這可能說不通。但是共享狀態後,內外部都可以控制,實在是很方便,這個可以權衡?
4. 彈層每次打開,是否應該重繪。
我的做法是暴露一個 prop 布爾值 auto-reset 讓使用者決定。如果需要每次都重回,就將 open 綁在 v-if 上,否則綁在 v-show 上:
&
.soil-popup(
v-if="reset? open: true",
v-show="open"
)
slot
&
5. 組件放在哪,是否單例
這個根據彈層的功能需求決定。比如後台運營系統里,點擊條目列表彈出個條目編輯框,這種天然高內聚的需求,可以與條目列表放在一個條目管理者組件中。而像 AlertDialog、ConfirmDialog 之類則適合放在全局位置。
單例也是看需求,像模態化的 AlertDialog 就一個出口,你肯定得先關閉一個再打開另一個,只需單例。其他需要同時存在的彈層類型,就要考慮多個了。這也是我不喜歡用聲明式的寫法去調用彈層的原因,看第6點。
6. 掛一個快捷方法,用時創建一個彈層,用完銷毀。
很多彈層需要幾個同時存在是不確定的( Notification、Toast 等 ),而且對於通用型的彈層,會在很多疙瘩角落裡調用…… 我覺得雖然快捷方法違反 Vue 狀態化管理組件的特性,但特殊需求還是應該特殊對待。
所以我會設置一個全局 alert 方法( CoffeeScript ):Constructor = require "./AlertDialog.vue"
VueComponent = Vue.extend Constructor
alert = (message, onOK) -&>
vm = new VueComponent().$mount()
vm.message = message
vm.$on "ok", onOK if onOK
vm.$on "ok", -&>
vm.open = false
document.body.removeChild(vm.$el)
setTimeout (-&> vm.$destroy()), 0
document.body.appendChild(vm.$el)
vm.open = true
記得銷毀實例前要設置 open = false,因為要通知那個全局 z-index 計數器去遞減
7. 模態,阻止滾動
這個與 Vue 沒什麼關係,網上有很多教程,不說了就我現在選擇的辦法是這樣的聲明:
&
調用:
this.$refs.modal.show() // -&> returns a promise
當然這個是建立在我這裡所有modal都具有明確的「確定」和「取消」語義的基礎之上的(或者直接理解為modal是一個可定製內容的confirm),如果沒有這麼一說的話,可以更簡單一些,.isShown = true就夠了。
不過我正在YY一種情況是在最外層放一個&
為什麼需要統一管理呢?打個比方,當你需要支持多個modal同時存在時,很有可能需要共享一個遮罩層,當然也許可以把遮罩層提出來放到root上來實現共享吧……然後多個modal有個排序問題,如果modal各自維護,那麼排序基本上就只能是後彈的蓋住先彈的,但有些高端一些的modal組件其實是可以支持無序的(獲得焦點的modal處於最上層),雖然目前我沒有這種需求,不過萬一有一天遇到呢?
考慮到這些短期內(我)用不到的東西一旦深入想就很容易變成過度設計,我暫時先就這麼地用著了,沒什麼明顯不好的。@Jim Liu 的 transclude 方案才是最優解, modal該放哪裡還是放哪裡,作用域不變,天知道modal裡面需要什麼數據呢,你放到跟組件上,如果是一個嵌套很深的組件要調用 modal一層一層的往上傳遞事件和數據太麻煩,有時候我們的組件是fixed元素,他必須在body下面放著,但我還想他離我最近能直接控制它,比如
&
&
&
&
&確認&
&
&
&
&
就比如這段代碼 app上面有transform屬性會導致下面fixed子元素失效(有很多css屬性形成層就會導致下面子元素定位失敗),這個時候這個modal就必須放在body上面,但如果放在body上面就會失去很多便捷性,這個時候如果能有一個指令 transclude
&
是這個modal神奇的嵌到body上面或者其他備用元素裡面,但作用域還是保持在原組件裡面,就是只改變布局,不改變組件順序,就完美解決這個問題。
更新: 發現已經有人開源了這個指令 calebroseland/vue-dom-portal 有遇到樣式問題的可以試試我是單獨搞了個通知App,用一個 List 來管理窗口。需要彈窗的時候就 push 一個對象描述下你要的窗口,狀態驅動嘛
在更新一下
在RadonUI 完整的實現了更簡單的 Modal 調用方式radon-ui/src at master · luojilab/radon-ui · GitHub
更新一下main.jslet $root = null
Vue.prototype.$Modal = {
setRoot(vm) {
$root = vm
},
create (title, text, confirm, cancel) {
if (!$root) return
$root.modal = {
show: true,
title: title,
content: text,
cancel: cancel,
confirm: confirm
}
}
}
&
import {
radonModal
} from "./components/index"
export default {
data () {
return {
modal: {
show: false,
title: "",
content: "",
cancel: () =&> {},
confirm: () =&> {}
}
}
},
created () {
this.$Modal.setRoot(this)
},
components: {
radonModal
}
}
export default {
methods: {
doSomeThing () {
this.$Modal.create("網路錯誤", "無法連接到伺服器", () =&> {
input.state = "default"
}, () =&> {
console.log("canceled")
})
}
}
}
這道題我要強答一下,雖然 @尤雨溪 大神說得非常在理,但是非同步載入模態框也很一種很常見的需求,畢竟頁面初次渲染的時候模態框部分的資源載入沒有啥意義,因為操作完頁面都可能用不到模態框,而如果模態框的內容比較重就造成請求資源的浪費了。(或許有時候我們該忽略這種浪費?)
目前針對我們自己系統內部的模態框需求我總結一下:
- DOM 結構上可以同時存在多個模態框組件,但同一時間只展示一個,而遮罩層是半透明的,否則頁面會很亂
- 所有模態框共用一個遮罩層,只在當前展示的模態框需要遮罩的時候顯示
- 模態框需要支持同步載入和非同步載入兩種方式
- 模態框組件可以自主地選擇是否加入過渡動畫
- 調用要非常簡單,同時模態框組件本身是普通 vue 組件,即靈活性
之前的答案確實太長了,直接貼從項目里提出來的 npm 包源碼倉庫吧。
https://github.com/JounQin/vue-async-modal
歡迎拍磚。
我是先建了一個.vue組件組件內部寫了各種類型的彈窗通過v-show來切換這個組件可以像普通組件一樣先放在頁面里,需要的時候父組件通過props來控制彈窗的內容和顯示等不過我還給該組件實現了install方法,方法內部給vm拓展了一個toast方法,這樣全局use之後也可以通過vm.toast來彈窗(非同步編譯組件再插到body)這兩種使用場景不同,前者適用普通場景,彈窗的需求是確定的後者一般用於提供給其他插件使用,比如ajaxapi:toast(type,content,[showDelay],[hideDelay])頁面中就是這種了&
剛寫了個shaodahong/vuejs-modal
最近用vue重構項目,之前的項目用到了大量的modal,輪子找了一遍發現沒有一個合心意的,一方面是自定義麻煩,一方面是不能嵌套使用,所以自己看了下插件的寫法寫了個,目前基本上滿足了我的需求,除了modal還有I18也沒有合心意的,太挫,正在考慮……
主要思路:
1. 一個vue文件就是一個modal
2. modal可以復用(顯示的文字可以通過參數來決定)
3. 使用的時候直接this.$modal.要使用的modal框名字
4. 這個modal是個promise,這樣我就可以根據它的狀態來確定我在modal上的狀態(確定,取消,關閉啥的)
5. 使用者不關心它的層級(z-index),層級遞增
目前基本的功能已經實現了,vue提供了use方法來安裝插件還是很便利的
補充:該插件的用法和angularjs的uiModal很類似,自定義modal模板和邏輯,用的時候直接用過this來使用,並且可以彈窗之上再彈窗,俗稱彈彈彈……魚尾紋
Demo地址
可以用非同步組件來實現題主的需求
最近在項目中經常用到彈窗組件,因此寫了一個Vue的彈窗組件,參見:GitHub - mengchen129/vue-modal: A modal component by Vue.js使用場景就如尤雨溪大神所說,在body掛在&
我們也是將其放在根組件上,但是鑒於2.0要取消 $dispatch 和 $broadcast,所以應該使用 vuex 或者 store 來管理狀態
放到根組件。。。我之前一直都是單獨出來的一個彈窗組件,感覺好蠢啊
angularjs $modal的用法
vue.js dynamic create modal剛好在糾結模態框到底是放在根組件還是子組件里的問題,膜拜尤大一波
最近項目中自己寫了個modal組件,寫了篇文章,同時對其他的實現做了一些比較:用vue實現模態框組件
對於多個modal來回切換的情況,我這邊的做法是多個modal的結構合併為一個modal,然後根據不同的狀態控制modal裡面的內容展示,這樣做的好處是始終只有一個彈框,切換的不過是彈框裡面的內容:
& & & & & & & & & & & &