The first idea is to trigger a click event by clicking on the outer layer, collecting clicks and then creating a water ripple animation component with the same number of clicks. Then destroy the current water ripple component every time the water ripple component animation finishes.

The first step is to create the water wave component

// 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; The transform: scale (0.26) translateZ (0); } </style>Copy the code

We use the Transition component to wrap a tag and then call the End method every time the Transition component finishes the animation to send an end event. Then we just need to listen for this event in the external component and destroy it to ensure that the current component does not remain in the DOM node.

Now let’s look at external components and see how I can add and remove this animation component.

Let’s start by creating a skeleton of the external components.

<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) {}, // touch touchMove (event) {}, // mouse click 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>Copy the code

We created a shell to wrap the water ripple animation component around, and then defined finger touch events and mouse click events on it. Let’s make mouse click events first.

Since we need to use requestAnimationFrame to make this component, but IE or other browsers may not support requestAnimationFrame, we introduce a polyfill library raf.

import raf from 'raf';


export default {
      data () {
            return {
                  /* * Event type * * @type {String} */
                  eventType: null./* * ripple Array * * @type {Array} */
                  ripples: []
            }
      },
      methods: {
            // Mouse click
            mousedown (event) {
                  return this.startRipple(event);
            },
            // The ripples begin
            startRipple ($event) {
                  console.log($event)

                  raf((a)= > {
                        if (!this.eventType || this.eventType === $event.type) {
                              // The current element position
                              let size = this.getSize();

                              // The location of the ripple
                              let position = null;
                              // Get the click position
                              position = this.getHitPosition($event, size);

                              // Event type
                              this.eventType = $event.type;

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

                  return Math.round(Math.max(offsetWidth, offsetHeight));
            },
            // Get the click position
            getHitPosition ($event, elementSize) {
                  // The size of the element and its position relative to the viewport.
                  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'}},/ / style
            applyStyles (position, size) {
                  size += 'px';
                  return {
                        ...position,
                        width: size,
                        height: size
                  }
            },
            uuid () {
                  return Math.random().toString(36).slice(4); }}}Copy the code

Above we introduced the RAF library and then defined a function startRipple triggered by mouse click to create the number of water ripple components.

  • The first step is to judgeeventTypeIs the current click event to determine whether the water ripple component is created, preventing other events from accidentally triggering the creation of the water ripple component.
  • And then we define the functiongetSizeTo get when the component is currently clickedThe width?andhighly, and returns the maximum value, which is the maximum width and height of the water ripple component.
  • And then by usinggetHitPositionWe get the current mouse click position for where the water ripple component starts to reality. This function internally passes the current click positionpageYandpageXSubtract the elements from the viewporttopandleftSubtract half of the maximum width or height of the current element to get the current click positiontopandleftThe value of the.
  • And then finally we define oneripplesArray is used to collect the number of clicks. Array contains the value of the water ripple componentstyleandkey, includinguuidIs a random number.

We also need to loop to create the water ripple component.

<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>
Copy the code

In addition to creating, we need to destroy the current component after the animation, because if we don’t destroy it, it will remain in the DOM forever. In the outer component, listen for the animating-end event sent at the end of the water ripple component’s animation, and execute the destroy component function 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: Ripples clearWave (uuid) {if (uuid) {this.ripples = this.ripples. Filter (ripple) => {return ripple. == uuid }); } else { this.ripples = []; }}}}Copy the code

And then we’re going to go ahead and do the finger touch event, which is actually pretty simple and we just need to determine that it only fires when the finger touches.

export default {
      methods: {
             // Start the touch
            touchstart (event) {
                  return this.touchStartCheck(event);
            },
            // Touch move
            touchmove (event) {
                  return this.touchMoveCheck(event);
            },
            // Check the start of the touch
            touchStartCheck ($event) {
                  this.touchTimeout = window.setTimeout((a)= > {
                        this.startRipple($event); }); }}}Copy the code

Here you have a simple water ripple animation component.

For detailed component code, see here. To see the effect of the water ripple button, see here