本文主要讲解van-popup组件
1.组件的什么周期
在讲解这个组件前,先讲解一个组件生命周期的知识点,请看以下代码,然后说出日志分别是什么,能说出正确答案的人应该不多。
js
Component({
data: {
a:0
},
attached: function(){
console.log('attached a:',this.data.a)
this.setData({
a:1
},()=>{
console.log('attached a2:',this.data.a)
})
},
ready: function(){
console.log('ready a:',this.data.a)
}
});
我原来日志是这样的
js
attached a 0
ready a 0
attached a2 1
实际打印是这样的
js
attached a 0
ready a 1
attached a2 1
主要区别是ready打印的值是1,而不是0。我以为是0,是因为我一直以为setData
方法是异步的,直到真实测试下,才发现不是,于是我赶紧去看了看文档,文档是这样写的。
setData
函数用于将数据从逻辑层发送到视图层(异步),同时改变对应的this.data
的值(同步)。
文档地址:框架接口 / 页面 / Page
看这段话的意思,就是setData
会立即改变this.data的值(同步),但是要改变视图层,就要等到js队列里的任务都执行完才行(异步)
又学到了一个知识点。
2.popup组件
2.1先看下index.wxml代码
html
<wxs src="../wxs/utils.wxs" module="utils" />
<wxs src="./index.wxs" module="computed" />
<import src="./popup.wxml" />
<van-overlay
wx:if="{{ overlay }}"
show="{{ show }}"
z-index="{{ zIndex }}"
custom-style="{{ overlayStyle }}"
duration="{{ duration }}"
bind:click="onClickOverlay"
lock-scroll="{{ lockScroll }}"
root-portal="{{ rootPortal }}"
/>
<root-portal wx:if="{{ rootPortal }}">
<include src="./popup.wxml" />
</root-portal>
<include wx:else src="./popup.wxml" />
最上面引入了utils和computed两个module,这两个module主要用来给组件设置class和style的。
主要代码放在popup.wxml
文件中,在index.wxml中,通过import引入popup.wxml
中的template
代码(其实这句没用,因为popup.wxml中没有用template代码),通过include引入了popup.wxml
中除template
以外的全部代码。
如果用户设置了overlay
属性,就使用van-overlay
组件设置阴影。
如果用户设置了rootPortal
属性,就把popup.wxml
放在root-portal
节点下,否则就直接放在引入的位置。
2.2 popup.wxml代码
html
<wxs src="../wxs/utils.wxs" module="utils" />
<wxs src="./index.wxs" module="computed" />
<view
wx:if="{{ inited }}"
class="custom-class {{ classes }} {{ utils.bem('popup', [position, { round, safe: safeAreaInsetBottom, safeTop: safeAreaInsetTop, safeTabBar: safeAreaTabBar }]) }}"
style="{{ computed.popupStyle({ zIndex, currentDuration, display, customStyle }) }}"
bind:transitionend="onTransitionEnd"
>
<slot />
<van-icon
wx:if="{{ closeable }}"
name="{{ closeIcon }}"
class="close-icon-class van-popup__close-icon van-popup__close-icon--{{ closeIconPosition }}"
bind:tap="onClickCloseIcon"
/>
</view>
这个代码跟transition
组件的代码非常像。我们一点点讲解。
3 动画的实现方式
首先讲动画实现方式,view动画是通过给class属性中的classes
变量,在不同的时间,赋予不同的值来实现的。
代码分两部分,一部分在index.ts
文件中,一部分在transition.ts
文件中。
在vant组件created生命周期(对应就是小程序的attached生命周期)中初始化动画名字name和动画时间duration。
动画的名字name来自于用户传进来的属性transition
和position
,transition
优先级更高。这个是在index.ts
文件中定义的。
动画时间duration来自用户传过来的属性duration,如果用户不传的话,默认值是300ms。这个是在transition.ts
文件中定义的。
js
created() {
this.observeClass();
}
js
observeClass() {
const { transition, position, duration } = this.data;
const updateData: { [key: string]: any } = {
name: transition || position,
};
if (transition === 'none') {
updateData.duration = 0;
this.originDuration = duration;
} else if (this.originDuration != null) {
updateData.duration = this.originDuration;
}
this.setData(updateData);
}
如果show为true,就直接执行监听show
属性的函数observeShow
,否则监听show
属性的变化,当show
属性改变的时候,再执行监听函数。
这个函数的主要作用就是当show
为true的时候,执行enureEnter
函数(进场动画),当show
为false的时候执行enureLeave
函数(离场动画)
在enureEnter
函数中,为了确保动画唯一,使用了promise函数,只有动画执行完毕时,才resolve。
主要的动画是在enter
函数里,通过两个定时器来动态改变classes变量来实现。
1.首先初始化变量
js
const { duration, name } = this.data;
const classNames = getClassNames(name);
const currentDuration = isObj(duration) ? duration.enter : duration;
getClassNames
函数会根据传入的name值,生成相应的className,它的返回值一个对象,我们不同的时间,使用它不同的值。
js
const getClassNames = (name: string) => ({
enter: `van-${name}-enter van-${name}-enter-active enter-class enter-active-class`,
'enter-to': `van-${name}-enter-to van-${name}-enter-active enter-to-class enter-active-class`,
leave: `van-${name}-leave van-${name}-leave-active leave-class leave-active-class`,
'leave-to': `van-${name}-leave-to van-${name}-leave-active leave-to-class leave-active-class`,
});
2.初始化进入样式
主要代码是这个
php
this.setData({
inited: true,
display: true,
classes: classNames.enter,
currentDuration,
});
此时classes
变量是classNames.enter
,我们如果使用默认的position的话(默认值是center)。对应的值就是
van-center-enter van-center-enter-active enter-class enter-active-class
这些class样式已经提前在class中定义好了,这时候就是把view的透明度设置成0,也就是先把view隐藏起来。
index.less文件
css
.van-center-enter,
.van-center-leave-to {
opacity: 0;
}
3.执行动画
在下一个定时器里,我们给classes设置新的变量
this.setData({ classes: classNames['enter-to'] });
此时对应的classes值是van-center-enter-to van-center-enter-active enter-to-class enter-active-class
这些class在css中没有定义,因为我们给classes赋予新的值了,就相当于移除了之前设置van-center-enter
样式。
view的透明度就由初始的0,变成了默认值1。
浏览器检测到后,就会执行对应的动画,将view的透明度由0变成1,从而实现一个淡入的效果。
其他动画与此同理,都是通过修改不同的classes变量来实现的,相应的class样式都在css文件中有定义,这里就不赘述了
4 position和round
弹出位置和是否显示圆角是通过生成不同的class来实现的。这里使用utils.bem工具函数,可以生成符合bem规范的class名称
代码如下:
js
utils.bem('popup', [position, { round, safe: safeAreaInsetBottom, safeTop: safeAreaInsetTop, safeTabBar: safeAreaTabBar }])
比如当position
为top
的时候,会生成一个值为van-popup--top
的class,如果round
为true,会生成一个c值为van-popup--round
的class。
相应的样式,都在css文件中定义好了。
css
.van-popup {
position: fixed;
box-sizing: border-box;
max-height: 100%;
overflow-y: auto;
transition-timing-function: ease;
animation: ease both;
-webkit-overflow-scrolling: touch;
background-color: var(--popup-background-color, @popup-background-color);
&--top {
top: 0;
left: 0;
width: 100%;
&.van-popup--round {
border-radius: 0 0
var(
--popup-round-border-radius,
var(--popup-round-border-radius, @popup-round-border-radius)
)
var(
--popup-round-border-radius,
var(--popup-round-border-radius, @popup-round-border-radius)
);
}
}
5 icon
如果用户设置closeable
为true,就是显示关闭图标。这里引用了van-icon
组件
html
<van-icon
wx:if="{{ closeable }}"
name="{{ closeIcon }}"
class="close-icon-class van-popup__close-icon van-popup__close-icon--{{ closeIconPosition }}"
bind:tap="onClickCloseIcon"
/>
用户可以通过给closeIcon
属性设置不同的值,选择不同的icon图标,或者使用图片链接。
通过给close-icon-position
设置不同的值,修改图标的位置。比如设置为'top-left'。van-popup__close-icon--{{ closeIconPosition }}
就变成了van-popup__close-icon--top-left
相应的样式,已经在css文件中定义了。
css
.van-popup {
&__close-icon {
position: absolute;
z-index: var(--popup-close-icon-z-index, @popup-close-icon-z-index);
color: var(--popup-close-icon-color, @popup-close-icon-color);
font-size: var(--popup-close-icon-size, @popup-close-icon-size);
&--top-left {
top: var(--popup-close-icon-margin, @popup-close-icon-margin);
left: var(--popup-close-icon-margin, @popup-close-icon-margin);
}
}
}
别的就没什么好说的了,这个组件也是蛮简单的。