点击涟漪效果指令(超级详细)

本文最后更新于:2023年11月5日 晚上

使用

先引入涟漪样式,建议在App.vue中全局引入:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
.ripple {
position: absolute;
left: 0;
top: 0;
display: none;
transform: translate3d(-50%, -50%, 0) scale(0);
border-radius: 50%;
pointer-events: none;
opacity: 0;
animation: rippleRound .6s ease-in-out;
}

@keyframes rippleRound {
0% {
transform: translate3d(-50%, -50%, 0) scale(0);
opacity: 0;
}
50% {
opacity: 0.5;
}
100% {
transform: translate3d(-50%, -50%, 0) scale(1);
opacity: 0;
}
}

注册后正式使用:

1
2
3
4
import Vue from 'vue'
import ripple from "./ripple"

Vue.directive('ripple', ripple)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<script>
export default {
data () {
return {
color: ''
}
}
}
</script>

<template>
<div>
<button class="btn position-abs" v-ripple.abs="color">绝对定位</button>
<button class="btn" v-ripple="color">涟漪效果</button>
<div>
<span v-if="color">当前颜色{{ color }}</span>
<span v-else>选择涟漪颜色,默认 rgba(255,255,255,.8)</span>
<input type="color" v-model="color">
</div>
</div>
</template>
<style>
.btn {
width: 200px;
height: 100px;
}
.position-abs {
position: absolute;
left: 50%;
}
</style>

说明:

当使用的元素自身存在定位时,需要添加abs修饰,指令的值可以是任何表示颜色的字符

ripple.js

vue2 版本

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
/**
* @author Xin-FAS
*/

// 初始化所在元素
const pNodeInit = (el, isAbs) => {
if (!isAbs) el.style.position = 'relative'
el.style.overflow = 'hidden'
// 兼容iphone
el.style['webkit-backface-visibility'] = 'hidden'
el.style['-webkit-transform'] = 'translate3d(0, 0, 0)'
}
// 创建/修改涟漪元素
const emitRippleNode = (color, target) => {
if (!target) {
target = document.createElement('span')
target.className = 'ripple'
}
target.style.background = `radial-gradient(ellipse 50% 50% at 50% 50%, ${color || 'rgba(255,255,255,.8)'} 80%,rgba(0,0,0,0) 100%)`
return target
}

const listenerClick = () => {
// 监听事件
let time
const start = event => {
if (time) return
const rippleNode = event.target.querySelector('span[class="ripple"]')
rippleNode.style.left = event.offsetX + 'px'
rippleNode.style.top = event.offsetY + 'px'
// 得到最大扩散的半径,开始扩散
const maxLen = Math.max(event.target.clientHeight, event.target.clientWidth)
rippleNode.style.width = maxLen * 2 + 'px'
rippleNode.style.height = maxLen * 2 + 'px'
rippleNode.style.display = 'block'
// 简单节流,让600毫秒内只执行一次
time = setTimeout(() => {
rippleNode.style.display = 'none'
time = undefined
}, 600)
}
return start
}

const ripple = {
bind (el, { value, modifiers }) {
// 当前元素初始化
pNodeInit(el, modifiers.abs)
// 涟漪元素初始化
el.appendChild(emitRippleNode(value))
// 开始监听点击
el.addEventListener('click', listenerClick())
},
unbind (el) {
// 取消监听
el.removeEventListener('click', listenerClick())
// 移除元素
el.removeChild(el.querySelector('span[class="ripple"]'))
},
update (el, { value, oldValue }) {
if (value === oldValue) return
const rippleNode = el.querySelector('span[class="ripple"]')
emitRippleNode(value, rippleNode)
}
}

export default ripple

vue3 版本

换一下生命周期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
/**
* @author Xin-FAS
*/

// 初始化所在元素
const pNodeInit = (el, isAbs) => {
if (!isAbs) el.style.position = 'relative'
el.style.overflow = 'hidden'
// 兼容iphone
el.style['webkit-backface-visibility'] = 'hidden'
el.style['-webkit-transform'] = 'translate3d(0, 0, 0)'
}
// 创建/修改涟漪元素
const emitRippleNode = (color, target) => {
if (!target) {
target = document.createElement('span')
target.className = 'ripple'
}
target.style.background = `radial-gradient(ellipse 50% 50% at 50% 50%, ${color || 'rgba(255,255,255,.8)'} 80%,rgba(0,0,0,0) 100%)`
return target
}

const listenerClick = () => {
// 监听事件
let time
const start = event => {
if (time) return
const rippleNode = event.target.querySelector('span[class="ripple"]')
rippleNode.style.left = event.offsetX + 'px'
rippleNode.style.top = event.offsetY + 'px'
// 得到最大扩散的半径,开始扩散
const maxLen = Math.max(event.target.clientHeight, event.target.clientWidth)
rippleNode.style.width = maxLen * 2 + 'px'
rippleNode.style.height = maxLen * 2 + 'px'
rippleNode.style.display = 'block'
// 简单节流,让600毫秒内只执行一次
time = setTimeout(() => {
rippleNode.style.display = 'none'
time = undefined
}, 600)
}
return start
}

const ripple = {
mounted (el, { value, modifiers }) {
// 当前元素初始化
pNodeInit(el, modifiers.abs)
// 涟漪元素初始化
el.appendChild(emitRippleNode(value))
// 开始监听点击
el.addEventListener('click', listenerClick())
},
unmounted (el) {
// 取消监听
el.removeEventListener('click', listenerClick())
// 移除元素
el.removeChild(el.querySelector('span[class="ripple"]'))
},
updated (el, { value, oldValue }) {
if (value === oldValue) return
const rippleNode = el.querySelector('span[class="ripple"]')
emitRippleNode(value, rippleNode)
}
}

export default ripple

点击涟漪效果指令(超级详细)
https://xin-fas.github.io/2023/08/24/点击涟漪效果指令(超级详细)/
作者
Xin-FAS
发布于
2023年8月24日
更新于
2023年11月5日
许可协议