红绿灯

  • 红绿灯的实现,第一想法就是用 setTimeout 和 setInterval ,但是严格一点来说 setTimeout 一定准时吗?随着时间的推移以及浏览器的渲染流程与优化策略,误差会越来越大.
  • 所以应该使用轮询的方式来做

html

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>红绿灯</title>
</head>
<body>
<div class="box">
<div class="line-count">
<div class="red"></div>
<div class="yellow"></div>
<div class="gene"></div>
</div>
<div class="toolbar__item"></div>
</div>
<script src="./index.js"></script>
</body>
</html>

css

.box {
position: relative;
display: flex;
width: 800px;
height: 500px;
margin: 0 auto;
}
.line-count {
width: 100%;
height: 300px;
position: absolute;
left: 0;
padding: 100px;
display: flex;
align-items: center;
}
.line-count.active-red > :nth-child(1) {
background-color: red;
}
.line-count.active-green > :nth-child(2) {
background-color: rgb(3, 248, 36);
}
.line-count.active-yellow > :nth-child(3) {
background-color: rgb(255, 238, 0);
}
.red {
border-radius: 50%;
width: 100px;
height: 100px;
background-color: rgb(146, 146, 146);
margin: 10px;
padding: 10px;
}
.gene {
border-radius: 50%;
width: 100px;
height: 100px;
background-color: rgb(146, 146, 146);
margin: 10px;
padding: 10px;
}
.yellow {
border-radius: 50%;
width: 100px;
height: 100px;
background-color: rgb(146, 146, 146);
margin: 10px;
padding: 10px;
}
.toolbar__item {
position: absolute;
width: 100px;
height: 100px;
margin-top: 400px;
left: 300px;
font-size: 30px;
}

js

const parent = document.querySelector(".box .line-count");
const time = document.querySelector(".box .toolbar__item");
const colorLight = [
{ color: "red", time: 4 },
{ color: "yellow", time: 3 },
{ color: "green", time: 6 },
{ color: "yellow", time: 3 },
];
//当前灯的下标
let currentIndex = 0;
//当前灯的持续时间
let currentTime = Date.now();
//整个灯循环的时间
let allTime = colorLight.reduce((pre, cur) => pre + cur.time, 0);

function getCurrentLight(lightArr) {
// update核心就是更改下标和灯的持续时间
update(lightArr);
return {
color: currentLight(lightArr).color,
remain: currentLight(lightArr).time - activeLight(currentTime),
};
}
//获得当前灯的信息
const currentLight = (arr) => arr[currentIndex];
//当前灯持续的时间(秒) 也就是当前时间戳-创建时的时间戳
const activeLight = () => (Date.now() - currentTime) / 1000;
//更新当前时间 核心逻辑
const update = (arr) => {
//获得当前持续的时间
let disTime = activeLight();
//记录时间轴并更新当前循环时间 保证超过最大时间后重置
disTime = disTime % allTime;
currentTime += allTime * Math.floor(disTime / allTime) * 1000;
//更新当前时间并映射为对应灯
while (true) {
//修正时间 也就是让disTime减去灯的持续时间来判断当前应该是什么灯
disTime -= currentLight(arr, currentIndex).time;
//disTime小于0就表示当前灯还未到切换时间
if (disTime < 0) break;
//更新当前灯
currentTime += currentLight(arr, currentIndex).time * 1000;
currentIndex = (currentIndex + 1) % arr.length;
}
};
function __update() {
const current = getCurrentLight(colorLight);
parent.className = `line-count active-${current.color}`;
time.textContent = Math.floor(current.remain);
}
__update();
setInterval(() => {
__update();
}, 1000);