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 judge
eventType
Is 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 function
getSize
To 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 using
getHitPosition
We get the current mouse click position for where the water ripple component starts to reality. This function internally passes the current click positionpageY
andpageX
Subtract the elements from the viewporttop
andleft
Subtract half of the maximum width or height of the current element to get the current click positiontop
andleft
The value of the. - And then finally we define one
ripples
Array is used to collect the number of clicks. Array contains the value of the water ripple componentstyle
andkey
, includinguuid
Is 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