本文主要讲解,transition
和overlay
两个组件
1.transition组件
如果对小程序动画不是很了解的,可以看看我上一篇文章,小程序中动画的几种实现方式做小程序很久了,但是动画做的很少,一提到动画,总是就感觉无从下手,今天正好有时间就把小程序中的 - 掘金
transition组件使用的是css中的transition来实现动画效果的。
index.wxml代码很简单。
html
<wxs src="./index.wxs" module="computed" />
<view
wx:if="{{ inited }}"
class="van-transition custom-class {{ classes }}"
style="{{ computed.rootStyle({ currentDuration, display, customStyle }) }}"
bind:transitionend="onTransitionEnd"
>
<slot />
</view>
第一行引入了一个叫computed的wxs模块,主要是根据用户传入的duration、customStyle等变量,计算最终的样式。
动画效果是通过class属性引入的classes
变量实现的。通过一个定时器,在不同的时间,给classes
赋不同的值,动态的改变view的class属性。不同的class属性,对应不同的样式,浏览器检测到不同的样式的时候,就会执行过渡动画。
代码里预先写好了:淡入、上滑淡入、下滑淡入、左滑淡入、右滑淡入......等效果,这些效果就是通过不同的css样式来实现的,这里没啥好解释的,代码如下。
css代码如下:
css
.van-transition {
transition-timing-function: ease;
}
.van-fade-enter-active,
.van-fade-leave-active {
transition-property: opacity;
}
.van-fade-enter,
.van-fade-leave-to {
opacity: 0;
}
.van-fade-up-enter-active,
.van-fade-up-leave-active,
.van-fade-down-enter-active,
.van-fade-down-leave-active,
.van-fade-left-enter-active,
.van-fade-left-leave-active,
.van-fade-right-enter-active,
.van-fade-right-leave-active {
transition-property: opacity, transform;
}
.van-fade-up-enter,
.van-fade-up-leave-to {
transform: translate3d(0, 100%, 0);
opacity: 0;
}
.van-fade-down-enter,
.van-fade-down-leave-to {
transform: translate3d(0, -100%, 0);
opacity: 0;
}
.van-fade-left-enter,
.van-fade-left-leave-to {
transform: translate3d(-100%, 0, 0);
opacity: 0;
}
.van-fade-right-enter,
.van-fade-right-leave-to {
transform: translate3d(100%, 0, 0);
opacity: 0;
}
.van-slide-up-enter-active,
.van-slide-up-leave-active,
.van-slide-down-enter-active,
.van-slide-down-leave-active,
.van-slide-left-enter-active,
.van-slide-left-leave-active,
.van-slide-right-enter-active,
.van-slide-right-leave-active {
transition-property: transform;
}
.van-slide-up-enter,
.van-slide-up-leave-to {
transform: translate3d(0, 100%, 0);
}
.van-slide-down-enter,
.van-slide-down-leave-to {
transform: translate3d(0, -100%, 0);
}
.van-slide-left-enter,
.van-slide-left-leave-to {
transform: translate3d(-100%, 0, 0);
}
.van-slide-right-enter,
.van-slide-right-leave-to {
transform: translate3d(100%, 0, 0);
}
重点是js代码
index.ts
代码通过mixins (对应小程序就是behaviors) 引入了transition
函数。动画的中间就是transition
文件
js
import { VantComponent } from '../common/component';
import { transition } from '../mixins/transition';
VantComponent({
classes: [
'enter-class',
'enter-active-class',
'enter-to-class',
'leave-class',
'leave-active-class',
'leave-to-class',
],
mixins: [transition(true)],
});
在ready
生命周期里,监听属性show
的变化,当show
为true的时候,执行进入函数enureEnter
,当show
为false的时候,执行退出函数enureLeave
为了保证一次只进行一次动画,在enureEnter
函数使用了promise,只有当动画运行完毕后,promise才会resolve。
js
if (this.enterPromise) return;
this.enterPromise = new Promise((resolve) => this.enter(resolve));
enter函数
enter函数代码分3不部分:
1.动画开始前,先触发了before-enter
钩子函数
js
this.status = 'enter';
this.$emit('before-enter');
2.接着使用了1个定时器。触发了enter
钩子函数,然后再将classes
变量改成了classNames.enter
,对应的class就是van-fade-enter
,这时候相当于初始化,将元素的透明度设置成0。
js
this.$emit('enter');
this.setData({
inited: true,
display: true,
classes: classNames.enter,
currentDuration,
});
css
.van-fade-enter,
.van-fade-leave-to {
opacity: 0;
}
3.然后又使用了一个定时器,将classes
变量改成了van-fade-enter-to
,van-fade-enter-to
没有对应的样式,但是因为classes变量改了。这时候相当于移除了之前添加的van-fade-enter
样式,也就是移除了opacity:0
样式,元素的透明度就会由0变成1,浏览器检测到后,就会执行过渡效果,从而实现淡入的动画。
js
this.setData({ classes: classNames['enter-to'] });
resolve();
代码非常简单,退出动画也是同理。
完整代码如下
transition
js
// @ts-nocheck
import { requestAnimationFrame } from '../common/utils';
import { isObj } from '../common/validator';
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`,
});
export function transition(showDefaultValue: boolean) {
return Behavior({
properties: {
customStyle: String,
// @ts-ignore
show: {
type: Boolean,
value: showDefaultValue,
observer: 'observeShow',
},
// @ts-ignore
duration: {
type: null,
value: 300,
},
name: {
type: String,
value: 'fade',
},
},
data: {
type: '',
inited: false,
display: false,
},
ready() {
if (this.data.show === true) {
this.observeShow(true, false);
}
},
methods: {
observeShow(value: boolean, old: boolean) {
if (value === old) {
return;
}
value ? this.enureEnter() : this.enureLeave();
},
enureEnter() {
if (this.enterPromise) return;
this.enterPromise = new Promise((resolve) => this.enter(resolve));
},
enureLeave() {
const { enterPromise } = this;
if (!enterPromise) return;
enterPromise
.then(() => new Promise((resolve) => this.leave(resolve)))
.then(() => {
this.enterPromise = null;
});
},
enter(resolve: () => void) {
const { duration, name } = this.data;
const classNames = getClassNames(name);
const currentDuration = isObj(duration) ? duration.enter : duration;
if (this.status === 'enter') {
return;
}
this.status = 'enter';
this.$emit('before-enter');
requestAnimationFrame(() => {
if (this.status !== 'enter') {
return;
}
this.$emit('enter');
this.setData({
inited: true,
display: true,
classes: classNames.enter,
currentDuration,
});
requestAnimationFrame(() => {
if (this.status !== 'enter') {
return;
}
this.transitionEnded = false;
this.setData({ classes: classNames['enter-to'] });
resolve();
});
});
},
leave(resolve: () => void) {
if (!this.data.display) {
return;
}
const { duration, name } = this.data;
const classNames = getClassNames(name);
const currentDuration = isObj(duration) ? duration.leave : duration;
this.status = 'leave';
this.$emit('before-leave');
requestAnimationFrame(() => {
if (this.status !== 'leave') {
return;
}
this.$emit('leave');
this.setData({
classes: classNames.leave,
currentDuration,
});
requestAnimationFrame(() => {
if (this.status !== 'leave') {
return;
}
this.transitionEnded = false;
setTimeout(() => {
this.onTransitionEnd();
resolve();
}, currentDuration);
this.setData({ classes: classNames['leave-to'] });
});
});
},
onTransitionEnd() {
if (this.transitionEnded) {
return;
}
this.transitionEnded = true;
this.$emit(`after-${this.status}`);
const { show, display } = this.data;
if (!show && display) {
this.setData({ display: false });
}
},
},
});
}
transition组件还是非常简单的,这里就不再赘述了。
2.小程序import和include的区别
import 和 include 是wxml提供的两种引入其他文件的方式。
- import 引入文件后,可以使用文件中的
template
部分(当然也可以不使用) - include 引入的是目标文件中除了
template
和wxs
以外的部分。相当于拷贝。
import用法
我们新建一个import.wxml,代码如下,在代码中我们声明了一个name为import-template1
的template
html
<view class="import">
<view class="item">这是import</view>
<template name="import-template1">
<view class="item">{{text}}</view>
</template>
</view>
再新建一个index.wxml,代码如下:
html
<!--index.wxml-->
<import src="./import.wxml" />
<view class="main">
<template is="import-template1" data="{{text:'我来自index'}}" />
</view>
我们首先使用import引入了import.wxml。然后通过is=import-template1
引用了import.wxml中的template
,并且把data参数传给了它。
效果如下:

因为是采用import方式引入import.wxml的,所以import.wxml中的"这是import"没有显示出来。
include的用法
修改index.wxml代码如下所示。import.wxml代码不用改。
html
<!--index.wxml-->
<view class="main">
<include src="./import.wxml" />
</view>
效果如下:

import.wxml中template部分就没有显示出来。
3.overlay 组件
这个组件很简单,基本上没啥可以讲的知识点。
index.wxml
html
<import src="./overlay.wxml" />
<root-portal wx:if="{{ rootPortal }}">
<include src="./overlay.wxml" />
</root-portal>
<include wx:else src="./overlay.wxml" />
这里既使用了import也使用了include,引入了overlay.wxml文件。overlay.wxml文件是这个组件的核心代码。vant-weapp很多组件都是这样引用代码的
值得一提的就是rootPortal
属性,如果用户使用rootPortal
属性,就把代码放在root-portal
标签下。如果没有,就把代码直接放在引入组件的位置。
root-portal
是微信小程序的一个标签,可以是子节点从文档树中脱离出来,类似css中的fixed position。主要用于制作弹窗弹出层。
overlay.wxml文件
html
<van-transition
show="{{ show }}"
custom-class="van-overlay custom-class"
custom-style="z-index: {{ zIndex }}; {{ customStyle }}"
duration="{{ duration }}"
bind:tap="onClick"
catch:touchmove="{{ lockScroll ? 'noop' : ''}}"
>
<slot></slot>
</van-transition>
从这代码可以看出来,overlay.wxml组件本质上就是vant-transition
组件,只不过添加了一些overlay
的css样式和slot
插槽。
css 样式
css
.van-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: var(--overlay-background-color, @overlay-background-color);
}
还有一点值得一提,就是使用catch捕获touchmove
事件。如果用户设置了lockScroll
,就给touchmove
赋值一个空函数noop
,否则就赋值空,不捕获touchmove事件。
js代码
js
import { VantComponent } from '../common/component';
VantComponent({
props: {
show: Boolean,
customStyle: String,
duration: {
type: null,
value: 300,
},
zIndex: {
type: Number,
value: 1,
},
lockScroll: {
type: Boolean,
value: true,
},
rootPortal: {
type: Boolean,
value: false,
},
},
methods: {
onClick() {
this.$emit('click');
},
// for prevent touchmove
noop() {},
},
});
transition和overlay组件都是比较简单的,这里就不过多讲解了