一些动效

波纹效果

假设 .waves-ripple 类是实现波纹效果的样式,那么它在初始时是一个大小固定的透明的圆,圆心是鼠标点击的地方。在结束时它的直径是目标元素对角线的长度,而且圆心是目标元素的中心。波纹效果就是通过改变它的透明度、伸缩性、位置再加上过渡来实现的。

监听目标元素的 mousedown 事件,当事件被触发时,在目标元素下创建一个 div,并赋予其类(.waves-ripple),然后动态更改其样式(opacity, transform)。

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
68
69
70
71
72
73
74
75
76
77
78
79
<html>
<body>
<div id="box" class="waves"></div>
</body>

<script>
onload = () => {
const el = document.getElementById("box");

function setConvertStyle(obj) {
let style = "";
for (let i in obj) {
if (obj.hasOwnProperty(i)) style += `${i}:${obj[i]};`;
}
return style;
}

function onCurrentClick(event) {
// 圆的最终直径(目标元素对角线)
const diameter= Math.sqrt(el.clientWidth**2 + el.clientHeight**2)
// 圆的最终圆心(目标元素的中心)
const centerX = el.clientWidth / 2;
const centerY = el.clientHeight / 2;
// 根据初始状态 20px 计算伸缩性
const scaleNum = diameter / 20;

const divEl = document.createElement("div");
divEl.classList.add("waves-ripple");
el.appendChild(divEl);
const style = {
// layerX,layerY 是相对于目标元素所在位置
left: `${event.layerX}px`,
top: `${event.layerY}px`,
opacity: 1,
transform: `scale(${scaleNum}) translate(${(centerX-event.layerX)/scaleNum}px,${(centerY-event.layerY)/scaleNum}px)`,
transtion: "all 0.7s cubic-bezier(0.250, 0.460, 0.450, 0.940)"
}
divEl.setAttribute("style", setConvertStyle(style));

setTimeout(() => {
// 水波纹的最终状态是透明
divEl.setAttribute("style", setConvertStyle({
left: style.left,
top: style.top,
opacity: 0,
transform: style.transform
}))
setTimeout(() => {
el.removeChild(divEl);
}, 700)
}, 450)
}

el.addEventListener("mousedown", onCurrentClick, false);
}
</script>

<style>
.waves {
position: relative;
width: 200px;
height: 200px;
background-color: #1095c1;
overflow: hidden;
cursor: pointer;
}
.waves-ripple {
position: absolute;
width: 20px;
height: 20px;
opacity: 0;
margin: -10px 0 0 -10px;
border-radius: 50%;
background-color: rgba(0, 0, 0, 0.2);
pointer-events: none; /* 禁止响应鼠标事件 */
transition: all 0.7s ease-out;
}
</style>
</html>

注意: 由于圆是在变大的同时进行移动的,那么它最终的位置与我们预想的位置不一样。假设我们想要它移动 translate(x, y),但它最终移动了 translate(scaleNum*x, scaleNum*y)。因此,要想将它移动到某个位置,需要移动 translate(x/scaleNum, y/scaleNum)


vue3 波纹指令

src/style/index.css

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
.waves-effect {
position: relative;
cursor: pointer;
display: inline-block;
overflow: hidden;
user-select: none;
-webkit-tap-highlight-color: transparent;
vertical-align: middle;
z-index: 1;
will-change: opacity, transform;
}
.waves-effect .waves-ripple {
position: absolute;
border-radius: 50%;
width: 20px;
height: 20px;
margin-top: -10px;
margin-left: -10px;
opacity: 0;
background: rgba(0, 0, 0, 0.2);
transition: all 0.7s ease-out;
pointer-events: none; /* 禁止响应鼠标事件 */
}
.waves-effect.waves-light .waves-ripple {
background-color: rgba(255, 255, 255, 0.45);
}
.waves-effect.waves-red .waves-ripple {
background-color: rgba(244, 67, 54, 0.7);
}
.waves-effect.waves-yellow .waves-ripple {
background-color: rgba(255, 235, 59, 0.7);
}
.waves-effect.waves-orange .waves-ripple {
background-color: rgba(255, 152, 0, 0.7);
}
.waves-effect.waves-purple .waves-ripple {
background-color: rgba(156, 39, 176, 0.7);
}
.waves-effect.waves-green .waves-ripple {
background-color: rgba(76, 175, 80, 0.7);
}
.waves-effect.waves-teal .waves-ripple {
background-color: rgba(0, 150, 136, 0.7);
}

src/directive/index.ts

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
import type { App } from "vue";

export function wavesDirective(app: App) {
app.directive("waves", {
mounted(el, binding) {
el.classList.add("waves-effect");
binding.value && el.classList.add(`waves-${binding.value}`);
function setConvertStyle(obj: { [key: string]: unknown }) {
let style: string = '';
for (let i in obj) {
if (obj.hasOwnProperty(i)) style += `${i}:${obj[i]};`;
}
return style;
}
function onCurrentClick(e: { [key: string]: any }) {
const diameter: number = Math.sqrt(el.clientWidth**2 + el.clientHeight**2)
const centerX: number = el.clientWidth / 2;
const centerY: number = el.clientHeight / 2;
const scaleNum: number = diameter / 20;

let elDiv = document.createElement('div');
elDiv.classList.add('waves-ripple');
el.appendChild(elDiv);
let styles = {
left: `${e.layerX}px`,
top: `${e.layerY}px`,
opacity: 1,
transform: `scale(${scaleNum}) translate(${(centerX-e.layerX)/scaleNum}px,${(centerY-e.layerY)/scaleNum}px)`,
'transition-duration': `700ms`,
'transition-timing-function': `cubic-bezier(0.250, 0.460, 0.450, 0.940)`,
};
elDiv.setAttribute('style', setConvertStyle(styles));

setTimeout(() => {
elDiv.setAttribute(
'style',
setConvertStyle({
opacity: 0,
transform: styles.transform,
left: styles.left,
top: styles.top,
})
);
setTimeout(() => {
elDiv && el.removeChild(elDiv);
}, 700);
}, 450);
}
el.addEventListener('mousedown', onCurrentClick, false);
},
unmounted(el) {
el.addEventListener("mousedown", () => {});
}
});
}

main.ts

1
2
3
4
5
6
import "@/style/index.css"
import { wavesDirective } from "@/directive";

const app = createApp(App)
wavesDirective(app)
app.mount('#app')

使用

1
<div v-waves="light"></div>

彩色流动边框

其实现原理是在一个盒子上叠加两个盒子,利用这两个盒子的 :before:after 给四条边添加渐变色的线。初始时四条线的位置如下图:

然后利用 animationanimation-delay 来实现流动的效果。

代码如下:

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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
<div class="box">
<div class="wrap1"></div>
<div class="wrap2"></div>
</div>

<style>
.wrap1, .wrap2 {
width: inherit;
height: inherit;
position: absolute;
overflow: hidden;
}
.wrap1:before {
content: "";
position: absolute;
top: 0;
width: 100%;
height: 3px;
z-index: 1;
background: linear-gradient(90deg, transparent, #1095c1);
animation: top 2s linear infinite;
}
.wrap1:after {
content: "";
position: absolute;
width: 3px;
height: 100%;
top: -100%;
background: linear-gradient(180deg, transparent, #1095c1);
filter: hue-rotate(60deg);
right: 0;
animation: right 2s linear infinite;
animation-delay: 0.5s;
}
.wrap2:before {
content: "";
position: absolute;
width: 3px;
height: 100%;
top: 100%;
background: linear-gradient(0deg, transparent, #1095c1);
filter: hue-rotate(120deg);
animation: left 2s linear infinite;
animation-delay: 1.5s;
}
.wrap2:after {
content: "";
position: absolute;
width: 100%;
height: 3px;
background: linear-gradient(270deg, transparent, #1095c1);
filter: hue-rotate(180deg);
bottom: 0;
left: 100%;
animation: bottom 2s linear infinite;
animation-delay: 1s;
}

@keyframes top {
0% {
left: -100%;
}
50%,
100% {
left: 100%;
}
}
@keyframes right {
0% {
top: -100%;
}
50%,
100% {
top: 100%;
}
}
@keyframes bottom {
0% {
left: 100%;
}
50%,
100% {
left: -100%;
}
}
@keyframes left {
0% {
top: 100%;
}
50%,
100% {
top: -100%;
}
}
</style>

loading 展示

示例一

利用网格布局创建九个圆,分别为每个圆添加动画,使其透明度发生变化。对角线元素透明度的变化顺序可通过 delay 实现。animation-direction: alternate 起到关键作用。

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
<div class="loading-box">
<div v-for="_ in 9" class="loading-item"></div>
</div>

<style>
.loading-box {
width: 200px;
height: 200px;
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-template-rows: repeat(3, 1fr);
background-color: black;
justify-items: center;
align-items: center;
}
.loading-item {
width: 20px;
height: 20px;
background-color: #3cefff;
border-radius: 50%;
animation: fade 1.5s ease-in-out infinite alternate;
}
.loading-item:nth-child(2), .loading-item:nth-child(4) {
animation-delay: 0.25s;
}
.loading-item:nth-child(3), .loading-item:nth-child(5), .loading-item:nth-child(7) {
animation-delay: 0.5s;
}
.loading-item:nth-child(6), .loading-item:nth-child(8) {
animation-delay: 0.75s;
}
.loading-item:last-child {
animation-delay: 1s;
}
@keyframes fade {
100% {
opacity: 0.2;
}
}
</style>
示例二

1
2
3
4
5
6
7
8
9
<div style="width: 150px; height: 150px; display: flex; justify-content: center; align-items: center">
<svg>
<circle cx="75" cy="75" r="50" fill="none" stroke="red" stroke-width="3" stroke-dasharray="1, 400" stroke-dashoffset="0" />
</svg>
</div>

<style>

</style>