<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          從打字機效果的N種實現(xiàn)看JS定時器機制和前端動畫

          共 8599字,需瀏覽 18分鐘

           ·

          2021-02-01 23:49

          來源 |?https://king-hcj.github.io/2021/01/10/js-typed/


          首先,什么是打字機效果呢?一圖勝千言,諸君請看:

          打字機效果即為文字逐個輸出,實際上就是Web動畫。

          在Web應(yīng)用中,實現(xiàn)動畫效果的方法比較多,JavaScript 中可以通過定時器 setTimeout 來實現(xiàn),css3 可以使用 transition 和 animation 來實現(xiàn),html5 中的 canvas 也可以實現(xiàn)。

          除此之外,html5 還提供一個專門用于請求動畫的 API,即 requestAnimationFrame(rAF),顧名思義就是 “請求動畫幀”。

          接下來,我們一起來看看 打字機效果 的幾種實現(xiàn)。為了便于理解,我會盡量使用簡潔的方式進行實現(xiàn),有興趣的話,你也可以把這些實現(xiàn)改造的更有逼格、更具藝術(shù)氣息一點,因為編程,本來就是一門藝術(shù)。

          打字機效果的 N 種實現(xiàn)

          實現(xiàn)一:setTimeout()

          setTimeout版本的實現(xiàn)很簡單,只需把要展示的文本進行切割,使用定時器不斷向DOM元素里追加文字即可,同時,使用::after偽元素在DOM元素后面產(chǎn)生光標(biāo)閃爍的效果。代碼和效果圖如下:

          setTimeout()方法的返回值是一個唯一的數(shù)值(ID),上面的代碼中,我們也做了setTimeout()返回值的打印,那么,這個數(shù)值有什么用呢?

          如果你想要終止setTimeout()方法的執(zhí)行,那就必須使用 clearTimeout()方法來終止,而使用這個方法的時候,系統(tǒng)必須知道你到底要終止的是哪一個setTimeout()方法(因為你可能同時調(diào)用了好幾個 setTimeout()方法),這樣clearTimeout()方法就需要一個參數(shù),這個參數(shù)就是setTimeout()方法的返回值(數(shù)值),用這個數(shù)值來唯一確定結(jié)束哪一個setTimeout()方法。

          實現(xiàn)二:setInterval()

          setInterval實現(xiàn)的打字機效果,其實在MDN window.setInterval 案例三中已經(jīng)有一個了,而且還實現(xiàn)了播放、暫停以及終止的控制,效果可點擊這里查看,在此只進行setInterval打字機效果的一個最簡單實現(xiàn),其實代碼和前文setTimeout的實現(xiàn)類似,效果也一致。

          (function () {  // 獲取容器  const container = document.getElementById('content')  // 把需要展示的全部文字進行切割  const data = '最簡單的打字機效果實現(xiàn)'.split('')  // 需要追加到容器中的文字下標(biāo)  let index = 0  let timer = null  function writing() {    if (index < data.length) {      // 追加文字      container.innerHTML += data[index ++]      // 沒錯,也可以通過,clearTimeout取消setInterval的執(zhí)行      // index === 4 && clearTimeout(timer)    } else {      clearInterval(timer)    }    console.log(timer) // 這里會打印出 1 1 1 1 1 ...  }  // 使用 setInterval 時,結(jié)束后不要忘記進行 clearInterval  timer = setInterval(writing, 200)})();

          和setTimeout一樣,setInterval也會返回一個 ID(數(shù)字),可以將這個ID傳遞給clearInterval()或者clearTimeout() 以取消定時器的執(zhí)行。

          在此有必要強調(diào)一點:定時器指定的時間間隔,表示的是何時將定時器的代碼添加到消息隊列,而不是何時執(zhí)行代碼。所以真正何時執(zhí)行代碼的時間是不能保證的,取決于何時被主線程的事件循環(huán)取到,并執(zhí)行。

          實現(xiàn)三:requestAnimationFrame()

          在動畫的實現(xiàn)上,requestAnimationFrame 比起 setTimeout 和 setInterval來無疑更具優(yōu)勢。我們先看看打字機效果的requestAnimationFrame實現(xiàn):

          (function () {    const container = document.getElementById('content')    const data = '與 setTimeout 相比,requestAnimationFrame 最大的優(yōu)勢是 由系統(tǒng)來決定回調(diào)函數(shù)的執(zhí)行時機。具體一點講就是,系統(tǒng)每次繪制之前會主動調(diào)用 requestAnimationFrame 中的回調(diào)函數(shù),如果系統(tǒng)繪制率是 60Hz,那么回調(diào)函數(shù)就每16.7ms 被執(zhí)行一次,如果繪制頻率是75Hz,那么這個間隔時間就變成了 1000/75=13.3ms。換句話說就是,requestAnimationFrame 的執(zhí)行步伐跟著系統(tǒng)的繪制頻率走。它能保證回調(diào)函數(shù)在屏幕每一次的繪制間隔中只被執(zhí)行一次,這樣就不會引起丟幀現(xiàn)象,也不會導(dǎo)致動畫出現(xiàn)卡頓的問題。'.split('')    let index = 0    function writing() {      if (index < data.length) {        container.innerHTML += data[index ++]        requestAnimationFrame(writing)      }    }    writing()  })();

          與setTimeout相比,requestAnimationFrame最大的優(yōu)勢是由系統(tǒng)來決定回調(diào)函數(shù)的執(zhí)行時機。具體一點講,如果屏幕刷新率是60Hz,那么回調(diào)函數(shù)就每16.7ms被執(zhí)行一次,如果刷新率是75Hz,那么這個時間間隔就變成了1000/75=13.3ms,換句話說就是,requestAnimationFrame的步伐跟著系統(tǒng)的刷新步伐走。

          它能保證回調(diào)函數(shù)在屏幕每一次的刷新間隔中只被執(zhí)行一次,這樣就不會引起丟幀現(xiàn)象,也不會導(dǎo)致動畫出現(xiàn)卡頓的問題。

          實現(xiàn)四:CSS3

          除了以上三種js方法之外,其實只用CSS我們也可以實現(xiàn)打字機效果。大概思路是借助CSS3的@keyframes來不斷改變包含文字的容器的寬度,超出容器部分的文字隱藏不展示。


          大江東去浪淘盡,千古風(fēng)流人物

          以上CSS打字機效果的原理一目了然:

          初始文字是全部在頁面上的,只是容器的寬度為0,設(shè)置文字超出部分隱藏,然后不斷改變?nèi)萜鞯膶挾龋?/span>

          設(shè)置border-right,并在關(guān)鍵幀上改變 border-color 為transparent,右邊框就像閃爍的光標(biāo)了。

          實現(xiàn)五:Typed.js

          Typed.js is a library that types. Enter in any string, and watch it type at the speed you've set, backspace what it's typed, and begin a new sentence for however many strings you've set.

          Typed.js是一個輕量級的打字動畫庫, 只需要幾行代碼,就可以在項目中實現(xiàn)炫酷的打字機效果(本文第一張動圖即為Typed.js實現(xiàn))。源碼也相對比較簡單,有興趣的話,可以到GitHub進行研讀。


          Document

          Typed.js is a JavaScript library.

          It types out sentences.


          使用Typed.js,我們也可以很容易的實現(xiàn)對動畫開始、暫停等的控制:
              

          參考資料:Typed.js官網(wǎng)?|?Typed.js GitHub地址

          當(dāng)然,打字機效果的實現(xiàn)方式,也不僅僅局限于上面所說的幾種方法,本文的目的,也不在于搜羅所有打字機效果的實現(xiàn),如果那樣將毫無意義,接下來,我們將會對CSS3動畫和JS動畫進行一些比較,并對setTimeout、setInterval 和 requestAnimationFrame的一些細(xì)節(jié)進行總結(jié)。

          CSS3動畫和JS動畫的比較

          關(guān)于CSS動畫和JS動畫,有一種說法是CSS動畫比JS流暢,其實這種流暢是有前提的。借此機會,我們對CSS3動畫和JS動畫進行一個簡單對比。

          JS動畫

          優(yōu)點:

          JS動畫控制能力強,可以在動畫播放過程中對動畫進行精細(xì)控制,如開始、暫停、終止、取消等;

          JS動畫效果比CSS3動畫豐富,功能涵蓋面廣,比如可以實現(xiàn)曲線運動、沖擊閃爍、視差滾動等CSS難以實現(xiàn)的效果;

          JS動畫大多數(shù)情況下沒有兼容性問題,而CSS3動畫有兼容性問題;

          缺點:

          JS在瀏覽器的主線程中運行,而主線程中還有其它需要運行的JS腳本、樣式計算、布局、繪制任務(wù)等,對其干擾可能導(dǎo)致線程出現(xiàn)阻塞,從而造成丟幀的情況;

          對于幀速表現(xiàn)不好的低版本瀏覽器,CSS3可以做到自然降級,而JS則需要撰寫額外代碼;

          JS動畫往往需要頻繁操作DOM的css屬性來實現(xiàn)視覺上的動畫效果,這個時候瀏覽器要不停地執(zhí)行重繪和重排,這對于性能的消耗是很大的,尤其是在分配給瀏覽器的內(nèi)存沒那么寬裕的移動端。

          CSS3動畫

          優(yōu)點:

          部分情況下瀏覽器可以對動畫進行優(yōu)化(比如專門新建一個圖層用來跑動畫),為什么說部分情況下呢,因為是有條件的:

          在Chromium基礎(chǔ)上的瀏覽器中

          同時CSS動畫不觸發(fā)layout或paint,在CSS動畫或JS動畫觸發(fā)了paint或layout時,需要main thread進行Layer樹的重計算,這時CSS動畫或JS動畫都會阻塞后續(xù)操作。

          部分效果可以強制使用硬件加速 (通過 GPU 來提高動畫性能)

          缺點:

          代碼冗長。CSS 實現(xiàn)稍微復(fù)雜一點動畫,CSS代碼可能都會變得非常笨重;

          運行過程控制較弱。css3動畫只能在某些場景下控制動畫的暫停與繼續(xù),不能在特定的位置添加回調(diào)函數(shù)。

          main thread(主線程)和compositor thread(合成器線程)

          渲染線程分為main thread(主線程)和compositor thread(合成器線程)。主線程中維護了一棵Layer樹(LayerTreeHost),管理了TiledLayer,在compositor thread,維護了同樣一顆LayerTreeHostImpl,管理了LayerImpl,這兩棵樹的內(nèi)容是拷貝關(guān)系。因此可以彼此不干擾,當(dāng)Javascript在main thread操作LayerTreeHost的同時,compositor thread可以用LayerTreeHostImpl做渲染。當(dāng)Javascript繁忙導(dǎo)致主線程卡住時,合成到屏幕的過程也是流暢的。

          為了實現(xiàn)防假死,鼠標(biāo)鍵盤消息會被首先分發(fā)到compositor thread,然后再到main thread。這樣,當(dāng)main thread繁忙時,compositor thread還是能夠響應(yīng)一部分消息,例如,鼠標(biāo)滾動時,如果main thread繁忙,compositor thread也會處理滾動消息,滾動已經(jīng)被提交的頁面部分(未被提交的部分將被刷白)。

          CSS動畫比JS動畫流暢的前提

          CSS動畫比較少或者不觸發(fā)pain和layout,即重繪和重排時。例如通過改變?nèi)缦聦傩陨傻腸ss動畫,這時整個CSS動畫得以在compositor thread完成(而JS動畫則會在main thread執(zhí)行,然后觸發(fā)compositor進行下一步操作):

          backface-visibility:該屬性指定當(dāng)元素背面朝向觀察者時是否可見(3D,實驗中的功能);

          opacity:設(shè)置 div 元素的不透明級別;

          perspective 設(shè)置元素視圖,該屬性只影響 3D 轉(zhuǎn)換元素;

          perspective-origin:該屬性允許您改變 3D 元素的底部位置;

          transform:該屬性應(yīng)用于元素的2D或3D轉(zhuǎn)換。這個屬性允許你將元素旋轉(zhuǎn),縮放,移動,傾斜等。

          JS在執(zhí)行一些昂貴的任務(wù)時,main thread繁忙,CSS動畫由于使用了compositor thread可以保持流暢;

          部分屬性能夠啟動3D加速和GPU硬件加速,例如使用transform的translateZ進行3D變換時;

          通過設(shè)置 will-change 屬性,瀏覽器就可以提前知道哪些元素的屬性將會改變,提前做好準(zhǔn)備。待需要改變元素的時機到來時,就可以立刻實現(xiàn)它們,從而避免卡頓等問題。

          不要將 will-change 應(yīng)用到太多元素上,如果過度使用的話,可能導(dǎo)致頁面響應(yīng)緩慢或者消耗非常多的資源。

          例如下面的代碼就是提前告訴渲染引擎 box 元素將要做幾何變換和透明度變換操作,這時候渲染引擎會將該元素單獨實現(xiàn)一幀,等這些變換發(fā)生時,渲染引擎會通過合成線程直接去處理變換,這些變換并沒有涉及到主線程,這樣就大大提升了渲染的效率。

          .box {will-change: transform, opacity;}

          setTimeout、setInterval 和 requestAnimationFrame 的一些細(xì)節(jié)

          setTimeout 和 setInterval

          setTimeout 的執(zhí)行時間并不是確定的。在JavaScript中,setTimeout 任務(wù)被放進了異步隊列中,只有當(dāng)主線程上的任務(wù)執(zhí)行完以后,才會去檢查該隊列里的任務(wù)是否需要開始執(zhí)行,所以 setTimeout 的實際執(zhí)行時機一般要比其設(shè)定的時間晚一些。

          刷新頻率受 屏幕分辨率 和 屏幕尺寸 的影響,不同設(shè)備的屏幕繪制頻率可能會不同,而 setTimeout 只能設(shè)置一個固定的時間間隔,這個時間不一定和屏幕的刷新時間相同。

          setTimeout 的執(zhí)行只是在內(nèi)存中對元素屬性進行改變,這個變化必須要等到屏幕下次繪制時才會被更新到屏幕上。如果兩者的步調(diào)不一致,就可能會導(dǎo)致中間某一幀的操作被跨越過去,而直接更新下一幀的元素。假設(shè)屏幕每隔16.7ms刷新一次,而setTimeout 每隔10ms設(shè)置圖像向左移動1px, 就會出現(xiàn)如下繪制過程:

          第 0 ms:屏幕未繪制,等待中,setTimeout 也未執(zhí)行,等待中;

          第 10 ms:屏幕未繪制,等待中,setTimeout 開始執(zhí)行并設(shè)置元素屬性 left=1px;

          第 16.7 ms:屏幕開始繪制,屏幕上的元素向左移動了 1px, setTimeout 未執(zhí)行,繼續(xù)等待中;

          第 20 ms:屏幕未繪制,等待中,setTimeout 開始執(zhí)行并設(shè)置 left=2px;

          第 30 ms:屏幕未繪制,等待中,setTimeout 開始執(zhí)行并設(shè)置 left=3px;

          第 33.4 ms:屏幕開始繪制,屏幕上的元素向左移動了 3px, setTimeout 未執(zhí)行,繼續(xù)等待中;

          ...

          從上面的繪制過程中可以看出,屏幕沒有更新 left=2px 的那一幀畫面,元素直接從left=1px 的位置跳到了 left=3px 的的位置,這就是丟幀現(xiàn)象,這種現(xiàn)象就會引起動畫卡頓。

          setInterval的回調(diào)函數(shù)調(diào)用之間的實際延遲小于代碼中設(shè)置的延遲,因為回調(diào)函數(shù)執(zhí)行所需的時間“消耗”了間隔的一部分,如果回調(diào)函數(shù)執(zhí)行時間長、執(zhí)行次數(shù)多的話,誤差也會越來越大:

          // repeat with the interval of 2 secondslet timerId = setInterval(() => console.log('tick', timerId), 2000);// after 50 seconds stopsetTimeout(() => {  clearInterval(timerId);  console.log('stop', timerId);}, 50000);

          嵌套的setTimeout可以保證固定的延遲:

          let timerId = setTimeout(function tick() {  console.log('tick', timerId);  timerId = setTimeout(tick, 2000); // (*)}, 2000);

          requestAnimationFrame

          除了上文提到的requestAnimationFrame的優(yōu)勢外,requestAnimationFrame還有以下兩個優(yōu)勢:

          CPU節(jié)能:使用setTimeout實現(xiàn)的動畫,當(dāng)頁面被隱藏或最小化時,setTimeout 仍然在后臺執(zhí)行動畫任務(wù),由于此時頁面處于不可見或不可用狀態(tài),刷新動畫是沒有意義的,完全是浪費CPU資源。

          而requestAnimationFrame則完全不同,當(dāng)頁面處于未激活的狀態(tài)下,該頁面的屏幕刷新任務(wù)也會被系統(tǒng)暫停,因此跟著系統(tǒng)步伐走的requestAnimationFrame也會停止渲染,當(dāng)頁面被激活時,動畫就從上次停留的地方繼續(xù)執(zhí)行,有效節(jié)省了CPU開銷。

          函數(shù)節(jié)流:在高頻率事件(resize,scroll等)中,為了防止在一個刷新間隔內(nèi)發(fā)生多次函數(shù)執(zhí)行,使用requestAnimationFrame可保證每個刷新間隔內(nèi),函數(shù)只被執(zhí)行一次,這樣既能保證流暢性,也能更好的節(jié)省函數(shù)執(zhí)行的開銷。

          一個刷新間隔內(nèi)函數(shù)執(zhí)行多次是沒有意義的,因為顯示器每16.7ms刷新一次,多次繪制并不會在屏幕上體現(xiàn)出來。

          關(guān)于最小時間間隔

          2011年的標(biāo)準(zhǔn)中是這么規(guī)定的:

          setTimeout:如果當(dāng)前正在運行的任務(wù)是由setTimeout()方法創(chuàng)建的任務(wù),并且時間間隔小于4ms,則將時間間隔增加到4ms;

          setInterval:如果時間間隔小于10ms,則將時間間隔增加到10ms。

          在最新標(biāo)準(zhǔn)中:如果時間間隔小于0,則將時間間隔設(shè)置為0。如果嵌套級別大于5,并且時間間隔小于4ms,則將時間間隔設(shè)置為4ms。

          定時器的清除

          由于clearTimeout()和clearInterval()清除的是同一列表(活動計時器列表)中的條目,因此可以使用這兩種方法清除setTimeout()或 setInterval()創(chuàng)建的計時器。

          本文完~


          瀏覽 42
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  午夜成人精品视频在线 | 青青草原视频精品在线免费观看 | 2大在线观看国内黄色 | 午夜福利成人视频 | 中文字幕手机在线观看 |