vue按钮水波纹效果

2,899 阅读1分钟

要完成以上效果首先我们的思路是通过点击外层触发点击事件,收集点击次数然后创建与点击次数相同的水波纹动画组件。然后每当水波纹组件动画执行完之后去销毁当前这个水波纹组件。

第一步我们先去制作这个水波分动画组件

// wave.vue
<template>
      <transition name="wave" @after-enter="end">
            <span v-show="animating" class="ripple-wave"></span>
      </transition>
</template>

<script>
export default {
      data () {
            return {
                  animating: true
            }
      },
      props: {
            waveClasses: {
                  type: String,
                  default: null
            },
            waveStyles: {
                  type: String,
                  default: null
            }
      },
      methods: {
            // 动画结束
            end () {
                  this.animating = null;
                  this.$emit('animating-end')
            }
      }
}
</script>

<style lang="scss" scoped>
.ripple-wave {
      position: absolute;
      z-index: 1;
      pointer-events: none;
      background: currentColor;
      border-radius: 50%;
      opacity: 0;
      transform: scale(2) translateZ(0);
      width: 100%;
      height: 100%;

      ~ *:not(.ripple-wave) {
            position: relative;
            z-index: 2;
      }
}

.wave-enter-active {
      transition: 0.8s cubic-bezier(0.25, 0.8, 0.25, 1);
      transition-property: opacity, transform;
      will-change: opacity, transform;
}

.wave-enter {
      opacity: 0.26;
      transform: scale(0.26) translateZ(0);
}
</style>

以上主要是使用了transition组件去包裹一个标签,然后每当transition组件动画执行完之后去调用end方法向外部发送动画结束事件。 然后我们只需要在外部组件去监听到这个事件然后对其进行销毁,就能确保当前组件不会残留在 DOM 节点中了。

接下来我们来看看外部组件我怎么实现对该动画组件进行增加删除的。

我们先来创建外部组件的一个骨架。

<template>
      <div
            class="ripple"
            @touchstart.passive="touchstart"
            @touchmove.passive="touchmove"
            @mousedown.passive="mousedown"
      >
            <slot></slot>
            <Wave></Wave>
      </div>
</template>

<script>
import Wave from './wave';

export default {
      data () {
            return {
            }
      },
      methods: {
            // 触摸开始
            touchstart (event) {
            },
            // 触摸移动
            touchmove (event) {
            },
            // 鼠标点击
            mousedown () {
            }
      },
      components: {
            Wave
      }
}
</script>

<style lang="scss" scoped>
.ripple {
      width: 100%;
      height: 100%;
      position: relative;
      z-index: 10;
      overflow: hidden;
      -webkit-mask-image: radial-gradient(circle, #fff 100%, #000 100%);
      box-sizing: border-box;
}
</style>

以上创建了一个外壳用于包裹水波纹动画组件,然后在上面定义了手指触摸事件和鼠标点击事件,我们先来制作鼠标点击事件。

由于我们制作此组件需要用到requestAnimationFrame,但是对于IE或其他浏览器可能不支持requestAnimationFrame所以我们这里引入是一个polyfillraf

import raf from 'raf';


export default {
      data () {
            return {
                  /*
                  * 事件类型
                  * 
                  * @type {String}
                  */
                  eventType: null,
                  /*
                  * 涟漪数组
                  * 
                  * @type {Array}
                  */
                  ripples: []
            }
      },
      methods: {
            // 鼠标点击
            mousedown (event) {
                  return this.startRipple(event);
            },
            // 涟漪开始
            startRipple ($event) {
                  console.log($event)

                  raf(() => {
                        if (!this.eventType || this.eventType === $event.type) {
                              //当前元素位置 
                              let size = this.getSize();

                              // 涟漪位置
                              let position = null;
                              // 获取点击的位置
                              position = this.getHitPosition($event, size);

                              // 事件类型
                              this.eventType = $event.type;

                              this.ripples.push({
                                    // 波浪 style
                                    waveStyles: this.applyStyles(position, size),
                                    uuid: this.uuid()
                              });
                        }
                  });
            },
            getSize () {
                  const { offsetWidth, offsetHeight } = this.$el;

                  return Math.round(Math.max(offsetWidth, offsetHeight));
            },
            // 获取点击的位置 
            getHitPosition ($event, elementSize) {
                  // 元素的大小及其相对于视口的位置。
                  const rect = this.$el.getBoundingClientRect();

                  let top = $event.pageY;
                  let left = $event.pageX;

                  return {
                        top: top - rect.top - elementSize / 2 - document.documentElement.scrollTop + 'px',
                        left: left - rect.left - elementSize / 2 - document.documentElement.scrollLeft + 'px'
                  }
            },
            // 样式
            applyStyles (position, size) {
                  size += 'px';
                  return {
                        ...position,
                        width: size,
                        height: size
                  }
            },
            uuid () {
                  return Math.random().toString(36).slice(4);
            }
      }
}

以上代码我们引入了raf库,然后定义一个函数startRipple通过鼠标点击触发,此函数用于创建水波纹组件数量。

  • 第一步我们通过判断eventType是否是当前点击事件来确定是否创建水波纹组件,防止其他事件误触发,导致创建了水波纹组件。
  • 然后定义了函数getSize,用于获取到当前点击此组件时的宽度啊高度,然后返回了最大值,此值正是水波纹组件的最大宽度和高度。
  • 然后通过使用getHitPosition我们获取到当前鼠标点击的位置,用于水波纹组件在哪个位置开始现实。此函数内部通过当前点击位置pageYpageX减去元素到视口的topleft再减去当前元素的最大宽度或最大高度的一半来获取到当前点击的位置topleft的值。
  • 最后我们通过定义一个ripples数组用于收集点击的次数。数组中包含了水波纹组件的stylekey,其中uuid是一个随机数。

我们还需要去循环创建水波纹组件。

<template>
      <div
            :class="['ripple']"
            @touchstart.passive="touchstart"
            @touchmove.passive="touchmove"
            @mousedown.passive="mousedown"
      >
            <slot></slot>
            <Wave
                  v-for="ripple in ripples"
                  :key="ripple.uuid"
                  :style="ripple.waveStyles"
            ></Wave>
      </div>
</template>

这里我们除了创建还差一个函数,就是动画执行完之后需要销毁当前组件,因为如果不销毁该组件,它会一直残留在DOM中。在外层组件中监听水波纹组件动画结束时发送的事件animating-end,执行销毁组件函数clearWave

<template>
      <div
            :class="['ripple']"
            @touchstart.passive="touchstart"
            @touchmove.passive="touchmove"
            @mousedown.passive="mousedown"
      >
            <slot></slot>
            <Wave
                  v-for="ripple in ripples"
                  :key="ripple.uuid"
                  :style="ripple.waveStyles"
                  @animating-end="clearWave(ripple.uuid)"
            ></Wave>
      </div>
</template>

export default {
      methods: {
            // 清除波纹效果
            clearWave (uuid) {
                  if (uuid) {
                        this.ripples = this.ripples.filter((ripple) => {
                              return ripple.uuid !== uuid
                        });
                  }
                  else {
                        this.ripples = [];
                  }
            }
      }
}

最后我们继续完成手指触摸事件,其实很简单我们只需要判断只有当手指触摸才触发即可。

export default {
      methods: {
             // 触摸开始
            touchstart (event) {
                  return this.touchStartCheck(event);
            },
            // 触摸移动
            touchmove (event) {
                  return this.touchMoveCheck(event);
            },
            // 检查触摸开始
            touchStartCheck ($event) {
                  this.touchTimeout = window.setTimeout(() => {
                        this.startRipple($event);
                  });
            }
      }
}

到这来就完成了一个简易的水波纹动画组件了。

如想查看详细的组件代码可以查看这里。
关于查看水波纹按钮效果可以查看这里