代码取自开源项目50projects50days,用作个人学习和巩固三件套的知识,增加了注释,可能会有小改动。
<!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>
@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;
}
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();
});