需求
實現一個訊號燈,這個訊號燈,有黃綠紅,他們各自亮燈的持續時間是 1s,2s,3s 如此反覆。
前景提要,我們的html程式碼是這樣:
<!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來實現
我們通過setTimeout或者setInterval來實現:
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來實現
這樣是可行的,但是這個回撥看起來讓人抓狂,我們用promise來實現這一版本
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進行優化
但是感覺這樣還是不夠優雅,我們用async做一下優化函式startWork:
async function startWork() {
await changeColor("green", 2000)
await changeColor("yellow", 3000)
await changeColor("red", 4000)
startWork()
}
複製程式碼
沒錯,這樣更簡潔了,但是到目前為止,但是我們想實現暫停和開始,停止和重新開始。 其實我一開始想的,停止的話,最好是迴圈,break出迴圈即可。那麼我們可以迴圈來實現這個一直重複的動作呢,答案是可以的。
好了 核心來了:
通過Symbol.iterator改良promise/async版本
我們封裝到一個類裡面,把這個一直持續的過程,通過自定義的迭代器來實現:
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()
複製程式碼
在自定義迭代裡面去控制暫停
已經到這一步了,你知道怎麼實現 暫停 和 恢復了嗎?如果不知道,建議你繼續想一下~
我們已經封裝了一個訊號燈類,這個例項有一個初始化的陣列物件,它儲存了訊號燈的幾種狀態。我們也自定義了迭代器。
我們需要一個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方法裡面也能做出來。
評論的鐵子說用css動畫來做,來實現一個版本
使用單個動畫來實現
我們用單個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