成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

函數(shù)除顫/節(jié)流提高性能 + 原生實(shí)現(xiàn)滾動時到視口時展現(xiàn)

mengera88 / 805人閱讀

摘要:引言前端開發(fā)中一個老生常談的問題就是當(dāng)用戶滾動時根據(jù)滾動的位置適當(dāng)觸發(fā)不同的函數(shù)動畫例如當(dāng)元素出現(xiàn)在視口時觸發(fā)該元素的改變通常的做法就是在上附加事件但是我們知道當(dāng)滾動條滾動時事件觸發(fā)的是很頻繁的且不由控制瀏覽器的事件隊(duì)列原生提供如圖添加事件

引言

前端開發(fā)中一個老生常談的問題就是"當(dāng)用戶滾動時, 根據(jù)滾動的位置適當(dāng)觸發(fā)不同的函數(shù)/動畫, 例如當(dāng)元素出現(xiàn)在視口時觸發(fā)該元素的style改變. 通常的做法就是在scrollElement上附加scoll事件. 但是我們知道, 當(dāng)滾動條滾動時scroll事件觸發(fā)的是很頻繁的, 且不由JS控制(瀏覽器的事件隊(duì)列原生提供), 如圖(添加事件監(jiān)聽后滾輪三格):

依據(jù)系統(tǒng)設(shè)置的不同, 一次滾輪觸發(fā)的scroll事件大概在10~15次之間. 如果在回調(diào)函數(shù)中添加大量的DOM操作或者計(jì)算的話, 會引起明顯的卡頓等性能問題. 那有沒有辦法去稀釋回調(diào)函數(shù)的觸發(fā)操作呢? 這個時候就需要函數(shù)節(jié)流(throttle)和debounce(去顫抖)來解決了!

2017-02-06更新函數(shù)式版本

這個版本運(yùn)用閉包封裝數(shù)據(jù), 修正this指向以加強(qiáng)魯棒性, 剔除了一開始就顯示在視口的元素
talk is cheap, here are the code

// 根據(jù)單一元素, throttle函數(shù)專門負(fù)責(zé)事件稀釋, 接受兩個參數(shù): 要間隔調(diào)用的函數(shù), 以及調(diào)用間隔. 
var throttle = function (fn, interval) {
  let start = Date.now()
  let first = true
  return function (...args) {
    let now = Date.now()
    // 如果是第一次調(diào)用, 則忽略時間間隔
    if (first) {
      fn.apply(this, args)
      first = false
      return
    }
    if (now - start > interval) {
      fn.apply(this, args)
      start = now
    }   
  }
}
// 顯示元素的IIFE
var showElems = (function (selector, func) {
  // 預(yù)處理, 標(biāo)識已經(jīng)顯示在視口的元素
  let elemCollect = [...document.querySelectorAll(selector)]
  let innerHeight = window.innerHeight
  let hiddenElems = []
  elemCollect.forEach((elem, index) => {
    let top = elem.getBoundingClientRect().top
    // 不顯示在視口才加入判斷隊(duì)列
    if (top > innerHeight) {
      hiddenElems.push(elem)
    }
  })
  // memory release
  elemCollect = null
  return function (...args) {
    hiddenElems.forEach((elem) => {
      let bottom = elem.getBoundingClientRect().bottom
      if (bottom < innerHeight) {
        func.apply(elem, args)
      }
    })
  }
})("p", function(e){
  console.log(this, e, "showed!")
})
// 組合, throttle函數(shù)負(fù)責(zé)稀釋showElems觸發(fā)的頻率, showElems負(fù)責(zé)元素滾動到視口時的相應(yīng)動作
var throttledScroll = throttle(showElems, 500)
window.addEventListener("scroll", throttledScroll)
debounce

設(shè)想一些用戶的頻繁操作, 例如滾動, 文本框輸入等, 每次觸發(fā)事件都要調(diào)用回調(diào)函數(shù); 這樣做的代價未免大了點(diǎn). 而debounce的作用就是在在用戶高頻觸發(fā)事件時, 暫時不調(diào)用回調(diào), 在用戶停止高頻操作(例如停止輸入, 停止?jié)L動時), 再調(diào)用一次回調(diào).

解決方案有了, 怎樣用代碼實(shí)現(xiàn)呢? 這里我們要用到setTimeout這個功能來做函數(shù)調(diào)用的延遲. 具體代碼如下(將代碼粘貼到console中執(zhí)行以下, 自己試試看):

            var timer;
            document.addEventListener("scroll", function(){
                clearTimeout(timer); //如果操作時已經(jīng)有了延遲執(zhí)行, 則取消該延遲執(zhí)行
                timer = setTimeout(function() { //設(shè)定新的延遲執(zhí)行
                    callback();
                }, 500)
            })

(這里我們?yōu)榱朔奖阏f明, 設(shè)定了timer全局變量. 實(shí)踐中我們可以將timer附加為函數(shù)的屬性, 隱藏在閉包中, 或者作為對象的屬性等. )

當(dāng)第一次高頻操作觸發(fā)時, 設(shè)定一個timer, 在500ms后執(zhí)行; 如果用戶在500ms之內(nèi)沒有再次進(jìn)行該操作(本例中是滾動), 那么我們調(diào)用callback; 然而如果500ms之內(nèi)用戶觸發(fā)了滾動(即所謂的高頻操作), 那么我們清除上一次設(shè)定的timeout, 設(shè)定一個新的, 500ms之后執(zhí)行的timeout.

大家思考一下, debounce的本質(zhì)就是在用戶觸發(fā)expensive操作時, 不斷延期該expensive操作的執(zhí)行時間(取消和設(shè)定timeout的代價是很小的). 當(dāng)用戶停止操作, 那我們就不再延期, 最后一次設(shè)定的timeout會在500ms后執(zhí)行expensive operation, 例如dom操作, 計(jì)算等.

到這里我們似乎已經(jīng)有了一個解決方案! 然而還有個小小的問題.....

如果用戶不停地操作, 那debounce就會不斷把操作延期, 如果用戶沒有兩次操作的間隔時間大于500ms, 那么我們的callback永遠(yuǎn)也得不到執(zhí)行. 可憐的callback! 恩, 在這一點(diǎn)上我們當(dāng)然可以改進(jìn)...

throttle

throttle的作用是, 保證一個函數(shù)至少間隔一段時間得到一次執(zhí)行. 不像等待用戶停止的debounce, throttle即使在用戶不停操作時, 也能讓callback在操作期間得到間隔的執(zhí)行.

那么該怎么做呢? 一種方法是在用戶開始操作時記錄開始時間, 同時設(shè)定一個flag ifOperationBegin = true. 之后在每次用戶的操作中判斷當(dāng)前時間, 如果當(dāng)前時間-開始時間 > 某個值, 比如500ms, 則執(zhí)行callback, 同時設(shè)定ifOperationBegin = true, 以開始下一次的設(shè)定開始時間 -> 記錄操作時間 -> 判斷的循環(huán). 具體到代碼實(shí)現(xiàn)上:

            var scrollBegin = false, scrollStartTime = null; //用戶尚未開始操作
            document.addEventListener("scroll", function(){
                if(!scrollBegin)scrollStartTime = Date.now();//記錄開始時間, 前提是callback還沒有被觸發(fā)過
                scrollBegin = true;//設(shè)定flag
                if(Date.now() - scrollStartTime > 500){ //如果操作時間和開始時間間隔大于500ms則
                    exec(elems, cb); //調(diào)用回調(diào)
                    scrollBegin = false; //flag設(shè)為false, 以設(shè)定新的開始時間
                }
            })

這樣做的效果是, 在用戶持續(xù)觸發(fā)scroll操作時, 保證在用戶操作期間callback至少會每隔500ms觸發(fā)一次. 如果用戶操作在500ms之內(nèi)結(jié)束, 那也木有關(guān)系, 下一次用戶重新開始操作時我們的scrollStartTime 依然保留著, callback會被立即觸發(fā).

實(shí)際運(yùn)用

那這兩種技術(shù)可以運(yùn)用到哪里呢? 請看如下代碼栗子:

        function detectVisible(selector, cb, interval){ //檢測元素是否在視口的函數(shù)
            var elems = document.querySelectorAll(selector), innerHeight = window.innerHeight;
            var exec = function(elems, cb){ //回調(diào)函數(shù)
                Array.prototype.forEach.call(elems, function(elem, index){
                    if(elem.getBoundingClientRect().top < innerHeight){ //判斷元素是否出現(xiàn)在視口
                        cb.call(elem, elem); //調(diào)用傳入的回調(diào)
                    }
                })
            }
            document.addEventListener("scroll", function(){ //使用debounce和throttle來稀釋scroll事件
                clearTimeout(detectVisible.timer);
                if(!detectVisible.scrollBegin)detectVisible.scrollStartTime = Date.now();
                detectVisible.scrollBegin = true;
                if(Date.now() - detectVisible.scrollStartTime > interval){
                    exec(elems, cb);
                    console.log("invoked by throttle!")
                    detectVisible.scrollBegin = false;
                }
                detectVisible.timer = setTimeout(function() {
                    exec(elems, cb);
                    console.log("invoked by debounce!")
                }, interval)
            })
        }
        detectVisible("div.elem", function(elem){
            this.style.backgroundColor = "yellow";
        }, 500);

這個栗子中我們綜合運(yùn)用了throttledebounce, 達(dá)到了如下效果: 用戶不停滾動時callback會至少每500ms觸發(fā)一次; 用戶停止?jié)L動后的500ms判斷函數(shù)也會觸發(fā)一次. 大家可以打開console查看callback何時是被throttle觸發(fā)的, 何時是被debounce觸發(fā)的.

總結(jié)

這一篇文章的主要主題事件的稀釋以期性能上的改善, 有兩種解決方法:throttledebounce. 前者是通過在用戶操作時判斷操作時間, 來達(dá)到間隔一段時間觸發(fā)回調(diào)的效果; 而后者則是將觸發(fā)的時間不斷延期, 直到用戶停止操作再執(zhí)行回調(diào). 兩者各有優(yōu)缺點(diǎn), 兩相結(jié)合, 我們得到了一個用戶無論怎樣操作(不停操作或者操作時間極短)都可以保證callback定期得到執(zhí)行的函數(shù). problem solved!

看完這篇, 如果你有所收獲, 請去github給我加個star唄!~

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/88025.html

相關(guān)文章

  • 函數(shù)除顫/節(jié)流提高性能 + 原生實(shí)現(xiàn)滾動視口展現(xiàn)

    摘要:引言前端開發(fā)中一個老生常談的問題就是當(dāng)用戶滾動時根據(jù)滾動的位置適當(dāng)觸發(fā)不同的函數(shù)動畫例如當(dāng)元素出現(xiàn)在視口時觸發(fā)該元素的改變通常的做法就是在上附加事件但是我們知道當(dāng)滾動條滾動時事件觸發(fā)的是很頻繁的且不由控制瀏覽器的事件隊(duì)列原生提供如圖添加事件 引言 前端開發(fā)中一個老生常談的問題就是當(dāng)用戶滾動時, 根據(jù)滾動的位置適當(dāng)觸發(fā)不同的函數(shù)/動畫, 例如當(dāng)元素出現(xiàn)在視口時觸發(fā)該元素的style改變....

    reclay 評論0 收藏0
  • 圖片懶加載的前世今生

    摘要:配置項(xiàng)配置項(xiàng)中的參數(shù)有以下三個所監(jiān)聽對象的具體祖先元素,默認(rèn)是計(jì)算交叉狀態(tài)時,將附加到祖先元素上,從而有效的擴(kuò)大或者縮小祖先元素判定區(qū)域設(shè)置一系列的閾值,當(dāng)交叉狀態(tài)達(dá)到閾值時,會觸發(fā)回調(diào)函數(shù)。 一、前言 ??通常情況下,HTML 中的圖片資源會自上而下依次加載,而部分圖片只有在用戶向下滾動頁面的場景下才能被看見,否則這部分圖片的流量就白白浪費(fèi)了。 ??所以,對于那些含有大量圖片資源的網(wǎng)...

    zhaot 評論0 收藏0
  • 移動端滾動研究

    摘要:還會有一個性能上的問題就是當(dāng)頁面的列表過長,元素過多時,在模擬滾動,下拉刷新這段時間內(nèi),頁面也會有卡頓現(xiàn)象,這里采取了一個優(yōu)化策略即列表較長時數(shù)量較多時,在觸發(fā)下拉刷新的時機(jī)時將頁面視窗之外的元素隱藏或者存放在里面。 移動web滾動問題 在移動端如果使用局部滾動,意思就是我們的滾動在一個固定寬高的div內(nèi)觸發(fā),將該div設(shè)置成overflow:scroll/auto;來形成div內(nèi)部的...

    ghnor 評論0 收藏0
  • 【前端詞典】5 種滾動吸頂實(shí)現(xiàn)方式的比較[性能升級版]

    摘要:用于獲得當(dāng)前元素到定位父級頂部的距離偏移值。后來在項(xiàng)目中總會遇到滾動吸頂?shù)男Ч枰獙?shí)現(xiàn),現(xiàn)在我將我知道的種滾動吸頂實(shí)現(xiàn)方式做詳細(xì)介紹。有兼容性問題,在微信瀏覽器某些版本中的值會為,于是乎也就有了第三種方案的兼容性寫法。修改版預(yù)覽 這篇文章是三天前寫就的,有大佬給我提了一些修改意見,我覺得這個意見確實(shí)中肯。所以就有了這個升級的修改版本。代碼同步更新到 GitHub 了。 修改內(nèi)容如下: 添加...

    happyfish 評論0 收藏0
  • 延遲加載(Lazyload)三種實(shí)現(xiàn)方式

    摘要:原文鏈接延遲加載也稱為惰性加載,即在長網(wǎng)頁中延遲加載圖像。傳入一個回調(diào)函數(shù),當(dāng)其觀察到元素集合出現(xiàn)時候,則會執(zhí)行該函數(shù)。管理的是一個數(shù)組,當(dāng)元素出現(xiàn)或消失的時候,數(shù)組添加或刪除該元素,并且執(zhí)行該回調(diào)函數(shù)。 原文鏈接 - https://zhuanlan.zhihu.com/p/25455672 延遲加載也稱為惰性加載,即在長網(wǎng)頁中延遲加載圖像。用戶滾動到它們之前,視口外的圖像不會加載。...

    niuxiaowei111 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<