Browse Source

feat(animate): ug (#1398)

* feat:动效

* feat: 单元测试demo国际化

Co-authored-by: lijiamiao3 <lijiamiao3@jd.com>
Co-authored-by: richard1015 <51844712@qq.com>
mushroomli 3 years ago
parent
commit
df6d68ddfd

+ 12 - 0
src/config.json

@@ -1057,6 +1057,18 @@
         },
         {
           "version": "3.0.0",
+          "name": "Animate",
+          "cType": "展示组件",
+          "cName": "动画/动效",
+          "desc": "给页面元素添加动画效果",
+          "show": true,
+          "taro": true,
+          "tarodoc": true,
+          "type": "component",
+          "author": "lijiamiao"
+        },
+        {
+          "version": "3.0.0",
           "name": "Ellipsis",
           "cType": "展示组件",
           "cName": "文本省略",

+ 96 - 0
src/packages/__VUE/animate/__tests__/animate.spec.ts

@@ -0,0 +1,96 @@
+import { mount } from '@vue/test-utils';
+import Animate from '@/packages/__VUE/animate/index.vue';
+import { nextTick } from 'vue';
+
+const testType = [
+  'shake',
+  'ripple',
+  'breath',
+  'float',
+  'slide-right',
+  'slide-left',
+  'slide-top',
+  'slide-bottom',
+  'jump',
+  'twinkle',
+  'flicker'
+];
+
+test('should change classname when using type prop', () => {
+  for (let i = 0; i < testType.length; i++) {
+    const typeProp = testType[i];
+    const wrapper = mount(Animate, {
+      props: {
+        type: typeProp
+      }
+    });
+
+    const animate: any = wrapper.find('.nut-ani-container');
+    expect(animate.classes(`nut-animate-${typeProp}`)).toBe(true);
+  }
+});
+
+test('trigger animate with loop', async () => {
+  const handleClick = () => {
+    console.log('click it');
+  };
+  for (let i = 0; i < testType.length; i++) {
+    const typeProp = testType[i];
+    const wrapper = mount(Animate, {
+      props: {
+        type: typeProp,
+        action: 'click',
+        loop: true,
+        click: handleClick
+      }
+    });
+
+    const animate: any = wrapper.find('.nut-ani-container');
+
+    animate.trigger('click');
+    await nextTick();
+    expect(wrapper.emitted('click')).toHaveLength(1);
+
+    expect(animate.classes('loop')).toBe(true);
+    expect(animate.classes(`nut-animate-${typeProp}`)).toBe(true);
+    expect(wrapper.vm.clicked).toBe(true);
+
+    setTimeout(() => {
+      expect(animate.classes('loop')).toBe(true);
+      expect(animate.classes(`nut-animate-${typeProp}`)).toBe(true);
+      expect(wrapper.vm.clicked).toBe(true);
+    }, 1500);
+  }
+});
+
+test('trigger animate', async () => {
+  const handleClick = () => {
+    console.log('click it');
+  };
+  for (let i = 0; i < testType.length; i++) {
+    const typeProp = testType[i];
+    const wrapper = mount(Animate, {
+      props: {
+        type: typeProp,
+        action: 'click',
+        click: handleClick
+      }
+    });
+
+    const animate: any = wrapper.find('.nut-ani-container');
+
+    animate.trigger('click');
+    await nextTick();
+    expect(wrapper.emitted('click')).toHaveLength(1);
+
+    expect(animate.classes('loop')).toBe(false);
+    expect(animate.classes(`nut-animate-${typeProp}`)).toBe(true);
+    expect(wrapper.vm.clicked).toBe(true);
+
+    setTimeout(() => {
+      expect(animate.classes('loop')).toBe(false);
+      expect(animate.classes(`nut-animate-${typeProp}`)).toBe(false);
+      expect(wrapper.vm.clicked).toBe(false);
+    }, 1500);
+  }
+});

+ 131 - 0
src/packages/__VUE/animate/demo.vue

@@ -0,0 +1,131 @@
+<template>
+  <div class="demo">
+    <h2>{{ translate('click') }}</h2>
+
+    <div class="ani-demo-div">
+      <nut-animate type="slide-right" action="click">
+        <nut-button type="primary">{{ translate('FRTL') }}</nut-button>
+      </nut-animate>
+    </div>
+
+    <div class="ani-demo-div">
+      <nut-animate type="slide-left" action="click">
+        <nut-button type="primary">{{ translate('FLTR') }}</nut-button>
+      </nut-animate>
+    </div>
+
+    <div class="ani-demo-div">
+      <nut-animate type="slide-top" action="click">
+        <nut-button type="primary">{{ translate('FTTB') }}</nut-button>
+      </nut-animate>
+    </div>
+
+    <div class="ani-demo-div">
+      <nut-animate type="slide-bottom" action="click">
+        <nut-button type="primary">{{ translate('FBTT') }}</nut-button>
+      </nut-animate>
+    </div>
+
+    <h2>{{ translate('basic') }}</h2>
+
+    <div class="ani-demo-div">
+      <nut-animate type="shake" :loop="true">
+        <nut-button type="primary">{{ translate('shake') }}</nut-button>
+      </nut-animate>
+    </div>
+
+    <div class="ani-demo-div">
+      <nut-animate type="ripple" :loop="true">
+        <nut-button type="primary">{{ translate('ripple') }}</nut-button>
+      </nut-animate>
+    </div>
+
+    <div class="ani-demo-div">
+      <nut-animate type="breath" :loop="true">
+        <nut-button type="primary">{{ translate('breath') }}</nut-button>
+      </nut-animate>
+    </div>
+
+    <div class="ani-demo-div">
+      <nut-animate type="twinkle" :loop="true">
+        <nut-button type="primary">{{ translate('twinkle') }}</nut-button>
+      </nut-animate>
+    </div>
+
+    <div class="ani-demo-div">
+      <nut-animate type="flicker" :loop="true">
+        <nut-button type="primary">{{ translate('flicker') }}</nut-button>
+      </nut-animate>
+    </div>
+
+    <div class="ani-demo-div">
+      <nut-animate type="jump" :loop="true">
+        <nut-button type="primary">{{ translate('jump') }}</nut-button>
+      </nut-animate>
+    </div>
+
+    <div class="ani-demo-div">
+      <nut-animate type="float" :loop="true">
+        <nut-button type="primary">{{ translate('float') }}</nut-button>
+      </nut-animate>
+    </div>
+  </div>
+</template>
+<script lang="ts">
+import { createComponent } from '@/packages/utils/create';
+
+const { createDemo, translate } = createComponent('animate');
+import { useTranslate } from '@/sites/assets/util/useTranslate';
+
+useTranslate({
+  'zh-CN': {
+    basic: '循环动画',
+    click: '点击触发',
+    FRTL: '由右向左划入',
+    FLTR: '由左向右划入',
+    FTTB: '由上至下划入',
+    FBTT: '由下至上划入',
+    shake: 'shake-抖动',
+    ripple: 'ripple-心跳',
+    breath: 'breath-呼吸灯',
+    twinkle: 'twinkle-水波',
+    flicker: 'flicker-擦亮',
+    jump: 'jump-跳跃',
+    float: 'float-漂浮'
+  },
+  'en-US': {
+    basic: 'Loop animation',
+    FRTL: 'From right to left',
+    FLTR: 'From left to right',
+    FTTB: 'From top to bottom',
+    FBTT: 'From bottom to top',
+    shake: 'shake-抖动',
+    ripple: 'ripple-ripple',
+    breath: 'breath-breath',
+    twinkle: 'twinkle-twinkle',
+    flicker: 'flicker-flicker',
+    jump: 'jump-jump',
+    float: 'float-float'
+  }
+});
+export default createDemo({
+  props: {},
+  setup() {
+    return { translate };
+  }
+});
+</script>
+<style lang="scss" scoped>
+.demo {
+}
+
+.ani-demo-div {
+  margin-bottom: 10px;
+  display: inline-block;
+  width: 100%;
+}
+
+.ani-demo-div2 {
+  margin-top: 60px;
+}
+</style>

+ 135 - 0
src/packages/__VUE/animate/doc.en-US.md

@@ -0,0 +1,135 @@
+# animate
+
+### Intro
+
+Add animation effects to child elements
+
+### Install
+
+```javascript
+
+import { createApp } from 'vue';
+// vue
+import { Animate } from '@nutui/nutui';
+// taro
+import { Animate } from '@nutui/nutui-taro';
+
+const app = createApp(Animate);
+app.use(Animate);
+
+```
+
+### Clicking to trigger
+
+:::demo
+
+```html
+<template>
+  <nut-animate type='slide-right' action='click'>
+    <nut-button type='primary'>From right to left</nut-button>
+  </nut-animate>
+
+  <nut-animate type='slide-left' action='click'>
+    <nut-button type='primary'>From left to right</nut-button>
+  </nut-animate>
+  
+  <nut-animate type='slide-top' action='click'>
+    <nut-button type='primary'>From top to bottom</nut-button>
+  </nut-animate>
+
+  <nut-animate type='slide-bottom' action='click'>
+    <nut-button type='primary'>From bottom to top</nut-button>
+  </nut-animate>
+ 
+</template>
+<script lang="ts">
+  export default {
+    setup() {
+      return {  };
+    }
+  };
+</script>
+```
+
+:::
+
+
+
+### Loop animation
+
+:::demo
+
+```html
+<template>
+  <nut-animate type='shake' :loop='true'>
+    <nut-button type='primary'>shake-shake</nut-button>
+  </nut-animate>
+
+  <nut-animate type='ripple' :loop='true'>
+    <nut-button type='primary'>ripple-ripple</nut-button>
+  </nut-animate>
+  
+  <nut-animate type='breath' :loop='true'>
+    <nut-button type='primary'>breath-breath</nut-button>
+  </nut-animate>
+
+  <nut-animate type='twinkle' :loop='true'>
+    <nut-button type='primary'>twinkle-twinkle</nut-button>
+  </nut-animate>
+  
+  <nut-animate type='flicker' :loop='true'>
+    <nut-button type='primary'>flicker-flicker</nut-button>
+  </nut-animate>
+
+  <nut-animate type='jump' :loop='true'>
+    <nut-button type='primary'>jump-jump</nut-button>
+  </nut-animate>
+
+  <nut-animate type='float' :loop='true'>
+    <nut-button type='primary'>float-float</nut-button>
+  </nut-animate>
+</template>
+<script lang="ts">
+  export default {
+    setup() {
+      return {  };
+    }
+  };
+</script>
+```
+
+:::
+
+
+## API
+
+### Props
+
+| Attribute         | Description                             | Type   | Default           |
+|--------------|----------------------------------|--------|------------------|
+| type         | For animation type, see the description of type value below               | String | -                |
+| action         | Triggering method,'initial'-- initialization execution; ' Click'-- Click to execute              | String | 默认'initial'             |
+| loop         | Whether to execute circularly. True: loop execution; False: execute once              | Boolean | false               |
+
+### Events
+
+| Event | Description           | Arguments     |
+|--------|----------------|--------------|
+| click  | Triggered when an element is clicked | event: Event |
+
+### Type value description
+
+
+|    Order  |    Type name     |      Description     |
+|:-------|:------- | :----------|
+| 1|   shake  | shake,It is recommended that loop be true
+| 2 |   ripple  | ripple
+|3 |   breath  | breath,It is recommended that loop be true
+|4 |   float  | float,It is recommended that loop be true
+|5|   slide-right  | From right to left
+|6 |   slide-left  | From left to right
+|7|   slide-top  | From top to bottom
+| 8 |   slide-bottom  | From bottom to top
+|9 |   jump  | jump,It is recommended that loop be true
+|10 |   twinkle  | twinkle,It is recommended that loop be true
+|11 |   flicker  | Polish button,It is recommended that loop be true

+ 134 - 0
src/packages/__VUE/animate/doc.md

@@ -0,0 +1,134 @@
+# animate 
+
+### 介绍
+
+给子元素添加动画效果
+
+### 安装
+
+```javascript
+
+import { createApp } from 'vue';
+// vue
+import { Animate } from '@nutui/nutui';
+// taro
+import { Animate } from '@nutui/nutui-taro';
+
+const app = createApp(Animate);
+app.use(Animate);
+
+```
+
+### 点击触发
+
+:::demo
+
+```html
+<template>
+  <nut-animate type='slide-right' action='click'>
+    <nut-button type='primary'>由右向左划入</nut-button>
+  </nut-animate>
+
+  <nut-animate type='slide-left' action='click'>
+    <nut-button type='primary'>由左向右划入</nut-button>
+  </nut-animate>
+  
+  <nut-animate type='slide-top' action='click'>
+    <nut-button type='primary'>由上至下划入</nut-button>
+  </nut-animate>
+
+  <nut-animate type='slide-bottom' action='click'>
+    <nut-button type='primary'>由下至上划入</nut-button>
+  </nut-animate>
+ 
+</template>
+<script lang="ts">
+  export default {
+    setup() {
+      return {  };
+    }
+  };
+</script>
+```
+
+:::
+
+
+
+### 循环动画
+
+:::demo
+
+```html
+<template>
+  <nut-animate type='shake' :loop='true'>
+    <nut-button type='primary'>shake-抖动</nut-button>
+  </nut-animate>
+
+  <nut-animate type='ripple' :loop='true'>
+    <nut-button type='primary'>ripple-心跳</nut-button>
+  </nut-animate>
+  
+  <nut-animate type='breath' :loop='true'>
+    <nut-button type='primary'>breath-呼吸灯</nut-button>
+  </nut-animate>
+
+  <nut-animate type='twinkle' :loop='true'>
+    <nut-button type='primary'>twinkle-水波</nut-button>
+  </nut-animate>
+  
+  <nut-animate type='flicker' :loop='true'>
+    <nut-button type='primary'>flicker-擦亮</nut-button>
+  </nut-animate>
+
+  <nut-animate type='jump' :loop='true'>
+    <nut-button type='primary'>jump-跳跃</nut-button>
+  </nut-animate>
+
+  <nut-animate type='float' :loop='true'>
+    <nut-button type='primary'>float-漂浮</nut-button>
+  </nut-animate>
+</template>
+<script lang="ts">
+  export default {
+    setup() {
+      return {  };
+    }
+  };
+</script>
+```
+
+:::
+
+
+## API
+
+### Props
+
+| 参数         | 说明                             | 类型   | 默认值           |
+|--------------|----------------------------------|--------|------------------|
+| type         | 动画类型,见下方type值说明               | String | -                |
+| action         | 触发方式,'initial'--初始化执行;  'click'--点击执行              | String | 'initial'             |
+| loop         | 是否循环执行。true-循环执行;false-执行一次              | Boolean | false               |
+
+### Events
+
+| 事件名 | 说明           | 回调参数     |
+|--------|----------------|--------------|
+| click  | 点击元素时触发 | event: Event |
+
+### type值说明
+
+|    序号  |    参数名称     |      参数说明     |
+|:-------|:------- | :----------|
+| 1|   shake  | 抖动,建议loop为true
+| 2 |   ripple  | 不循环则是放大后缩小,循环则是心跳
+|3 |   breath  | 呼吸灯,建议loop为true
+|4 |   float  | 漂浮,建议loop为true
+|5|   slide-right  | 由右向左划入
+|6 |   slide-left  | 由左向右划入
+|7|   slide-top  | 由上至下划入
+| 8 |   slide-bottom  | 由下至上划入
+|9 |   jump  | 跳跃,建议loop为true
+|10 |   twinkle  | 水波,建议loop为true
+|11 |   flicker  | 擦亮按钮,建议loop为true

+ 299 - 0
src/packages/__VUE/animate/index.scss

@@ -0,0 +1,299 @@
+.nut-animate {
+  .nut-ani-container {
+    display: inline-block;
+  }
+
+  /* Animation css */
+  [class*="nut-animate-"] {
+    animation-duration: 0.5s;
+    animation-timing-function: ease-out;
+    animation-fill-mode: both;
+  }
+
+  //抖动
+  .nut-animate-shake {
+    animation-name: shake;
+  }
+
+  //心跳
+  .nut-animate-ripple {
+    animation-name: ripple;
+  }
+
+  // 漂浮
+
+  .nut-animate-float {
+    position: relative;
+    animation-name: float-pop;
+  }
+
+  //呼吸灯
+  .nut-animate-breath {
+    animation-name: breath;
+    animation-duration: 2700ms;
+    animation-timing-function: ease-in-out;
+    animation-direction: alternate;
+  }
+
+  //右侧向左侧划入
+  .nut-animate-slide-right {
+    animation-name: slide-right;
+  }
+
+  //右侧向左侧划入
+  .nut-animate-slide-left {
+    animation-name: slide-left;
+  }
+
+  //上面向下面划入
+  .nut-animate-slide-top {
+    animation-name: slide-top;
+  }
+
+  //下面向上面划入
+  .nut-animate-slide-bottom {
+    animation-name: slide-bottom;
+  }
+
+  .nut-animate-jump {
+    transform-origin: center center;
+    animation: jump 0.7s linear;
+  }
+
+  //循环
+  .loop {
+    animation-iteration-count: infinite;
+  }
+
+  //抖动动画
+  @keyframes shake {
+    0%,
+    100% {
+      transform: translateX(0);
+    }
+
+    10% {
+      transform: translateX(-9px);
+    }
+
+    20% {
+      transform: translateX(8px);
+    }
+
+    30% {
+      transform: translateX(-7px);
+    }
+
+    40% {
+      transform: translateX(6px);
+    }
+
+    50% {
+      transform: translateX(-5px);
+    }
+
+    60% {
+      transform: translateX(4px);
+    }
+
+    70% {
+      transform: translateX(-3px);
+    }
+
+    80% {
+      transform: translateX(2px);
+    }
+
+    90% {
+      transform: translateX(-1px);
+    }
+  }
+
+  //心跳
+  @keyframes ripple {
+    0% {
+      //box-shadow: 0 0.5em 0.5em rgba(0, 0, 0, 0.4);
+      transform: scale(1);
+    }
+    50% {
+      //box-shadow: 0 1em 1em rgba(0, 0, 0, 0.4);
+      transform: scale(1.1);
+    }
+  }
+
+  //呼吸
+  @keyframes breath {
+    0% {
+      transform: scale(1);
+    }
+    50% {
+      transform: scale(1.1);
+    }
+    100% {
+      transform: scale(1);
+    }
+  }
+
+  //右侧向左侧划入
+  @keyframes slide-right {
+    0% {
+      opacity: 0;
+      transform: translateX(100%);
+    }
+
+    100% {
+      opacity: 1;
+      transform: translateX(0);
+    }
+  }
+  //左侧向右侧划入
+  @keyframes slide-left {
+    0% {
+      opacity: 0;
+      transform: translateX(-100%);
+    }
+
+    100% {
+      opacity: 1;
+      transform: translateX(0);
+    }
+  }
+  //上面向下面划入
+  @keyframes slide-top {
+    0% {
+      opacity: 0;
+      transform: translateY(-100%);
+    }
+
+    100% {
+      opacity: 1;
+      transform: translateY(0);
+    }
+  }
+  //下面向上面划入
+  @keyframes slide-bottom {
+    0% {
+      opacity: 0;
+      transform: translateY(100%);
+    }
+
+    100% {
+      opacity: 1;
+      transform: translateY(0);
+    }
+  }
+  // 漂浮 float
+  @keyframes float-pop {
+    0% {
+      top: 0px;
+    }
+
+    25% {
+      top: 1px;
+    }
+
+    50% {
+      top: 4px;
+    }
+
+    75% {
+      top: 1px;
+    }
+
+    100% {
+      top: 0px;
+    }
+  }
+
+  //跳跃
+  @keyframes jump {
+    0% {
+      animation-timing-function: ease-in;
+      transform: rotate(0deg) translateY(0);
+    }
+    25% {
+      animation-timing-function: ease-out;
+      transform: rotate(10deg) translateY(20 * 1px);
+    }
+    50% {
+      animation-timing-function: ease-in;
+      transform: rotate(0deg) translateY(-10 * 1px);
+    }
+    75% {
+      animation-timing-function: ease-out;
+      transform: rotate(-10deg) translateY(20 * 1px);
+    }
+    100% {
+      animation-timing-function: ease-in;
+      transform: rotate(0deg) translateY(0);
+    }
+  }
+
+  .nut-animate-twinkle {
+    position: relative;
+    &::after,
+    &::before {
+      width: 60 * 1px;
+      height: 60 * 1px;
+      content: "";
+      box-sizing: border-box;
+      border: 4 * 1px solid rgba(255, 255, 255, 0.6);
+      position: absolute;
+      border-radius: calc(60 / 2) * 1px;
+      right: 50%;
+      margin-top: calc(-30 / 2) * 1px;
+      margin-right: calc(-60 / 2) * 1px;
+      z-index: 1;
+      transform: scale(0);
+      animation: twinkle 2s ease-out infinite;
+    }
+
+    &::after {
+      animation-delay: 0.4s;
+    }
+  }
+
+  // 水波
+  @keyframes twinkle {
+    0% {
+      transform: scale(0);
+    }
+    20% {
+      opacity: 1;
+    }
+    50%,
+    100% {
+      transform: scale(1.4);
+      opacity: 0;
+    }
+  }
+
+  .nut-animate-flicker {
+    position: relative;
+    overflow: hidden;
+
+    &::after {
+      width: 100 * 1px;
+      height: 60 * 1px;
+      position: absolute;
+      left: 0;
+      top: 0;
+      opacity: 0.73;
+      content: "";
+      background-image: linear-gradient(106deg, rgba(232, 224, 255, 0) 24%, #e8e0ff 91%);
+      animation: flicker 1.5s linear infinite;
+      transform: skewX(-20deg);
+      filter: blur(3 * 1px);
+    }
+  }
+
+  @keyframes flicker {
+    0% {
+      transform: translateX(-100 * 1px) skewX(-20deg);
+    }
+    40%,
+    100% {
+      transform: translateX(150 * 1px) skewX(-20deg);
+    }
+  }
+}

+ 62 - 0
src/packages/__VUE/animate/index.taro.vue

@@ -0,0 +1,62 @@
+<template>
+  <view class="nut-animate">
+    <view :class='classes' @click='handleClick'>
+      <slot></slot>
+    </view>
+  </view>
+</template>
+<script lang="ts">
+import { reactive, toRefs, computed, PropType } from 'vue';
+import { createComponent } from '@/packages/utils/create';
+const { componentName, create } = createComponent('animate');
+export default create({
+  props: {
+    type: {
+      type: String as PropType<import('./type').AnimateType>,
+      default: ''
+    },
+    action: {
+      type: String as PropType<import('./type').AnimateAction>,
+      default: 'initial'
+    },
+    loop: {
+      type: Boolean,
+      default: false
+    }
+  },
+  emits: ['click'],
+
+  setup(props, { emit }) {
+    const { type, loop, action } = toRefs(props);
+
+    const state = reactive({
+      clicked: false
+    });
+
+    let classes = computed(() => {
+      const prefixCls = componentName;
+      return {
+        'nut-ani-container': true,
+        [`${prefixCls}-${type.value}`]: action.value === 'initial' || state.clicked ? type.value : false,
+        'loop': loop.value,
+      };
+    });
+
+    const handleClick = (event: Event) => {
+
+      state.clicked = true
+
+      //如果不是无限循环,清除类名
+      if (!loop.value) {
+        setTimeout(() => {
+          state.clicked = false
+        }, 1000)
+      }
+
+      emit('click', event);
+    };
+
+    return { ...toRefs(state), classes, handleClick };
+  }
+});
+</script>

+ 62 - 0
src/packages/__VUE/animate/index.vue

@@ -0,0 +1,62 @@
+<template>
+  <view class="nut-animate">
+    <view :class='classes' @click='handleClick'>
+      <slot></slot>
+    </view>
+  </view>
+</template>
+<script lang="ts">
+import { reactive, toRefs, computed, PropType } from 'vue';
+import { createComponent } from '@/packages/utils/create';
+const { componentName, create } = createComponent('animate');
+export default create({
+  props: {
+    type: {
+      type: String as PropType<import('./type').AnimateType>,
+      default: ''
+    },
+    action: {
+      type: String as PropType<import('./type').AnimateAction>,
+      default: 'initial'
+    },
+    loop: {
+      type: Boolean,
+      default: false
+    }
+  },
+  emits: ['click'],
+
+  setup(props, { emit }) {
+    const { type, loop, action } = toRefs(props);
+
+    const state = reactive({
+      clicked: false
+    });
+
+    let classes = computed(() => {
+      const prefixCls = componentName;
+      return {
+        'nut-ani-container': true,
+        [`${prefixCls}-${type.value}`]: action.value === 'initial' || state.clicked ? type.value : false,
+        'loop': loop.value,
+      };
+    });
+
+    const handleClick = (event: Event) => {
+
+      state.clicked = true
+
+      //如果不是无限循环,清除类名
+      if (!loop.value) {
+        setTimeout(() => {
+          state.clicked = false
+        }, 1000)
+      }
+
+      emit('click', event);
+    };
+
+    return { ...toRefs(state), classes, handleClick };
+  }
+});
+</script>

+ 2 - 0
src/packages/__VUE/animate/type.ts

@@ -0,0 +1,2 @@
+export type AnimateType = 'shake' | 'ripple' | 'breath' | 'float' | 'slide-right' | 'slide-left' | 'slide-top' | 'slide-bottom' | 'jump' | 'twinkle' | 'flicker';
+export type AnimateAction = 'initial' | 'click';

+ 1 - 0
src/sites/mobile-taro/vue/src/exhibition/pages/animate/index.config.ts

@@ -0,0 +1 @@
+export default { navigationBarTitleText: 'Animate' }

+ 99 - 0
src/sites/mobile-taro/vue/src/exhibition/pages/animate/index.vue

@@ -0,0 +1,99 @@
+<template>
+  <div class='demo'>
+
+    <h2>点击触发</h2>
+
+    <div class='ani-demo-div'>
+      <nut-animate type='slide-right' action='click'>
+        <nut-button type='primary'>由右向左划入</nut-button>
+      </nut-animate>
+    </div>
+
+    <div class='ani-demo-div'>
+      <nut-animate type='slide-left' action='click'>
+        <nut-button type='primary'>由左向右划入</nut-button>
+      </nut-animate>
+    </div>
+
+    <div class='ani-demo-div'>
+      <nut-animate type='slide-top' action='click'>
+        <nut-button type='primary'>由上至下划入</nut-button>
+      </nut-animate>
+    </div>
+
+    <div class='ani-demo-div'>
+      <nut-animate type='slide-bottom' action='click'>
+        <nut-button type='primary'>由下至上划入</nut-button>
+      </nut-animate>
+    </div>
+
+    <h2>循环动画</h2>
+
+    <div class='ani-demo-div'>
+      <nut-animate type='shake' :loop='true'>
+        <nut-button type='primary'>shake-抖动</nut-button>
+      </nut-animate>
+    </div>
+
+    <div class='ani-demo-div'>
+      <nut-animate type='ripple' :loop='true'>
+        <nut-button type='primary'>ripple-心跳</nut-button>
+      </nut-animate>
+    </div>
+
+    <div class='ani-demo-div'>
+      <nut-animate type='breath' :loop='true'>
+        <nut-button type='primary'>breath-呼吸灯</nut-button>
+      </nut-animate>
+    </div>
+
+    <div class='ani-demo-div'>
+      <nut-animate type='twinkle' :loop='true'>
+        <nut-button type='primary'>twinkle-水波</nut-button>
+      </nut-animate>
+    </div>
+
+    <div class='ani-demo-div'>
+      <nut-animate type='flicker' :loop='true'>
+        <nut-button type='primary'>flicker-擦亮</nut-button>
+      </nut-animate>
+    </div>
+
+    <div class='ani-demo-div'>
+      <nut-animate type='jump' :loop='true'>
+        <nut-button type='primary'>jump-跳跃</nut-button>
+      </nut-animate>
+    </div>
+
+    <div class='ani-demo-div'>
+      <nut-animate type='float' :loop='true'>
+        <nut-button type='primary'>float-漂浮</nut-button>
+      </nut-animate>
+    </div>
+
+
+  </div>
+</template>
+<script lang="ts">
+import { defineComponent } from 'vue';
+export default defineComponent({
+  props: {},
+  setup() {
+    return {};
+  }
+});
+</script>
+<style lang='scss' scoped>
+.demo {
+}
+
+.ani-demo-div {
+  margin-bottom: 10px;
+  display: inline-block;
+  width: 100%;
+}
+
+.ani-demo-div2 {
+  margin-top: 60px;
+}
+</style>