前言
还是微信小程序项目,虽然有一些优秀的第三方组件,但是秉持高度还原UI设计稿的原则,没有直接在项目中使用。遇到一些类似的逻辑时,可以参考一下这些开源项目的实现方式。难的往往只是某一个点,有时候我们需要的只是一张地图(PS:来自硬派脱口秀节目《知识就是力量》)。
开始
为什么研究actionsheet组件?
因为项目中设计较多的交互动画,想要自己同一封装,可多处使用。一方面可以减少代码量,另一方面可以将动画与逻辑分离,更易于管理。
正文
- 首先,需要在github上下载vant-weapp的源码;
- 把项目clone或下载到本地,参照官方说明,编译出可运行的小程序代码;
- 使用微信编辑器打开项目,并把编译出的小程序源码拖进你喜欢的编辑器。
actionsheet示例页面:
actionsheet页面代码:
wxml:
<demo-block title="基础用法" padding>
<van-button bind:click="toggleActionsheet1">弹出 Actionsheet</van-button>
<!-- 监听了close事件 show控制显示和隐藏 -->
<van-actionsheet
show="{{ show1 }}"
actions="{{ actions }}"
bind:close="toggleActionsheet1"
bind:select="toggleActionsheet1"
/>
</demo-block>
<demo-block title="带取消按钮的 Actionsheet" padding>
<van-button bind:click="toggleActionsheet2">弹出带取消按钮的 Actionsheet</van-button>
<van-actionsheet
show="{{ show2 }}"
actions="{{ actions }}"
cancel-text="取消"
bind:close="toggleActionsheet2"
bind:cancel="toggleActionsheet2"
bind:select="toggleActionsheet2"
/>
</demo-block>
<demo-block title="带标题的 Actionsheet" padding>
<van-button bind:click="toggleActionsheet3">弹出带标题的 Actionsheet</van-button>
<van-actionsheet
show="{{ show3 }}"
title="标题"
bind:close="toggleActionsheet3"
>
<view class="content">内容</view>
</van-actionsheet>
</demo-block>
js:
import Page from '../../common/page';
Page({
data: {
show1: false,
show2: false,
show3: false
},
onLoad() {
this.setData({
actions: [
{ name: '选项' },
{ name: '选项', subname: '禁用' },
{ name: '选项', loading: true },
{ name: '禁用选项', disabled: true }
]
});
},
// 设置隐藏 在最外层page里改变了show的值,触发transition.js中show的observer函数,导致所有的动画执行
toggle(type) {
this.setData({
[type]: !this.data[type]
});
},
// 调用方法
toggleActionsheet1() {
this.toggle('show1');
},
toggleActionsheet2() {
this.toggle('show2');
},
toggleActionsheet3() {
this.toggle('show3');
}
});
在示例页面可以看出,是使用了van-actionsheet组件,那我们再去看看van-actionsheet组件的实现。
van-actionsheet组件代码:
<!-- 引入了van-popup组件 -->
<van-popup
show="{{ show }}"
overlay="{{ overlay }}"
close-on-click-overlay="{{ closeOnClickOverlay }}"
custom-class="van-actionsheet {{ title ? 'van-actionsheet--withtitle' : '' }}"
position="bottom"
bind:close="onClose"
>
<!-- 这部分是作为van-popup组件的slot内容插入的 -->
<view wx:if="{{ title }}" class="van-hairline--top-bottom van-actionsheet__header">
<view>{{ title }}</view>
<van-icon custom-class="van-actionsheet__close" name="close" bind:click="onClose" />
</view>
<view wx:else class="van-hairline--bottom">
<view
wx:for="{{ actions }}"
wx:key="index"
class="van-actionsheet__item van-hairline--top {{ item.disabled || item.loading ? 'van-actionsheet__item--disabled' : '' }} {{ item.className || '' }}"
data-index="{{ index }}"
bind:tap="onSelect"
>
<block wx:if="{{ !item.loading }}">
<view class="van-actionsheet__name">{{ item.name }}</view>
<view class="van-actionsheet__subname" wx:if="{{ item.subname }}">{{ item.subname }}</view>
</block>
<van-loading wx:else custom-class="van-actionsheet__loading" size="20px" />
</view>
</view>
<view
wx:if="{{ cancelText }}"
class="van-actionsheet__cancel van-hairline--top"
bind:tap="onCancel"
>
{{ cancelText }}
</view>
<view wx:else class="van-actionsheet__content">
<!-- slot:可以自定义actionsheet内容 -->
<slot />
</view>
</van-popup>
在van-actionsheet组件源码中可以看出,组件内引入了van-popup组件,那我们再去看看van-popup组件的实现。
van-popup组件源码:
<!-- 引入了van-overlay组件 -->
<van-overlay
mask
show="{{ overlay && show }}"
custom-style="{{ overlayStyle }}"
bind:click="onClickOverlay"
/>
<!-- 真正的弹窗内容 -->
<view
wx:if="{{ inited }}"
class="custom-class van-popup {{ position ? 'van-popup--' + position : '' }}"
style="animation-name: van-{{ position }}-{{ type }}; animation-duration: {{ duration }}ms; {{ display ? '' : 'display: none;' }}"
bind:animationend="onAnimationEnd"
>
<!-- slot: 可以自定义弹窗的内容 -->
<slot />
</view>
组件van-popup内又引入了van-overlay组件,那再去看看van-overlay组件的实现。
van-overlay组件源码:
<!-- 好家伙,又是一个组件 -->
<van-transition
show="{{ show }}"
custom-class="van-overlay"
custom-style="z-index: {{ zIndex }}; {{ mask ? 'background-color: rgba(0, 0, 0, .7);' : '' }}; {{ customStyle }}"
bind:tap="onClick"
/>
组件中还是组件。。。再去看看van-transition组件的实现吧。
van-transition组件源码:
wxml:
<!-- name值默认为fade, type值是取transition中的type值(enter/leave) -->
<!-- 终于到头了~ -->
<view
wx:if="{{ inited }}"
class="van-transition custom-class"
style="animation-name: van-{{ name }}-{{ type }}; animation-duration: {{ duration }}ms; {{ display ? '' : 'display: none;' }} {{ customStyle }}"
bind:animationend="onAnimationEnd"
>
<!-- 显示和隐藏通过控制display属性实现,绑定了动画结束的处理函数 -->
<slot />
</view>
wxss: 该部分代码定义的是动画,代码过长,此处就不深入探究CSS动画的实现啦。
js:
// 引入behaviors
import transitionBehaviors from '../behaviors/transition';
Component({
options: {
addGlobalClass: true
},
externalClasses: ['custom-class'],
// 类似于mixins和traits的组件间代码复用机制
// 每个behavior一组属性、数据、生命周期函数和方法,组件引用它时,它的属性、数据和方法会被合并到组件中,生命周期函数也会在对应时机被调用。每个组件可以引用多个 behavior。behavior也可以引用其他behavior 。
behaviors: [transitionBehaviors(true)],
properties: {
name: {
type: String,
value: 'fade'
}
}
});
transition.js:
export default function(showDefaultValue) {
return Behavior({
properties: {
customStyle: String,
// 根据show的值判断是动画type是enter还是leave
show: {
value: showDefaultValue,
type: Boolean,
// 属性值被更改时的响应函数
observer(value) {
if (value) {
this.show();
} else {
this.setData({
type: 'leave'
});
}
}
},
duration: {
type: Number,
value: 300
}
},
data: {
type: '',
inited: false,
display: false
},
attached() {
if (this.data.show) {
this.show();
}
},
methods: {
show() {
this.setData({
inited: true,
display: true,
type: 'enter'
});
},
// 动画结束,只修改了display
onAnimationEnd() {
// 如果false, 就隐藏
if (!this.data.show) {
this.setData({
display: false
});
}
}
}
});
}
总结
很细的组件化开发,分工明确,耦合度低。对behavior进行了同一的管理,组件behaviors+observer的结合使用,完美~
之前参考vant-weapp slider组件的实现,自己做了一个双向滑动的slider,收获颇多。致敬这些优秀的开源项目,致敬那些优秀的程序员~路还长
如果你有什么疑问或想法,欢迎留言评论,或者扫描下方二维码,与我取得联系~ (记得备注:CSND喔~)