微信小程式vant彈窗元件的實現方式
作為從事前端開發的你肯定見過不少的彈框元件,你可曾有想過要自己實現一個彈框元件庫,又或者想完全定製化的使用各種標準UI框架中的彈框元件呢?
今天這篇文章將會帶著你解析這一系列疑問,以vant-weapp元件庫為例,從開發標準的彈窗元件使用到高度定製複合自我審美的彈窗,再到完全研究清楚vant-weapp框架彈窗元件部分原始碼。
一、vant-weapp彈窗元件介紹
vant-weapp元件庫是有贊團隊開發的 一款靈活簡潔且美觀的小程式UI元件庫 ,此文將以這個元件庫的用法為標準,下文提及的彈框元件均指的是此元件庫中的彈框。
彈框分類
vant-weapp中彈框主要分為**兩大類:彈出層Popup和對話方塊Dialog,**彈出層一般是帶有背景遮罩層和內容展示區域用於在不跳轉頁面情況下進行詳情的展示作用,對話方塊多數用於帶有詳情展示的同時還帶有希望使用者確認等操作。如下圖所示,圖左為典型的Dialog,圖右為典型的Popup。
註冊小程式元件
在使用彈框元件之前記得在小程式的app.json檔案中先註冊元件,詳細介紹見 快速上手 ,例如註冊van-popup元件程式碼如下:
// app.json "usingComponents": { "van-popup": "path/to/@vant/weapp/dist/popup/index" }
在專案中實際使用如下:
在本文後續分析van-dialog原始碼中會發現在dialog的index.json中也定義過van-popup元件,但是我們要直接實行van-popup元件必須在小程式的配置檔案app.json中按照上圖方式進行定義,微信小程式官網說明過 自定義元件內部的引入元件只在該元件內生效
註冊完元件之後,就可以直接在小程式頁面中使用這裡註冊的自定義元件,元件名稱為這裡 key ,例如:。
二、Popup基本用法 常見用法
最常見的用法就是直接使用van-popup元件,通過元件的show屬性來控制其是否展示,元件內部巢狀的其他元件或標籤是popup元件的內容,如下所示:
// wxml <button bindtap="showPopup">展示彈出層</button> <van-popup show="{{ show }}" position="top" bind:close="onClose" closeable >內容</van-popup> // js Page({ data: { show: false },showPopup() { this.setData({ show: true }); },onClose() { this.setData({ show: false }); } });
重點屬性分析
van-popup元件可以通過position屬性的五個值: center、top、right、bottom、left
來快捷的控制是從哪個位置彈出,例如:上例中的彈框從上往下彈出
可以通過round屬性來控制彈窗內容是否顯示圓角,closeable可以決定是否顯示關閉彈框的圖示按鈕,例如:上例中的彈窗將不顯示圓角,同時顯示關閉按鈕
各種基本的彈窗形式如下:
三、Dialog對話方塊基本用法
對話方塊則是在popup彈出層的基礎上添加了額外的內建的標題,快速確定按鈕等元件,用於訊息提示、訊息確認等場景,下面看看其常見用法。
常規用法——帶標題
最常規的用法就是直接使用van-dialog元件,通過元件的show屬性來控制其是否展示,元件內部巢狀的其他元件或標籤是dialog元件的內容,如下所示:
// wxml <van-dialog title="標題" message="程式碼是寫出來給人看的,附帶能在機器上執行" show="{{ show }}" confirm-button-open-type="getUserInfo" bind:close="onClose" bind:getuserinfo="getUserInfo" > <image src="https://img.yzcdn.cn/1.jpg" /> </van-dialog> // js Page({ data: { show: true },getUserInfo(event) { console.log(event.detail); },onClose() { this.setData({ close: false }); } });
常規用法——無標題
直接使用van-dialog元件,通過元件的show屬性來控制其是否展示,元件內部巢狀的其他元件或標籤是dialog元件的內容,不使用use-title-slot且不傳遞title屬性,如下所示:
// wxml <van-dialog show="{{ show }}" confirm-button-open-type="getUserInfo" bind:close="onClose" bind:getuserinfo="getUserInfo" > <view class="message">程式碼是寫出來給人看的,附帶能在機器上執行</view> </van-dialog> // js Page({ data: { show: true },onClose() { this.setData({ close: false }); } });
上述兩種用法中的use-slot屬性表示使用預設的slot(即van-dialog巢狀的wxml內容,比如此處的
函式式呼叫——confirm
最常規的另一種用法就是直接使用 Dialog、Dialog.alert、Dialog.confirm
的方法快速開啟彈窗元件,關閉彈框元件則通過 Dialog.close
,取消彈框的載入狀態則使用 Dialog.stopLoading
,元件內部巢狀的其他元件或標籤是dialog元件的內容,如下所示:
// wxml <van-dialog id="van-dialog"> import Dialog from 'path/to/@vant/weapp/dist/dialog/dialog'; // js Dialog.alert({ title: "標題" message: '程式碼是寫出來給人看的,附帶能在機器上執行' }).then(() => { // on close });
這裡使用函式呼叫一定要注意在使用van-dialog的頁面的wxml中一定需要寫這個來使用元件,下文在分析dialog的原始碼中會講到(賣個關子),或者你可以先猜一猜:blush::blush:
上面三種van-dialog的常規使用方法的效果如下:
四、Dialog進階用法
下面將會提供幾個作者在實戰中寫出的Dialog對話方塊元件的實戰用法
使用use-title-slot定製標題
<van-dialog id="van-dialog" show="{{ dialogShow }}" message="資質原件拍照或掃描可以不加蓋公章,影印件需蓋章\n\n如是三證合一,則無需提供稅務登記證、組織機構程式碼證" message-align="left" confirm-button-text="知道了" confirm-button-color="#EE712F" use-title-slot > <view slot="title" class=" merchant-dialog__title"> <view class="merchant-dialog__title-text">開戶前,請準備以下資料</view> <van-icon name="cross" size="40rpx" class="merchant-dialog__title-icon" bindtap="closeDialog" /> </view> </van-dialog> // 樣式部分的程式碼此處省略
觸發彈框顯示
handleButtonClick1: function () { this.setData({ dialogShow: true }) },
此例子如要使用瞭如下特性:
use-title-slot confirm-button-text、confirm-button-color van-icon
對應的效果如下:
使用use-slot定製提示內容
<van-dialog id="van-dialog-2" use-slot use-title-slot > <view slot="title" style="padding-bottom: 10px;"> <van-icon name="close" color="#fff" size="30" bindtap="closeDialog2" /> </view> <image class="image" src="https://tva1.sinaimg.cn/large/0082zybply1gbylbcwm44j30rs13bdsg.jpg" mode="aspectFit"></image> </van-dialog>
通過觸發彈框顯示
handleButtonClick2: function () { Dialog({ selector: '#van-dialog-2',showConfirmButton: false,closeOnClickOverlay: false,className: 'dialog2',width: '260px' }) },
此例子如要使用瞭如下特性:
- 使用
use-slot
表示使用預設的slot來內容來渲染到彈框主體內容位置 - 渲染的內容為一張圖片,以此來 實現幕簾curtain效果
- 使用
className
這個externalClasses
來用頁面樣式控制組件內部樣式
對應效果如下:
使用css變數定義主題
<van-dialog id="van-dialog-3" use-title-slot > <view slot="title" style="color: #000;">提示</view> <view> <view>為了給你推薦更合適的漫展~</view> <view>請開啟定位許可權~</view> </view> </van-dialog>
通過觸發彈框顯示
handleButtonClick3: function () { Dialog({ selector: '#van-dialog-3',showCancelButton: true,cancelButtonTrext: '取消',confirmButtonText: '去設定',cancelButtonColor: '#C46B85',confirmButtonColor: '#C46B85',message: '為了給你推薦更合適的漫展~\n請開啟定位許可權~',confirmButtonOpenType: 'openSetting',width: '260px',className: 'dialog3' }) },
外部樣式類
.dialog-index--dialog3 { --dialog-background-color: rgba(255,255,0.8); --popup-background-color: rgba(255,0.8); --button-default-background-color: transparent; color: #666; }
此例子如要使用瞭如下特性:
cancelButtonColor、confirmButtonColor --dialog-background-color
對應效果如下:
五、開發實際場景中的彈窗元件
如果你仔細看過上面中的三種自定義方式的實現程式碼應該也可以根據UI需求實現自己的彈窗互動效果;這裡我已經基於前面提到的三種用法來開發了幾個實際場景中的彈框元件:
- 實現幕簾curtain效果
- 實現操作許可權提示
- 提示應用升級
- 應用使用提示
這部分的可以直接去看原始碼 github.com/JohnieXu/va…
也可以掃碼這個小程式二維碼檢視效果
六、原始碼分析的前置條件
在看完上面幾種炫酷的彈框效果後,我們還是按照慣例研究下如此強大的彈框元件的原始碼。在研究彈框部分原始碼之前有必有分析一下一套完整UI框架所需要注意的框架級別的整體架構
如何使用less工程化處理樣式
處理樣式是所有UI框架比不可忽略的核心邏輯之一,在vant-weapp中對樣式的處理主要分為以下三部分;原始碼對應結構如下圖所示,使用less的mixins複用實現主題變數控制、公共樣式抽離等。
主題變數
在var.less檔案定義了框架所用到的全部的樣式控制相關的變數,其中與彈框相關的部分原始碼如下:
// Dialog @dialog-width: 320px; @dialog-small-screen-width: 90%; @dialog-font-size: @font-size-lg; @dialog-border-radius: 16px; @dialog-background-color: @white; @dialog-header-font-weight: @font-weight-bold; @dialog-header-line-height: 24px; @dialog-header-padding-top: @padding-lg; @dialog-header-isolated-padding: @padding-lg 0; @dialog-message-padding: @padding-lg; @dialog-message-font-size: @font-size-md; @dialog-message-line-height: 20px; @dialog-message-max-height: 60vh; @dialog-has-title-message-text-color: @gray-7; @dialog-has-title-message-padding-top: @padding-sm;
原始碼: var.less
此檔案中的最終會轉換成 css變數 ,並非像antd、iview等網頁端框架中的樣式處理那樣編譯成變數指向的值。根據css變數作用域的特性,可以在自定義元件的外部樣式類中區域性覆蓋樣式變數來改變元件內部的樣式。
通用樣式
像清除浮動、文字省略、1畫素邊框等通用的樣式類的處理在mixin資料夾下
清除浮動
.clearfix() { &::after { display: table; clear: both; content: ''; } }
使用常見的after偽類來實現清除浮動
檔案省略
.multi-ellipsis(@lines) { display: -webkit-box; overflow: hidden; text-overflow: ellipsis; -webkit-line-clamp: @lines; /* autoprefixer: ignore next */ -webkit-box-orient: vertical; } .ellipsis() { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }
使用less的函式封裝了兩個處理文字省略方法:單行省略、多行省略
BEM命名
如何工程化的註冊自定義元件
微信小程式官方提供了 Component構造方法 註冊自定義元件,為了結合typescript給自定義元件提供更靈活強大的元件註冊器對Component進行了下面的功能封裝處理
封裝通用元件構造方法
function VantComponent<Data,Props,Methods>( vantOptions: VantComponentOptions< Data,Methods,CombinedComponentInstance<Data,Methods> > = {} ): void { const options: any = {}; mapKeys(vantOptions,options,{ data: 'data',props: 'properties',mixins: 'behaviors',methods: 'methods',beforeCreate: 'created',created: 'attached',mounted: 'ready',relations: 'relations',destroyed: 'detached',classes: 'externalClasses' }); const { relation } = vantOptions; if (relation) { makeRelation(options,vantOptions,relation); } // 給所有元件新增預設外部樣式類custom-class options.externalClasses = options.externalClasses || []; options.externalClasses.push('custom-class'); // 給所有元件新增預設behaviors options.behaviors = options.behaviors || []; options.behaviors.push(basic); // map field to form-field behavior if (vantOptions.field) { options.behaviors.push('wx://form-field'); } // 預設啟用多slot支援、元件中允許全域性樣式修改 options.options = { multipleSlots: true,addGlobalClass: true }; // 最終使用官網構造方法構造元件 Component(options); }
原始碼:component.ts
behaviors複用共享邏輯
behaviors
是微信小程式官方用於元件複用 data、methods
等屬性方法的一種方式,和vue中的 mixins
小作用一致,vant-weapp中定義的 mixins
如下圖所示:
其中basic是所有自定義元件都複用的一個mxin,給所有自定義的元件提供了三個方法: $emit
、 set
和 getRect
。
- $emit 封裝了 triggerEvent 方法;
- set 封裝 setData 方法為Promise形式;
- getRect 採用Promise方法用查詢對應節點的boundingClientRect。
原始碼如下:
// basic.ts export const basic = Behavior({ methods: { $emit(...args) { this.triggerEvent(...args); },set(data: object,callback: Function) { this.setData(data,callback); return new Promise(resolve => wx.nextTick(resolve)); },getRect(selector: string,all: boolean) { return new Promise(resolve => { wx.createSelectorQuery() .in(this)[all ? 'selectAll' : 'select'](selector) .boundingClientRect(rect => { if (all && Array.isArray(rect) && rect.length) { resolve(rect); } if (!all && rect) { resolve(rect); } }) .exec(); }); } } });
原始碼: basic.ts
生命週期命名
其實生命週期如何命名到不是很重要,vant-weapp對命名進行了轉換主要基於以下兩個原因:
- 開發效率 :vant-weapp源自適用於vue的UI元件庫—— vant ,命名統一轉換便於現有框架的邏輯複用
- 使用成本 :生命週期命名向主流MVVM框架靠近減輕使用者的學習成本、框架維護成本
function mapKeys(source: object,target: object,map: object) { Object.keys(map).forEach(key => { if (source[key]) { target[map[key]] = source[key]; } }); } mapKeys(vantOptions,classes: 'externalClasses' });
原始碼: component.ts#L24
通過 mapKeys
方法對 VantComponent
中傳入的生命週期函式進行了轉換,轉換名生命週期名稱與微信小程式一致
自定義元件的樣式隔離
微信小程式自定義元件預設樣式作用域的範圍是為當前元件,也就是說元件資料夾下的wxss中的樣式只對該資料夾下的wxml生效(除去標籤名、ID選擇器)
這種以元件為單位進行樣式隔離的模式類似於React框架中處理的元件樣式的庫 styled-components
元件間樣式共享
要在元件之前共享樣式或者讓自定義元件接受外部樣式,可行方案有如下幾種:
| styleIsolation屬性配置 |
- page-isolated 表示在這個頁面禁用 app.wxss ,同時,頁面的 wxss 不會影響到其他自定義元件;
- page-apply-shared 表示在這個頁面禁用 app.wxss ,同時,頁面 wxss 樣式不會影響到其他自定義元件,但設為 shared 的自定義元件會影響到頁面;
- page-shared 表示在這個頁面禁用 app.wxss ,同時,頁面 wxss 樣式會影響到其他設為 apply-shared 或 shared 的自定義元件,也會受到設為 shared 的自定義元件的影響。 | styleIsolation 選項從基礎庫版本 2.6.5 開始支援 | | --- | --- | --- | | addGlobalClass屬性配置 | 表示頁面 wxss 樣式將影響到自定義元件,但自定義元件 wxss 中指定的樣式不會影響頁面 |
- 小程式基礎庫版本 2.2.3 以上支援
- 等價於styleIsolation: apply-shared
- vant-weapp中使用的是此方案 : addGlobalClass: 'true' ,預設的 styleIsolation: 'shared' 不生效 | | externalClasses外部樣式類 | 元件的使用者可以指定這個樣式類對應的外部樣式名 ,對應樣式名的樣式在元件內部生效 |
- 基礎庫 1.9.90 開始支援
- vant-weapp中也支援此方式
- 推薦使用此方式來自定義vant-weapp的樣式 | | class="~blue-text"引用父元件樣式 | 即使啟用了樣式隔離 isolated ,元件仍然可以在區域性引用元件所在頁面的樣式或父元件的樣式 | 基礎庫 2.9.2 開始支援 |
使用總結
使用vant-weapp元件庫的使用者最佳的自定義元件樣式的方式是: 採用外部樣式類+CSS變數,在無相關CSS變數時才用自己的樣式+ !important
確保樣式優先順序 ,在自定義元件中使用vant-weapp的元件時候的注意事項參照 樣式覆蓋 。
自定義元件通訊方案
自定義元件通訊主要包括 元件引數傳遞 和 事件監聽 ,這兩個功能都是微信小程式官網提供的;引數傳遞是由父傳到子的單向傳遞,而事件監聽則是相應原生事件或者自定義事件。自定義事件用於對元件的事件進行封裝,自定義事件機制如下:
這裡在van-dialog元件使用位置監聽bindclick事件,最終這個事件會在van-dialog元件內部的button的tap時被觸發,後面原始碼分析中的自定義元件的自定義事件全部採用的此種模式。
七、Popup彈出層元件原始碼分析
元件部分原始碼結構
popup元件部分原始碼結構如下:
元件的命名規範與微信小程式自定義元件的規範相符合(README.md為元件的使用說明文件,用於生成官網的元件文件說明)。
popup元件的配置檔案標識當前的index為元件,通過 using-components
引入了 van-icon
和 van-overlay
元件,在對應的wxml中可以直接使用。
元件主要邏輯
彈出層元件主要分類 遮蓋層 和 內容層 ,內容層巢狀在遮蓋層內部來確保視覺上覆蓋在遮蓋層之上。
遮蓋層及事件
遮蓋層通過overlay、overlayStyle等元件屬性來控制其是否顯示以及遮蓋層的樣式等,遮蓋的事件有 onClickOverlay
,通過$emit觸發元件的自定義事件close。
onClickOverlay() { this.$emit('click-overlay'); if (this.data.closeOnClickOverlay) { this.$emit('close'); } }
關閉按鈕及事件
通過closable屬性決定是否顯示預設的關閉按鈕,也可以通過關閉圖示相關屬性配置更改按鈕樣式,關閉按鈕的事件有onClickCloseIcon,通過$emit觸發元件的自定義事件close。
onClickCloseIcon() { this.$emit('close'); },
內容分發
接受一個預設的slot,其位置根據傳入的 position
引數不同有 top、right、bottom、left、center
五種,根據這五種位置引數有對應的五種不同的彈出位置和動畫
過渡動畫
使用transform來實現動畫效果,根據 position
引數的五種情況有五種預設動畫
// popup/index.less .van-bottom-enter,.van-bottom-leave-to { transform: translate3d(0,100%,0); } .van-top-enter,.van-top-leave-to { transform: translate3d(0,-100%,0); } .van-left-enter,.van-left-leave-to { transform: translate3d(-100%,-50%,0); } .van-right-enter,.van-right-leave-to { transform: translate3d(100%,0); }
同時暴露了外部樣式類可以用來自定義動畫,這裡動畫階段劃分和vue相同,分類: enter、enter-active、enter-to、leave、leave-active、leave-to
// popup/index.ts VantComponent({ classes: [ 'enter-class','enter-active-class','enter-to-class','leave-class','leave-active-class','leave-to-class' ],... }
八、Dialog對話方塊元件原始碼分析
元件部分原始碼結構
dialog元件部分原始碼結構如下:
結構同popup元件,不同點在於index.json使用了 van-popup、van-button
元件,以及多了dialog.ts這個暴露API函式呼叫方法的檔案。
元件佈局結構
dialog元件整體基於popup元件,在其預設slot中添加了頂部標題的slot和按鈕組元素,大致結構如下
原始碼結構:
// dialog/index.wxml <van-popup show="{{ show }}" ... > <view wx:if="{{ title || useTitleSlot }}" class="van-dialog__header {{ message || useSlot ? '' : 'van-dialog--isolated' }}" > <slot wx:if="{{ useTitleSlot }}" name="title" /> <block wx:elif="{{ title }}"> {{ title }}</block> </view> <slot wx:if="{{ useSlot }}" /> <view wx:elif="{{ message }}" class="van-dialog__message {{ title ? 'van-dialog__message--has-title' : '' }} {{ messageAlign ? 'van-dialog__message--' + messageAlign : '' }}" > <text class="van-dialog__message-text">{{ message }}</text> </view> <view class="van-hairline--top van-dialog__footer"> <van-button wx:if="{{ showCancelButton }}" ... > {{ cancelButtonText }} </van-button> <van-button wx:if="{{ showConfirmButton }}" ... > {{ confirmButtonText }} </van-button> </view> </van-popup>
函式式呼叫實現
在前面中通過Dialog函式呼叫來開啟彈出框元件,實現函式式呼叫的核心思路主要是: 通過selectComponent(selector)方法查詢(類似於查詢DOM、Vue中查詢元件例項)對頁面中定義渲染好的dialog元件,手動更新其元件例項的資料。 ** Dialog方法定義如下:
const Dialog: Dialog = options => { options = { ...Dialog.currentOptions,...options }; return new Promise((resolve,reject) => { const context = options.context || getContext(); const dialog = context.selectComponent(options.selector); delete options.context; delete options.selector; if (dialog) { dialog.setData({ onCancel: reject,onConfirm: resolve,...options }); queue.push(dialog); } else { console.warn('未找到 van-dialog 節點,請確認 selector 及 context 是否正確'); } }); };
**
函式式呼叫時候根據傳入的options配置去更新找到的元件例項上的屬性
由微信小程式自定義元件限制不能更新slot,slot需要用元件巢狀來傳入
函式式呼叫中的options會有預設值強制覆蓋掉van-dialog元件屬性處傳入的非id等其他屬性,即函式呼叫的時通過元件傳入的屬性無效
**
Dialog.confirm
確認彈窗
呼叫方法
Dialog.confirm({ selector: '#van-dialog',title: '提示',message: '這裡放置提示內容' })
實現方式
Dialog.confirm = options => Dialog({ showCancelButton: true,...options });
呼叫Dialog時候預設執行定了顯示取消按鈕,其他無區別
Dialog.close
關閉彈窗
呼叫方法
Dialog.close()
實現方式
Dialog.close = () => { queue.forEach(dialog => { dialog.close(); }); queue = []; };
遍歷內部快取的所有呼叫Dialog方法找到的van-dialog元件例項,執行其close方法
Dialog.setDefaultOptions
更改對話方塊預設配置
呼叫方法
Dialog.setDefaultOptions(options)
實現方式
Object.assign(Dialog.currentOptions,options);
通過Object.assign將傳入的預設配置合併到內部Dialog.currentOptions配置上
Dialog.resetDefaultOptions
恢復對話方塊預設配置
呼叫方法
Dialog.resetDefaultOptions()
實現方式
Dialog.resetDefaultOptions = () => { Dialog.currentOptions = { ...Dialog.defaultOptions }; };
恢復Dialog.currentOptions配置為Dialog.defaultOptions
總結
本文講解了vant-weapp元件庫中的彈框元件的基本用法、進階用法、定製主題、自定義內容等用法,同時還更進一步的研究了vant-weapp元件中的popup元件、dialog元件的實現。也只有徹底弄懂了UI框架的封裝思路我們才能更進一步的修改框架來定製化更復雜更貼合專案要求的各種元件,本文按照 由實用到進階再到研究原始碼 的思路為各位研究框架原始碼提供另一種方法。
下一步將會在vant-weapp彈框元件之上封裝一系列實戰的案例,期待你的關注與收藏。
若此文對你有一點點幫助請點個贊鼓勵下作者,畢竟原創不易:)
首發自語雀: www.yuque.com/johniexu/fr…
作者部落格地址: blog.lessing.online/
作者github: github.com/johniexu
以上所述是小編給大家介紹的微信小程式vant彈窗元件的實現方式,希望對大家有所幫助,也非常感謝大家對我們網站的支援!