【javascript】一個能夠配置的紅綠燈

需求

實現一個信號燈,這個信號燈,有黃綠紅,他們各自亮燈的持續時間是 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來實現

咱們經過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來實現

這樣是可行的,可是這個回調看起來讓人抓狂,咱們用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進行優化

可是感受這樣仍是不夠優雅,咱們用async作一下優化函數startWork:jquery

async function startWork() {
       await changeColor("green", 2000)
       await changeColor("yellow", 3000)
       await changeColor("red", 4000)
       startWork()
   }
複製代碼

沒錯,這樣更簡潔了,可是到目前爲止,可是咱們想實現暫停和開始,中止和從新開始。 其實我一開始想的,中止的話,最好是循環,break出循環便可。那麼咱們能夠循環來實現這個一直重複的動做呢,答案是能夠的。web

好了 核心來了數組

經過Symbol.iterator改良promise/async版本

咱們封裝到一個類裏面,把這個一直持續的過程,經過自定義的迭代器來實現: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方法裏面也能作出來。


評論的鐵子說用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

相關文章
相關標籤/搜索