实现一个信号灯,这个信号灯,有黄绿红,他们各自亮灯的持续时间是 1s,2s,3s 如此反复。javascript
前景提要,咱们的html代码是这样:css
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>红绿灯</title>
<style> #circle { background-color: green; border-radius: 50%; width: 100px; height: 100px; } </style>
</head>
<body>
<div id="circle"></div>
</body>
</html>
复制代码
咱们经过setTimeout或者setInterval来实现:html
function setColor(color) {
document.getElementById("circle").style.backgroundColor = color
}
function startWork() {
setColor("green")
setTimeout(() => {
setColor("yellow")
setTimeout(() => {
setColor("red")
setTimeout(() => {
startWork()
}, 4000)
}, 3000)
}, 2000)
}
startWork()
复制代码
这样是可行的,可是这个回调看起来让人抓狂,咱们用promise来实现这一版本java
function delay(duration) {
return new Promise((resolve, reject) => {
setTimeout(resolve, duration)
})
}
function changeColor(color, duration) {
const func = function () {
document.getElementById("circle").style.backgroundColor = color
}
func()
return delay(duration)
}
function startWork() {
return Promise.resolve()
.then(() => changeColor("green", 2000))
.then(() => changeColor("yellow", 3000))
.then(() => changeColor("red", 4000))
.then(() => startWork())
}
startWork()
复制代码
可是感受这样仍是不够优雅,咱们用async作一下优化函数startWork:jquery
async function startWork() {
await changeColor("green", 2000)
await changeColor("yellow", 3000)
await changeColor("red", 4000)
startWork()
}
复制代码
没错,这样更简洁了,可是到目前为止,可是咱们想实现暂停和开始,中止和从新开始。 其实我一开始想的,中止的话,最好是循环,break出循环便可。那么咱们能够循环来实现这个一直重复的动做呢,答案是能够的。web
好了 核心来了:数组
咱们封装到一个类里面,把这个一直持续的过程,经过自定义的迭代器来实现:promise
function delay(duration) {
return new Promise((resolve, reject) => {
setTimeout(resolve, duration)
})
}
class TrafficLight {
constructor(initStates) {
this.stateLength = initStates.length
this.states = initStates
}
*[Symbol.iterator]() {
const max = Number.POSITIVE_INFINITY
const states = this.states
for (let i = 0; i < max; i++) {
const index = i % this.stateLength
yield this.states[index]
}
}
}
var trafficLight = new TrafficLight([
{
color: "green",
duration: 2000
},
{
color: "yellow",
duration: 3000
},
{
color: "red",
duration: 4000
}
])
var startWork = async function() {
for(let item of trafficLight ) {
document.getElementById("circle").style.backgroundColor = item.color
await delay(item.duration)
}
}
startWork()
复制代码
已经到这一步了,你知道怎么实现 暂停 和 恢复了吗?若是不知道,建议你继续想一下~浏览器
咱们已经封装了一个信号灯类,这个实例有一个初始化的数组对象,它保存了信号灯的几种状态。咱们也自定义了迭代器。bash
咱们须要一个flag,这个flag用来标记是不是暂停状态,若是是暂停状态,咱们保存当前的index,而且在自定义迭代器里面执行break
[那么这次迭代就中止了实际上]。咱们恢复操做的时候,由于已经记录了从哪个index开始中止的,咱们恢复方法要作的操做就是:从该index开始遍历便可。
啊,最后的 代码长下面这样:
function delay(duration) {
return new Promise((resolve, reject) => {
setTimeout(resolve, duration)
})
}
class TrafficLight {
constructor(initStates) {
this.stateLength = initStates.length
this.states = initStates
this.startIndex = 0 // 暂停以后保存的index,须要存放在这里
this.pauseStatus = false
}
setStartIndex(index) {
this.startIndex = index
}
setStartIndexToZero() {
this.startIndex = 0
}
pauseProcess() {
this.pauseStatus = true
}
recoveryProcess() {
this.pauseStatus = false
this.startWork()
}
async startWork() {
for(let item of this ) {
// 操做能够封装到函数,传递进来
document.getElementById("circle").style.backgroundColor = item.color
await delay(item.duration)
}
}
*[Symbol.iterator]() {
const max = Number.POSITIVE_INFINITY
const states = this.states
for (let i = this.startIndex; i<max; i++) {
const index = i % this.stateLength
// 在这里控制暂停 or 中止
// core code
if (this.pauseStatus) {
this.startIndex = index === 0 ? (this.stateLength - 1) : (index - 1)
break
}
yield this.states[index]
}
}
}
var trafficLight = new TrafficLight([
{
color: "green",
duration: 2000
},
{
color: "yellow",
duration: 3000
},
{
color: "red",
duration: 4000
}
])
trafficLight.startWork()
复制代码
到此就完毕了,中止和从新开始没有作,这个应该很容易实现了,要不你本身动手试试吧!
后来想了下,不必定非要用自定义迭代器,咱们能够修改这个类的startWork方法,遍历实例的states数组,仍然用求余数的方式去实现无限循环,而后await delay便可,把pause状态的判断移到startWork方法里面也能作出来。
咱们用单个keyframe来实现 一个keyframe里面继承3种颜色的变化,可是这个是存在偏差的,咱们要计算关键帧里面的转换时机,实际上是不太友好的。
代码长这样:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>test</title>
<style> #circle { background-color: green; border-radius: 50%; width: 100px; height: 100px; } /** 好比 绿灯2s 黄灯2s 红灯2s 咱们在一个frame里面去尝试 算比如例 绿灯 占了多久时间 1/3 33.333% 黄灯 占了多少时间 1/3 33.333% 红灯 占了多少时间 1/3 33.333% */ @keyframes single-keyframe { 0% { background-color: green; } 33.332% { background-color: green; } 33.333% { background-color: yellow; } 66.665% { background-color: yellow } 66.666% { background-color: red; } 99.999% { background-color: red; } } .single-animation { animation: single-keyframe 6s linear infinite; animation-fill-mode: forwards; } </style>
</head>
<body>
<div id="circle" class="single-animation"></div>
</body>
</html>
复制代码
而后咱们用另一种办法,定义多个keyframes【animation】来完成,利用animation的延时操做,来实现。 css 长这样,
#circle {
background-color: green;
border-radius: 50%;
width: 100px;
height: 100px;
}
.scroll-animation {
animation:
greenAnimation 2s linear,
yellowAnimation 2s linear 2s,
redAnimation 2s linear 4s;
animation-fill-mode: forwards;
}
@keyframes greenAnimation {
from {background-color: green;}
to {background-color: green;}
}
@keyframes yellowAnimation {
from {background-color: yellow;}
to {background-color: yellow;}
}
@keyframes redAnimation {
from {background-color: red;}
to {background-color: red;}
}
复制代码
可是我一直没有找到,重复一个复合动画的方法。一开始我尝试的是使用setInterval方法,而后去掉 class 而后 新增这个 class,可是不会从新 start 这个动画。
var interval = setInterval(()=> {
const circleEl = document.getElementById("circle")
circleEl.className = ""
circleEl.className = "scroll-animation"
}, 6000)
复制代码
最后在css tricks上面找到了这个办法。
请看最后的完整代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>经过动画来实现红绿灯</title>
<style> #circle { background-color: green; border-radius: 50%; width: 100px; height: 100px; } .scroll-animation { animation: greenAnimation 2s linear, yellowAnimation 2s linear 2s, redAnimation 2s linear 4s; animation-fill-mode: forwards; } @keyframes greenAnimation { from {background-color: green;} to {background-color: green;} } @keyframes yellowAnimation { from {background-color: yellow;} to {background-color: yellow;} } @keyframes redAnimation { from {background-color: red;} to {background-color: red;} } </style>
</head>
<body>
<div id="circle" class="scroll-animation"></div>
<script type="text/javascript"> // 方法一 var interval = setInterval(()=> { const circleEl = document.getElementById("circle") circleEl.className = "" // -> triggering reflow /* The actual magic */ // without this it wouldn't work. Try uncommenting the line // and the transition won't be retriggered. // Oops! This won't work in strict mode. Thanks Felis Phasma! // element.offsetWidth = element.offsetWidth; // Do this instead: void circleEl.offsetWidth; circleEl.className = "scroll-animation" }, 6000) // 方法二 // var interval = setInterval(()=> { // const circleEl = document.getElementById("circle"); // // 用jquery或者其余方式抹平浏览器之间的差别 // circleEl.style.webkitAnimationPlayState="paused"; // circleEl.style.webkitAnimationPlayState="running"; // }, 6000) </script>
</body>
</html>
复制代码
实际上仍是用到了js,不是纯css来实现的。 参考: css-tricks.com/restart-css…
po主以前在csdn的博客,但愿对你有帮助: blog.csdn.net/a5534789