vant-weapp源码解读(2)

本文主要讲解,transitionoverlay两个组件

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-tovan-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 引入的是目标文件中除了templatewxs以外的部分。相当于拷贝。

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组件都是比较简单的,这里就不过多讲解了

相关推荐
用户857594145002913 小时前
面试官问你:flex:0 和 flex:auto 的区别?你该如何回答?
前端
渊不语13 小时前
React + Webpack + React Router + TypeScript + Ant Design 多子项目工程化
前端
路修远i13 小时前
项目中JSSDK封装方案
前端·架构
一蓑烟雨,一任平生13 小时前
h5实现内嵌微信小程序&支付宝 --截图保存海报分享功能
开发语言·前端·javascript
他们都不看好你,偏偏你最不争气13 小时前
【iOS】MVC架构
前端·ios·mvc·objective-c·面向对象
苏纪云13 小时前
Ajax笔记(下)
前端·javascript·笔记·ajax
三十_A14 小时前
【NestJS】HTTP 接口传参的 5 种方式(含前端调用与后端接收)
前端·网络协议·http
几个高兴14 小时前
ES6和CommonJS模块区别
前端
渊不语14 小时前
Webpack 深入学习指南
前端·webpack