編寫元件
基本結構
我們今天先來實現這個彈出層:
之前這個元件是一個容器類元件,彈出層可設定載入的html結構,然後再設定各種事件即可,這種元件有一個特點:
① 只提供Header部分以及容器部分
② 容器部分的HTML結構由業務層提供
③ 容器部分對應樣式由業務層提供
我們如果要在小程式中實現這類元件,意味著我們需要往小程式中動態插入WXML結構,我們這裡先做個demo,試試往動態插入WXML是不是可行
1 2 3 4 5 |
this.setData({'wxml': ` <my-component> <view>動態插入的節點</view> </my-component> `}); |
小程式對應設定的資料進行了轉義,所以並不能動態解析,如果站在效能角度思考,不進行動態解析也不是錯誤的;另一方面,一旦小程式能動態解析wxml,那麼可能會湧出各種花式用法,控制力會減低,那麼我們這裡如何解決這個問題呢?
我想的是,直接將業務級wxml結構放到頁面裡面,隱藏起來,需要使用彈出層的時候,直接將之裝載進去,我們來看看是否可行,我們將我們需要展示的結構放到一個模板當中:
1 2 3 4 5 |
<template name="searchbox"> <my-component> <view>動態元件部分</view> </my-component> </template> |
然後,我們在我們主介面中載入模板:
1 2 3 4 5 6 7 8 9 10 |
<import src="mod.searchbox.wxml"/> <view> <my-component> <!-- 這部分內容將被放置在元件 <slot> 的位置上 --> <view>這裡是插入到元件slot中的內容</view> </my-component> </view> <view> <template is="searchbox" /> </view> |
主體結構放到頁面中,我們傳入資料模型或者控制顯示即可,看起來是可行的,於是我們先實現我們基本的樣式,因為業務模組的樣子應該由業務提供,所以對應樣式寫到index.wxss裡面:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 |
.btn-primary { background-color: #00b358; color: #fff; border: 0 none; } .btn, .btn-primary, .btn-secondary, .btn-sub { line-height: 88rpx; height: 88rpx; padding: 0 20rpx; display: inline-block; vertical-align: middle; text-align: center; border-radius: 8rpx; cursor: pointer; font-size: 32rpx; -webkit-box-sizing: border-box; box-sizing: border-box; } .full-width { width: 100%; -webkit-box-sizing: border-box; box-sizing: border-box; } .c-row { width: auto; display: -webkit-box; -webkit-box-orient: horizontal; -webkit-box-direction: normal; -webkit-box-pack: justify; -webkit-box-align: stretch; -webkit-box-lines: single; display: -webkit-flex; -webkit-flex-direction: row; -webkit-justify-content: space-between; -webkit-align-items: strecth; -webkit-align-content: flex-start; -webkit-flex-wrap: nowrap; padding: 20rpx 40rpx; } .c-span3 { width: 25%; -webkit-box-flex: 3; -webkit-flex: 3 3 auto; } .c-span9 { width: 75%; -webkit-box-flex: 9; -webkit-flex: 9 9 auto; } .search-line { position: relative; height: 96rpx; line-height: 96rpx; font-size: 30rpx; font-weight: 600; border-bottom: 1rpx solid #e6e6e6; } .search-line::after { content: ""; display: inline-block; vertical-align: middle; width: 20rpx; height: 20rpx; border-top: 4rpx solid #00b358; border-right: 4rpx solid #00b358; position: absolute; right: 60rpx; top: 50%; margin-top: -4rpx; -webkit-transform: rotate(45deg) translateY(-50%); transform: rotate(45deg) translateY(-50%); -webkit-box-sizing: border-box; box-sizing: border-box; } .search-line-txt { text-align: right; padding-right: 60rpx; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<template name="searchbox"> <view class="c-row search-line" data-flag="start"> <view class="c-span3"> 出發</view> <view class="c-span9 js-start search-line-txt"> 請選擇出發地</view> </view> <view class="c-row search-line" data-flag="arrive"> <view class="c-span3"> 到達</view> <view class="c-span9 js-arrive search-line-txt"> 請選擇到達地</view> </view> <view class="c-row " data-flag="arrive"> <span class="btn-primary full-width js_search_list">查詢</span> </view> </template> |
如此一來,我們基本的彈出層樣式就七七八八了,這裡可以看出一些特點:小程式與平時我們的樣式差距不大,稍微改點就能用,甚至能直接通用;另一方面,我們也需要思考一個問題:公共部分的CSS該怎麼處理?其實我這裡需要解決的不只是公共的樣式部分,還需要解決公共的元件部分。
我這裡想的是將所有公共部分的CSS放到一個全域性的檔案global.wxss中,然後在每個業務級頁面import即可,所以我們這裡需要形成一個公共的WXSS庫,這個與純web對映起來即可,我們這裡便不深入。
公共元件庫
要提高開發效率的第一個前提就是要有足夠多的UI元件,小程式本身提供了一些定製化的元件,我們仍然會用到的元件有:
① alert類彈出層
② loading類彈出層
③ 日曆元件
④ toast&message類提示彈出元件
⑤ 容器類元件
⑥ ……
之前的做法,是我們將html實體和元件實現直接放到一起,css放到全域性global裡面去,現在小程式並不支援動態展示wxml,所以動態插入的方式行不通了,我們需要將元件的wxml放到頁面裡面做預載入,這裡我想的是提供一個通用global.ui.wxml檔案用以裝載所有的wxml實體,常用的元件我們預設全域性引入,我們這裡先挑點軟柿子來捏,我們先實現一個alert類彈出層元件。
我們將原來彈出層類會用到的CSS全部翻譯為WXSS,放入global.wxss中:
然後我們每個元件都會有一個固定的生命週期:建立->顯示->隱藏,這個生命週期是每個元件都具有的特性,所以我們這裡應該引入繼承概念實現元件,但是小程式官方提供的Components並沒有提供繼承概念,而是提供了behaviors概念,用以將元件間的公共部分處理掉,所以我們這裡也使用behaviors,因為不能操作dom,我們的元件抽象會變得相對簡單,不用記錄太多dom節點了,另外小程式的元件與我們之前的“元件”從定義到使用上有很大的不同,之前我們是以js作為控制器,現在是以標籤wxml作為控制器,根本沒有辦法在js中獲取例項,而小程式元件的生命週期並不包含顯示隱藏生命週期,所以他的元件和我們以為的元件有很大的不同
我思考了下為什麼小程式中,js不能獲取元件的例項,這裡得出的結論是:
1 2 |
小程式中所有的WXML必須在頁面中進行預載入邏輯,不能動態插入DOM的方式插入WXML,所以小程式沒有提供元件例項給我們控制 所以在小程式中想完成元件庫,那麼便只能把元件做標籤使用(而且是js不能獲取的標籤),而不是js元件,這樣會有效幫助我們理解 |
我們這裡嘗試實現一個遮蓋層的標籤(這裡開始不用元件這個詞,感覺很有歧義):
程式碼非常簡單:
1 |
<view class="cm-overlay"></view> |
1 2 3 4 5 6 7 8 |
.cm-overlay { background: rgba(0, 0, 0, 0.5); position: fixed; top: 0; right: 0; bottom: 0; left: 0; } |
1 2 3 4 5 6 7 8 9 10 11 12 |
let LayerView = require('behavior-layer-view') Component({ behaviors: [LayerView], data: { myData: {} }, attached: function () { }, methods: { } }) |
可以看到,這個遮蓋層mask沒有什麼意義,而且一般來說mask也不會單獨存在,一般是一個元件(比如彈出層的loading)會包含一個遮蓋層,所以我們這裡要改造下Mask的結構,讓他可以裝載元件,我們從js元件邏輯來說是mask應該是loading的一個例項,但是我們站在標籤角度來說,他們兩個應該是獨立的:
1 2 3 |
<view class="cm-overlay"> <slot></slot> </view> |
我們這裡實現一個loading的元件(PS:CSS3動畫稍微要做點相容除錯):
loading樣式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 |
.spinner { width: 140rpx; height: 140rpx; position: fixed; align-items: center; display: flex; top: 50%; left: 50%; margin-left: -70rpx; margin-top: -70rpx; } .container1 > view, .container2 > view, .container3 > view { width: 24rpx; height: 24rpx; background-color: #00b358; border-radius: 100%; position: absolute; -webkit-animation: bouncedelay 1.2s infinite ease-in-out; animation: bouncedelay 1.2s infinite ease-in-out; -webkit-animation-fill-mode: both; animation-fill-mode: both; } .spinner .spinner-container { position: absolute; width: 66%; height: 66%; top: 10%; left: 10%; } .container2 { -webkit-transform: rotateZ(45deg); transform: rotateZ(45deg); } .container3 { -webkit-transform: rotateZ(90deg); transform: rotateZ(90deg); } .circle1 { top: 0; left: 0; } .circle2 { top: 0; right: 0; } .circle3 { right: 0; bottom: 0; } .circle4 { left: 0; bottom: 0; } .container2 .circle1 { -webkit-animation-delay: -1.1s; animation-delay: -1.1s; } .container3 .circle1 { -webkit-animation-delay: -1.0s; animation-delay: -1.0s; } .container1 .circle2 { -webkit-animation-delay: -0.9s; animation-delay: -0.9s; } .container2 .circle2 { -webkit-animation-delay: -0.8s; animation-delay: -0.8s; } .container3 .circle2 { -webkit-animation-delay: -0.7s; animation-delay: -0.7s; } .container1 .circle3 { -webkit-animation-delay: -0.6s; animation-delay: -0.6s; } .container2 .circle3 { -webkit-animation-delay: -0.5s; animation-delay: -0.5s; } .container3 .circle3 { -webkit-animation-delay: -0.4s; animation-delay: -0.4s; } .container1 .circle4 { -webkit-animation-delay: -0.3s; animation-delay: -0.3s; } .container2 .circle4 { -webkit-animation-delay: -0.2s; animation-delay: -0.2s; } .container3 .circle4 { -webkit-animation-delay: -0.1s; animation-delay: -0.1s; } @-webkit-keyframes bouncedelay { 0%, 80%, 100% { -webkit-transform: scale(0.0) } 40% { -webkit-transform: scale(1.0) } } @keyframes bouncedelay { 0%, 80%, 100% { transform: scale(0.0); -webkit-transform: scale(0.0); } 40% { transform: scale(1.0); -webkit-transform: scale(1.0); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<ui-mask z-index="{{maskzIndex}}" ></ui-mask> <view class="spinner" style="z-index: {{meIndex}}"> <view class="spinner-container container1"> <view class="circle1"></view> <view class="circle2"></view> <view class="circle3"></view> <view class="circle4"></view> </view> <view class="spinner-container container2"> <view class="circle1"></view> <view class="circle2"></view> <view class="circle3"></view> <view class="circle4"></view> </view> <view class="spinner-container container3"> <view class="circle1"></view> <view class="circle2"></view> <view class="circle3"></view> <view class="circle4"></view> </view> </view> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
const util = require('../utils/util.js'); let LayerView = require('behavior-layer-view'); Component({ behaviors: [LayerView], data: { maskzIndex: util.getBiggerzIndex(), meIndex: util.getBiggerzIndex() }, attached: function () { console.log('loading') }, methods: { } }) |
index呼叫情況:
1 2 3 4 5 6 |
<import src="./mod.searchbox.wxml" /> <view> <template is="searchbox" /> <ui-loading></ui-loading> </view> |
我們後續將完整的專案程式碼放到github上去,這裡便繼續程式碼了
新增事件
於是,我們開始新增事件了,這裡新增一個點選遮蓋層關閉整個元件的功能,這裡有個問題是,我們點選遮蓋層事實上關閉的是遮蓋以及loading兩個標籤,而我們這裡的isShow屬性便派上了用處,我們現在page中設定下屬性:
1 |
<ui-loading is-show="{{isLoadingShow}}"></ui-loading> |
1 2 3 4 5 |
onShow: function() { this.setData({ isLoadingShow: '' }); }, |
然後我們改造mask以及loading新增事件:
1 2 |
<view class="cm-overlay" style="z-index: {{zIndex}}; display: {{isShow}}" bindtap="onTap"> </view> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
let LayerView = require('behavior-layer-view') Component({ behaviors: [LayerView], data: { myData: {} }, attached: function () { console.log('mask') }, methods: { onTap: function() { this.triggerEvent('customevent', {}, {}) } } }) |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<ui-mask z-index="{{maskzIndex}}" is-show="{{isShow}}" bindcustomevent="onMaskEvent"></ui-mask> <view class="spinner" style="z-index: {{meIndex}}; display: {{isShow}}; "> <view class="spinner-container container1"> <view class="circle1"></view> <view class="circle2"></view> <view class="circle3"></view> <view class="circle4"></view> </view> <view class="spinner-container container2"> <view class="circle1"></view> <view class="circle2"></view> <view class="circle3"></view> <view class="circle4"></view> </view> <view class="spinner-container container3"> <view class="circle1"></view> <view class="circle2"></view> <view class="circle3"></view> <view class="circle4"></view> </view> </view> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
const util = require('../utils/util.js'); let LayerView = require('behavior-layer-view'); Component({ behaviors: [LayerView], data: { maskzIndex: util.getBiggerzIndex(), meIndex: util.getBiggerzIndex() }, attached: function () { console.log('loading') }, methods: { onMaskEvent: function (e) { console.log(e); this.setData({ isShow: 'none' }); } } }) |
這個時候,當我們點選遮蓋層的時候,我們整個元件便關閉了。
總結
我們今天花了很多功夫寫一個loading,發現小程式中的元件事實上是標籤,我們沒法使用js獲取到我們“元件”的例項,所以使用上有很大的區別,但是什麼都不能阻礙我們寫通用元件的決心,於是我們明天來寫一些通用的元件庫,並且形成一個小程式的體系,這裡想的是有:
① 訊息框
② toast提示
③ 日曆元件
④ 然後再做一個需要定位的氣泡元件