CSS3動畫實現3D倒數計時效果

大猿猴發表於2018-08-01

先介紹CSS3動畫如何使用?兩步即可搞定。

  1. 定義動畫規則

    @keyframe animationName{  
         timeStamp{  
             attr1:value1;  
             attr2:value2;  
             ...
             attrn,valuen;  
         }  
     }
    複製程式碼

說明:

  • animationName表示動畫名稱;
  • timeStamp表示時間點,可取值from,to或0-100%,from表示0,to表示100%;
  • 每個時間點可以指定多個樣式屬性attr與對應值value。
  1. 元素應用動畫

     elemSelector{  
         animation: animationName animationDuration;   
     }
    複製程式碼

說明:

  • elemSelector表示元素選擇器
  • 執行一個動畫至少指定動畫名稱animationName和動畫時長animationDuration
  • animation屬性是以下屬性的簡寫形式,每個屬性的含義說明如下表,
屬性名稱 含義
animation-name @keyframe動畫的名稱
animation-duration 動畫的時長,需指定單位如s或ms,預設值為0
animation-timing-function 動畫的速度曲線,預設值為ease,其他取值有ease-in、ease-out、ease-in-out、linear、step-start、step-end、貝塞爾曲線函式cubic-bezier、步進函式steps
animation-delay 動畫延時多久開始,需指定指定單位如s或ms,預設為0,,取正值表示延時,負值表示超前
animation-iteration-count 動畫播放次數,預設為1
animation-direction 動畫是否在下一週期逆向播放,預設是normal,其他取值有reverse、alternate、alternate-reverse
animation-play-state 動畫的播放狀態,是執行還是暫停,預設是running,其他取值有paused
animation-fill-mode 動畫執行前、後是否應用目標狀態,預設是none,其他取值有forwards、backwards、both

重點來了,一個倒數計時效果是如何實現的,先看效果。由於是壓縮生成的gif,所以看起來會很快。

倒數計時

下面對主要程式碼進行說明,

  • HTML部分

      <!--用於呈現數字-->
      <div id="number"></div>
      <!--用於重新倒數計時-->
      <button class="button">再來一次</button>
       <!--用於最後的聲音播放。-->
      <audio ></audio>
    複製程式碼
  • CSS部分

      /*初始化數字div樣式*/
      #number {
          position: absolute;
          top: 50%;
          left: 50%;
      
          text-align: center;
      
          -webkit-transform: translate3d(-50%, -50%, 0);
          -moz-transform: translate3d(-50%, -50%, 0);
          transform: translate3d(-50%, -50%, 0);
      }
      
      /*繫結動畫*/
      .number-anim {
          -webkit-animation: animation_in 10s linear;
          -moz-animation: animation_in 10s linear;
          /*動畫時長是10s,且動畫的速度是線性的*/
          animation: animation_in 10s linear;
      }
      
      /*定義動畫規則*/
      @keyframes animation_in {
          0 {}
          10% {
              /*由於動畫時長是10s,此時是1s時刻,字型大小變為100px,z方向距離主螢幕為300px*/
              font-size: 100px;
              -webkit-transform: translate3d(-50%, -50%, -300px);
              -moz-transform: translate3d(-50%, -50%, -300px);
              transform: translate3d(-50%, -50%, -300px);
          }
          11% {
              /*隨後字型大小變為300px,z方向距離主螢幕為0,後面每到整秒數都會重複進行*/
              font-size: 300px;
              -webkit-transform: translate3d(-50%, -50%, 0);
              -moz-transform: translate3d(-50%, -50%, 0);
              transform: translate3d(-50%, -50%, 0);
          }
          20% {
              font-size: 100px;
              -webkit-transform: translate3d(-50%, -50%, -300px);
              -moz-transform: translate3d(-50%, -50%, -300px);
              transform: translate3d(-50%, -50%, -300px);
          }
          21% {
              font-size: 300px;
              -webkit-transform: translate3d(-50%, -50%, 0);
              -moz-transform: translate3d(-50%, -50%, 0);
              transform: translate3d(-50%, -50%, 0);
          }
          30% {
              font-size: 100px;
              -webkit-transform: translate3d(-50%, -50%, -300px);
              -moz-transform: translate3d(-50%, -50%, -300px);
              transform: translate3d(-50%, -50%, -300px);
          }
          31% {
              font-size: 300px;
              -webkit-transform: translate3d(-50%, -50%, 0);
              -moz-transform: translate3d(-50%, -50%, 0);
              transform: translate3d(-50%, -50%, 0);
          }
          40% {
              font-size: 100px;
              -webkit-transform: translate3d(-50%, -50%, -300px);
              -moz-transform: translate3d(-50%, -50%, -300px);
              transform: translate3d(-50%, -50%, -300px);
          }
          41% {
              font-size: 300px;
              -webkit-transform: translate3d(-50%, -50%, 0);
              -moz-transform: translate3d(-50%, -50%, 0);
              transform: translate3d(-50%, -50%, 0);
          }
          50% {
              font-size: 100px;
              -webkit-transform: translate3d(-50%, -50%, -300px);
              -moz-transform: translate3d(-50%, -50%, -300px);
              transform: translate3d(-50%, -50%, -300px);
          }
          51% {
              font-size: 300px;
              -webkit-transform: translate3d(-50%, -50%, 0);
              -moz-transform: translate3d(-50%, -50%, 0);
              transform: translate3d(-50%, -50%, 0);
          }
          60% {
              font-size: 100px;
              -webkit-transform: translate3d(-50%, -50%, -300px);
              -moz-transform: translate3d(-50%, -50%, -300px);
              transform: translate3d(-50%, -50%, -300px);
          }
          61% {
              font-size: 300px;
              -webkit-transform: translate3d(-50%, -50%, 0);
              -moz-transform: translate3d(-50%, -50%, 0);
              transform: translate3d(-50%, -50%, 0);
          }
          70% {
              font-size: 100px;
              -webkit-transform: translate3d(-50%, -50%, -300px);
              -moz-transform: translate3d(-50%, -50%, -300px);
              transform: translate3d(-50%, -50%, -300px);
          }
          71% {
              font-size: 300px;
              -webkit-transform: translate3d(-50%, -50%, 0);
              -moz-transform: translate3d(-50%, -50%, 0);
              transform: translate3d(-50%, -50%, 0);
          }
          80% {
              font-size: 100px;
              -webkit-transform: translate3d(-50%, -50%, -300px);
              -moz-transform: translate3d(-50%, -50%, -300px);
              transform: translate3d(-50%, -50%, -300px);
          }
          81% {
              font-size: 300px;
              -webkit-transform: translate3d(-50%, -50%, 0);
              -moz-transform: translate3d(-50%, -50%, 0);
              transform: translate3d(-50%, -50%, 0);
          }
          90% {
              font-size: 100px;
              -webkit-transform: translate3d(-50%, -50%, -300px);
              -moz-transform: translate3d(-50%, -50%, -300px);
              transform: translate3d(-50%, -50%, -300px);
          }
          91% {
              font-size: 300px;
              -webkit-transform: translate3d(-50%, -50%, 0);
              -moz-transform: translate3d(-50%, -50%, 0);
              transform: translate3d(-50%, -50%, 0);
          }
          100% {
              font-size: 100px;
              -webkit-transform: translate3d(-50%, -50%, -300px);
              -moz-transform: translate3d(-50%, -50%, -300px);
              transform: translate3d(-50%, -50%, -300px);
          }
       }
    複製程式碼
  • JS部分

    window.onload = function() {
      
      //第一步:定義全域性變數
      num = 10;//數字內容
      isCounting = true;//是否正在計數
      timer = null;//定時器
      numberDiv = document.querySelector("#number");
      audio = document.querySelector("audio");
      button = document.querySelector(".button");
    
      //第二步:初始化
      init();
    
      //第三步:開始倒數計時
      timer = setInterval(count, 1000);
        
    }
    複製程式碼

    初始化函式宣告如下,

       function init() {
          numberDiv.innerHTML = num;
          numberDiv.style.color =  getRandomColor();
          numberDiv.style.fontSize = "300px";
          numberDiv.className = "number-anim";
        
          button.addEventListener("click", function() {
              if (isCounting) {
                  return;
              }
              isCounting = true;
              num = 10;
      
              numberDiv.innerHTML = num;
              numberDiv.style.color = getRandomColor();   
              numberDiv.style.fontSize = "300px";
              
              //先解綁,再使用setTimeout使瀏覽器重新渲染頁面,重新繫結
              //setTimeout只是一種方式,只要能使瀏覽器重新渲染即可      
              numberDiv.className = null;
              setTimeout(function() {
                  numberDiv.className = "number-anim";
      
                  timer = setInterval(count, 1000);
              }, 30); 
          }, false);
      }
    複製程式碼

    倒計數函式宣告如下,

      function count() {
      
          numberDiv.innerHTML = --num;
          numberDiv.style.color = getRandomColor();
        
          if (num == 0) {
              clearInterval(timer);
      
              audio.src="./audio/readygo.mp3";
              audio.play();
      
              numberDiv.innerHTML = "Ready Go!";
              numberDiv.style.color = getRandomColor();
              numberDiv.style.fontSize = "100px";
      
              numberDiv.style.opacity = 0.8;
              numberDiv.style.filter = "alpha(opacity=80)";
              CompatibleFunc(numberDiv, "Transition", "opacity 1s");
      
              isCounting = false;
          }
      }
    複製程式碼

這裡有一個地方需要特別注意,避免以後踩坑。

關於動畫樣式的解綁與繫結。

動畫樣式animation一執行結束,就不再起作用了,要使其重新生效,需要重新繫結。我試了4種方式。

  • 第一種是直接解綁,然後再繫結。

      numberDiv.className = null;
      numberDiv.className = "number-anim";
    複製程式碼

    結果是不能使動畫重新生效。

  • 第二種是使用classlist屬性進行解繫結。

      numberDiv.classList.toggle("number-anim");
      numberDiv.classList.toggle("number-anim");
    複製程式碼

    classList返回類名列表物件,呼叫toggle方法,若類名存在則刪除,返回false,若類名不存在則新增,返回true,所以要呼叫兩次,第一次刪除類名,第二次新增類名。但是結果依然是不能使動畫重新生效。

  • 第三種是開啟定時器。

    先解綁,再利用定時器使瀏覽器重新渲染頁面,重新繫結。setTimeout只是一種方式,只要能使瀏覽器重新渲染即可,最終動畫重新生效。

      numberDiv.className = null;
      setTimeout(function() {
          numberDiv.className = "number-anim";
      }, 30); 
    複製程式碼

    在mozilla官方文件介紹了另一種重新渲染方式,

    https://developer.mozilla.org/zh-CN/docs/Web/CSS/CSS_Animations/Tips
    複製程式碼

    應用在這裡的話,寫法是,

      numberDiv.className = null;
      window.requestAnimationFrame(function(){
          window.requestAnimationFrame(function(){
              numberDiv.className = "number-anim";
          });
      });
    複製程式碼
  • 第四種是藉助其他重新渲染途徑,如顏色、內容、大小的變化。

      numberDiv.className = null;
    
      numberDiv.innerHTML = num;
      numberDiv.style.color = getRandomColor();   
      numberDiv.style.fontSize = "300px";
      
      numberDiv.className = "number-anim";
    複製程式碼

    結果IE和Microsoft Edge 瀏覽器是支援的,但是FireFox和Chrome不支援, 但這是不是也說明了Chrome和FireFox已經對瀏覽器渲染做了優化?

簡而言之,要使動畫重新生效,需要觸發瀏覽器重新渲染。
好了,附上原始碼連結。

https://github.com/muzhidong/frontend-demo/tree/master/countdown
複製程式碼

相關文章