欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解無限滾動插件vue-infinite-scroll源碼解析

 更新時間:2019年05月12日 17:07:19   作者:十年一刻  
這篇文章主要介紹了詳解無限滾動插件vue-infinite-scroll源碼解析,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧

最近在項目中遇到一個需求,有一個列表需要滾動加載,類似于微博的無限滾動。當(dāng)時第一反應(yīng)時監(jiān)聽滾動事件,在判斷滾動到達(dá)底部時加載下一頁,同時心里也清楚,監(jiān)聽滾動事件需要做好截流。順手搜索了下發(fā)現(xiàn)有一個現(xiàn)成的插件vue-infinite-scroll ,用法也很簡單,于是乎就用了起來。 需求上線后,對它的實現(xiàn)挺好奇的,于是研究了一番源碼,這篇文章就是源碼解析筆記。

插件使用方法

這是一個 vue 的指令,按照 github 倉庫上的介紹,用法挺簡單的,例如:

<div class="app" v-infinite-scroll="loadMore" infinite-scroll-disabled="busy" infinite-scroll-distance="10">
 <div class="content"></div>
 <div class="loading" v-show="busy">loading.....</div>
</div>
.app {
 height: 1000px;
 border: 1px solid red;
 width: 600px;
 margin: 0 auto;
 overflow: auto;
}
.content {
 height: 1300px;
 background-color: #ccc;
 width: 80%;
 margin: 0 auto;
}
.loading {
 font-weight: bold;
 font-size: 20px;
 color: red;
 text-align: center;
}
var app = document.querySelector('.app');
new Vue({
 el: app,
 directives: {
  InfiniteScroll,
 },
 data: function() {
  return { busy: false };
 },
 methods: {
  loadMore: function() {
   var self = this;
   self.busy = true;
   console.log('loading... ' + new Date());
   setTimeout(function() {
    var target = document.querySelector('.content');
    var height = target.clientHeight;
    target.style.height = height + 300 + 'px';
    console.log('end... ' + new Date());
    self.busy = false;
   }, 1000);
  },
 },
});

這里的指令宿主元素自身設(shè)置了 overflow:auto ,內(nèi)部元素用來支撐滾動,當(dāng)滾動到底部時,增加內(nèi)部元素的高度從而模擬了無限滾動。效果如下:

另外可以將父元素設(shè)置為滾動,當(dāng)自身滾動到父元素底部時,增加自身的高度,模擬拉取下一頁數(shù)據(jù)的操作。 例如:

<div class="app">
 <div class="content" v-infinite-scroll="loadMore" infinite-scroll-disabled="busy" infinite-scroll-distance="10"></div>
 <div class="loading" v-show="busy">loading.....</div>
</div>

達(dá)到的效果和上面完全相同。

源碼解析

接下來就是看看內(nèi)部怎么實現(xiàn)的。照例從入口開始看起。因為這個插件就是一個 vue 的指令,所以入口還是挺簡單的:

指令入口

export default {
 bind(el, binding, vnode) {
  el[ctx] = {
   el,
   vm: vnode.context,
   expression: binding.value, // 滾動到底部時需要的監(jiān)聽函數(shù),通常用于加載下一頁數(shù)據(jù)
  };
  const args = arguments;
  // 監(jiān)聽宿主元素所在組件的mounted事件
  el[ctx].vm.$on('hook:mounted', function() {
   el[ctx].vm.$nextTick(function() {
    // 判斷元素是否已經(jīng)在頁面上
    if (isAttached(el)) {
     // 獲取各項指令相關(guān)屬性,執(zhí)行各種事件綁定
     doBind.call(el[ctx], args);
    }

    el[ctx].bindTryCount = 0;

    // 間隔50ms輪訓(xùn)10次,判斷元素是否已經(jīng)在頁面上
    var tryBind = function() {
     if (el[ctx].bindTryCount > 10) return; //eslint-disable-line
     el[ctx].bindTryCount++;
     if (isAttached(el)) {
      doBind.call(el[ctx], args);
     } else {
      setTimeout(tryBind, 50);
     }
    };

    tryBind();
   });
  });
 },

 unbind(el) {
  // 事件解綁
  if (el && el[ctx] && el[ctx].scrollEventTarget) el[ctx].scrollEventTarget.removeEventListener('scroll', el[ctx].scrollListener);
 },
};

核心就是在宿主元素渲染后,執(zhí)行 doBind 方法,我們猜測會在 doBind 綁定滾動父元素的 scroll 事件。

isAttached 方法用于判斷一個元素是否已渲染在頁面上,判斷方法是查看是否有組件元素的標(biāo)簽名為 HTML

// 判斷元素是否已經(jīng)在頁面上
var isAttached = function(element) {
 var currentNode = element.parentNode;
 while (currentNode) {
  if (currentNode.tagName === 'HTML') {
   return true;
  }
  // 11 表示DomFragment
  if (currentNode.nodeType === 11) {
   return false;
  }
  currentNode = currentNode.parentNode;
 }
 return false;
};

參數(shù)解析與事件綁定

現(xiàn)在看看 doBind 方法,邏輯比較多,不過都不難。

var doBind = function() {
 if (this.binded) return; // 只綁定一次
 this.binded = true;

 var directive = this;
 var element = directive.el;

 // throttleDelayExpr: 截流間隔。 設(shè)置在元素的屬性上
 var throttleDelayExpr = element.getAttribute('infinite-scroll-throttle-delay');
 var throttleDelay = 200;
 if (throttleDelayExpr) {
  // 優(yōu)先嘗試組件上的throttleDelayExpr屬性值, 如 <div infinite-scroll-throttle-delay="myDelay"></div>
  throttleDelay = Number(directive.vm[throttleDelayExpr] || throttleDelayExpr);
  if (isNaN(throttleDelay) || throttleDelay < 0) {
   throttleDelay = 200;
  }
 }
 directive.throttleDelay = throttleDelay;

 // 監(jiān)聽滾動父元素的scroll時間,監(jiān)聽函數(shù)設(shè)置了函數(shù)截流
 directive.scrollEventTarget = getScrollEventTarget(element); // 設(shè)置了滾動的父元素
 directive.scrollListener = throttle(doCheck.bind(directive), directive.throttleDelay);
 directive.scrollEventTarget.addEventListener('scroll', directive.scrollListener);

 this.vm.$on('hook:beforeDestroy', function() {
  directive.scrollEventTarget.removeEventListener('scroll', directive.scrollListener);
 });

 // infinite-scroll-disabled: 是否禁用無限滾動
 // 可以為表達(dá)式
 var disabledExpr = element.getAttribute('infinite-scroll-disabled');
 var disabled = false;

 if (disabledExpr) {
  this.vm.$watch(disabledExpr, function(value) {
   directive.disabled = value;
   // 當(dāng)disable為false時,重啟check
   if (!value && directive.immediateCheck) {
    doCheck.call(directive);
   }
  });
  disabled = Boolean(directive.vm[disabledExpr]);
 }
 directive.disabled = disabled;

 // 宿主元素到滾動父元素底部的距離閾值,小于這個值時,觸發(fā)listen-for-event監(jiān)聽函數(shù)
 var distanceExpr = element.getAttribute('infinite-scroll-distance');
 var distance = 0;
 if (distanceExpr) {
  distance = Number(directive.vm[distanceExpr] || distanceExpr);
  if (isNaN(distance)) {
   distance = 0;
  }
 }
 directive.distance = distance;

 // immediate-check:是否在bind后立即檢查一遍,也會在disable失效時立即觸發(fā)檢查
 var immediateCheckExpr = element.getAttribute('infinite-scroll-immediate-check');
 var immediateCheck = true;
 if (immediateCheckExpr) {
  immediateCheck = Boolean(directive.vm[immediateCheckExpr]);
 }
 directive.immediateCheck = immediateCheck;

 if (immediateCheck) {
  doCheck.call(directive);
 }

 // 當(dāng)組件上設(shè)置的此事件觸發(fā)時,執(zhí)行一次檢查
 var eventName = element.getAttribute('infinite-scroll-listen-for-event');
 if (eventName) {
  directive.vm.$on(eventName, function() {
   doCheck.call(directive);
  });
 }
};

整個看下來,核心就是利用各種參數(shù)控制 doCheck 的調(diào)用,包括時間間隔、 disabled 、距離閾值、 immediate-check 、組件事件。

doCheck 因為會非常頻繁的調(diào)用,所以用 throttle 進(jìn)行了截流,具體邏輯這里不再贅述。

getScrollEventTarget 查找滾動父元素時,有一個細(xì)節(jié)就是會從自身開始查找,這也就是我們上面的 demo 中可以將指令宿主元素賦值給滾動元素自身的原因:

// 從自身開始,尋找設(shè)置了滾動的父元素。 overflow-y 為scroll或auto
var getScrollEventTarget = function(element) {
 var currentNode = element;
 // bugfix, see http://w3help.org/zh-cn/causes/SD9013 and http://stackoverflow.com/questions/17016740/onscroll-function-is-not-working-for-chrome
 // nodeType 1表示元素節(jié)點
 while (currentNode && currentNode.tagName !== 'HTML' && currentNode.tagName !== 'BODY' && currentNode.nodeType === 1) {
  var overflowY = getComputedStyle(currentNode).overflowY;
  if (overflowY === 'scroll' || overflowY === 'auto') {
   return currentNode;
  }
  currentNode = currentNode.parentNode;
 }
 return window;
};

doCheck

這個函數(shù)用于判斷是否已經(jīng)滾動到底部,可以說是整個插件的核心邏輯。由于滾動的元素可以是自身,也可以是某個父元素,所以判斷會分成兩個分支。

var doCheck = function(force) {
 var scrollEventTarget = this.scrollEventTarget; // 滾動父元素
 var element = this.el;
 var distance = this.distance; // 距離閾值

 if (force !== true && this.disabled) return;
 var viewportScrollTop = getScrollTop(scrollEventTarget); // 被隱藏在內(nèi)容區(qū)上方的像素數(shù)
 // viewportBottom: 元素底部與文檔坐標(biāo)頂部的距離; visibleHeight:元素不帶邊框的高度
 var viewportBottom = viewportScrollTop + getVisibleHeight(scrollEventTarget);

 var shouldTrigger = false;

 // 滾動元素就是自身
 if (scrollEventTarget === element) {
  // scrollHeight - 在沒有滾動條的情況下,元素內(nèi)容的總高度,是元素的內(nèi)容區(qū)加上內(nèi)邊距再加上任何溢出內(nèi)容的尺寸。
  // shouldTrigger為true表示已經(jīng)滾動到元素的足夠底部了。
  // 參考https://hellogithub2014.github.io/2017/10/19/dom-element-size-summary/
  shouldTrigger = scrollEventTarget.scrollHeight - viewportBottom <= distance;
 } else {
  // 當(dāng)前元素與不是父元素,此時通常意味著當(dāng)前元素的高度比滾動父元素要高,這樣父元素才會出現(xiàn)滾動

  // getElementTop(element) - getElementTop(scrollEventTarget) 當(dāng)前元素頂部與滾動父元素頂部的距離
  // offsetHeight元素帶邊框的高度
  // elementBottom: 元素底部與文檔坐標(biāo)頂部的距離
  var elementBottom = getElementTop(element) - getElementTop(scrollEventTarget) + element.offsetHeight + viewportScrollTop;

  shouldTrigger = viewportBottom + distance >= elementBottom;
 }

 if (shouldTrigger && this.expression) {
  this.expression(); // 觸發(fā)綁定的無限滾動函數(shù),通常是獲取下一頁數(shù)據(jù)。 之后scrollEventTarget.scrollHeight會變大
 }
};

這里涉及到了多種尺寸值,包括 scrollTop 、 offsetTop 、 clientHeightscrollHeight 等等,如果不清楚的話整個函數(shù)的邏輯就很難看懂,關(guān)于它們的具體意義可以參考我之前寫的一篇博客。

這里我用兩幅圖來輔助理解上面的邏輯,相信會好懂很多。

滾動元素是自身

 如下,我們的目標(biāo)是判斷元素是否已滾動到底部的距離閾值之內(nèi),很容易可以看出來,距離內(nèi)容底部的距離公式為:

const { scrollHeight, clientHeight, scrollTop } = scrollEventTarget;
const currentDistance = scrollHeight - clientHeight - scrollTop;

這也就是函數(shù) if 分支的邏輯,當(dāng) currentDistance 小于 distance 時,我們就可以加載下一頁數(shù)據(jù)了。

父級元素設(shè)置滾動

此時就沒有 scrollTop 屬性可以操作了,但是元素的高度仍然可以用上面的屬性:滾動父元素的高度可以用 scrollEventTarget.clientHeight ,子元素內(nèi)容高度可以用 element.offsetHeight ,剩下的就是計算 topGap 了。

我們知道 DOM 的坐標(biāo)有兩種:文檔坐標(biāo)、視口坐標(biāo),計算 topGap 只要始終在其中一個坐標(biāo)系計算就可以了,這里我們采用視口坐標(biāo)。 ele.getBoundingClientRect().top 可以知道一個元素距離視口頂部的距離,那么 topGap 的計算公式就是:

const topGap = scrollEventTarget.getBoundingClientRect().top - element.getBoundingClientRect().top;

綜上,子元素底部與父元素底部的距離公式就是:

const currentDistance =
 element.offsetHeight - scrollEventTarget.clientHeight - (scrollEventTarget.getBoundingClientRect().top - element.getBoundingClientRect().top);

這也就是函數(shù)的 else 分支邏輯。

以上就是 doCheck 的核心檢測邏輯了,同時針對 scrollEventTargetdocument 時做了一些特殊處理,留給大家自己去看。

以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。

相關(guān)文章

  • vue實現(xiàn)進(jìn)入某個頁面后替換地址欄路徑的操作方法

    vue實現(xiàn)進(jìn)入某個頁面后替換地址欄路徑的操作方法

    vue頁面在實際開發(fā)中,經(jīng)常會遇到改變url參數(shù),重新加載頁面數(shù)據(jù)的需求,但是只改變頁面url并不會觸發(fā)組件的生命周期,這就需要用其他方法來實現(xiàn)了,本文重點介紹vue實現(xiàn)進(jìn)入某個頁面后替換地址欄路徑的操作方法,感興趣的朋友跟隨小編一起看看吧
    2024-04-04
  • vue移動UI框架滑動加載數(shù)據(jù)的方法

    vue移動UI框架滑動加載數(shù)據(jù)的方法

    這篇文章主要介紹了vue移動UI框架滑動加載的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-03-03
  • vue2實現(xiàn)封裝動態(tài)表單組件

    vue2實現(xiàn)封裝動態(tài)表單組件

    這篇文章主要介紹了vue2實現(xiàn)封裝動態(tài)表單組件,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下
    2022-08-08
  • vue如何自定義InputNumber計數(shù)器組件

    vue如何自定義InputNumber計數(shù)器組件

    這篇文章主要介紹了vue如何自定義InputNumber計數(shù)器組件問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-10-10
  • vue2如何實現(xiàn)vue3的teleport

    vue2如何實現(xiàn)vue3的teleport

    這篇文章主要介紹了vue2如何實現(xiàn)vue3的teleport,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-04-04
  • vue實現(xiàn)多級菜單效果

    vue實現(xiàn)多級菜單效果

    這篇文章主要為大家詳細(xì)介紹了vue實現(xiàn)多級菜單效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-07-07
  • vue將html頁面生成高清晰pdf文件的方法

    vue將html頁面生成高清晰pdf文件的方法

    最近工作中遇到個需求,需要實現(xiàn)個可視化圖表頁的PDF文件導(dǎo)出,所以下面這篇文章主要給大家介紹了關(guān)于利用vue如何將html頁面生成高清晰pdf文件的相關(guān)資料,需要的朋友可以參考下
    2022-03-03
  • vue里面v-bind和Props 利用props綁定動態(tài)數(shù)據(jù)的方法

    vue里面v-bind和Props 利用props綁定動態(tài)數(shù)據(jù)的方法

    今天小編就為大家分享一篇vue里面v-bind和Props 利用props綁定動態(tài)數(shù)據(jù)的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-08-08
  • axios的interceptors多次執(zhí)行問題解決

    axios的interceptors多次執(zhí)行問題解決

    這篇文章主要為大家介紹了axios中interceptors多次執(zhí)行問題解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-06-06
  • vue基礎(chǔ)ESLint?Prettier配置教程詳解

    vue基礎(chǔ)ESLint?Prettier配置教程詳解

    這篇文章主要介紹了vue基礎(chǔ)ESLint?Prettier配置教程詳解,本文使用VsCode?+?Vue?+?ESLint?+?Prettier?實現(xiàn)代碼格式規(guī)范?+?保存自動修復(fù)代碼js+vue
    2022-07-07

最新評論