📢前言

代码取自开源项目50projects50days,用作个人学习和巩固三件套的知识,增加了注释,可能会有小改动。

在线演示地址

📝实现思路及效果

20220902123330.jpg

QQ截图20220902123341.jpg

QQ截图20220902123358.jpg

QQ截图20220902123407.jpg

💻代码

index.html

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
<!DOCTYPE html>
<html lang="en">

<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>倒计时动画</title>
<link rel="shortcut icon" href="../logo.svg">
<link rel="stylesheet" href="style.css">
</head>

<body>
<!-- 计数 倒数3 2 1 的盒子 -->
<div class="counter">
<div class="nums">
<!-- 初始化 第一个数一定是倒计时的第一个数 -->
<span class="in">3</span>
<span>2</span>
<span>1</span>
<span>0</span>
</div>
<h4>Get Ready</h4>
</div>


<!-- 添加js交互 点击replay按钮 重新倒计时 -->
<div class="final">
<h1>GO</h1>
<button id="replay">
<span>Replay</span>
</button>
</div>
<script src="script.js"></script>
</body>

</html>

style.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
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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap');

* {
box-sizing: border-box;
}

body {
font-family: 'Roboto', sans-serif;
margin: 0;
height: 100vh;
overflow: hidden;
}

h4 {
font-size: 20px;
margin: 5px;
/* 文本大小写转换 uppercase 强制该文本全部大写 */
text-transform: uppercase;
}

.counter {
/* 经典的 盒子在网页的 中心位置显示设置 */
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
}

.counter.hide {
transform: translate(-50%, -50%) scale(0);
/* 动画 淡出(慢速结束) 控制 3 2 1 倒计时的转换和隐藏*/
animation: hide 0.2s ease-out;
}

@keyframes hide {
0% {
transform: translate(-50%, -50%) scale(1);
}
100% {
transform: translate(-50%, -50%) scale(0);
}
}

.final {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) scale(0);
text-align: center;
}

.final.show {
transform: translate(-50%, -50%) scale(1);
animation: show 0.2s ease-out;
}

@keyframes show {
0% {
transform: translate(-50%, -50%) scale(0);
}

30% {
transform: translate(-50%, -50%) scale(1.5);
}

100% {
transform: translate(-50%, -50%) scale(1);
}
}

.nums {
color: #3498db;
font-size: 50px;
position: relative;
overflow: hidden;
widows: 250px;
height: 50px;
}

.nums span {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) rotate(120deg);
transform-origin: bottom center;
}

.nums span.in {
transform: translate(-50%, -50%) rotate(0deg);
animation: goIn 0.5s ease-in-out;
/* 淡入淡出 */
}
.nums span.out {
animation: goOut 0.5s ease-in-out;
}

/* 实现旋转 左右摆动的视觉效果 */
@keyframes goIn {
0% {
transform: translate(-50%, -50%) rotate(120deg);
}
30% {
transform: translate(-50%, -50%) rotate(-20deg);
}
60% {
transform: translate(-50%, -50%) rotate(10deg);
}
100% {
transform: translate(-50%, -50%) rotate(0deg);
}
}

@keyframes goOut {
0% {
transform: translate(-50%, -50%) rotate(0deg);
}

60% {
transform: translate(-50%, -50%) rotate(20deg);
}

100% {
transform: translate(-50%, -50%) rotate(-120deg);
}
}

#replay {
background-color: #3498db;
border-radius: 3px;
border: none;
color: aliceblue;
padding: 5px;
text-align: center;
display: inline-block;
cursor: pointer;
transition: all 0.3s;
}

#replay span {
cursor: pointer;
display: inline-block;
position: relative;
transition: 0.3s;
}

script.js

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
const nums = document.querySelectorAll(".nums span");
const counter = document.querySelector(".counter");
const finalMessage = document.querySelector(".final");
const replay = document.querySelector("#replay");

runAnimation();

function resetDOM() {
// 使用元素的 classList 属性可以访问或添加、删除及修改元素的 class 属性。需注意的是,支持 classList 属性的浏览器主要是一些较新版的,例如:IE10+、Firefox3.6+。使用 classList 属性访问 class 属性的格式如下:element.classList

// classList 是一个只读属性,其返回的值为 DOMTokenList,其中包含了元素的所有 class 属性,不同的 class 属性之间使用一空格分隔。

// classList 调用 add()、remove() 和 toggle() 等方法可以添加、移除或修改元素 class 属性
counter.classList.remove("hide");
finalMessage.classList.remove("show");

nums.forEach((num) => {
num.classList.value = "";
});
nums[0].classList.add("in");
}

function runAnimation() {
nums.forEach((num, idx) => {
const nextToLast = nums.length - 1;
// nextElementSibling 返回当前元素在其父元素的子元素节点中的后一个元素节点,如果该元素已经是最后一个元素节点,则返回null,该属性是只读的.
num.addEventListener("animationend", (e) => {
if (e.animationName === "goIn" && idx !== nextToLast) {
num.classList.remove("in");
num.classList.add("out");
} else if (e.animationName === "goOut" && num.nextElementSibling) {
num.nextElementSibling.classList.add("in");
} else {
// 隐藏计数 显示replay按钮
counter.classList.add("hide");
finalMessage.classList.add("show");
}
});
});
}

replay.addEventListener("click", () => {
resetDOM();
runAnimation();
});