如何使用ES6寫全屏滾動插件

這篇文章主要為大家展示了“如何使用ES6寫全屏滾動插件”,內(nèi)容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領(lǐng)大家一起研究并學習一下“如何使用ES6寫全屏滾動插件”這篇文章吧。

10年積累的成都做網(wǎng)站、成都網(wǎng)站設(shè)計、成都外貿(mào)網(wǎng)站建設(shè)經(jīng)驗,可以快速應(yīng)對客戶對網(wǎng)站的新想法和需求。提供各種問題對應(yīng)的解決方案。讓選擇我們的客戶得到更好、更有力的網(wǎng)絡(luò)服務(wù)。我雖然不認識你,你也不認識我。但先網(wǎng)站制作后付款的網(wǎng)站建設(shè)流程,更有欒川免費網(wǎng)站建設(shè)讓你可以放心的選擇與我們合作。

1)前面的話

現(xiàn)在已經(jīng)有很多全屏滾動插件了,比如著名的 fullPage ,那為什么還要自己造輪子呢?

現(xiàn)有輪子有以下問題:

  1. 首先,最大的問題是最流行的幾個插件都依賴 jQuery,這意味著在使用 React 或者 Vue 的項目中使用他們是一件十分蛋疼的事:我只需要一個全屏滾動功能,卻還需要把 jQuery 引入,有種殺雞使用宰牛刀的感覺;

  2. 其次,現(xiàn)有的很多全屏滾動插件功能往往都十分豐富,這在前幾年是優(yōu)勢,但現(xiàn)在(2018-5)可以看作是劣勢:前端開發(fā)已經(jīng)發(fā)生了很大變化,其中很重要的一個變化是 ES6 原生支持模塊化開發(fā),模塊化開發(fā)最大的特點是一個模塊最好只專注做好一件事,然后再拼成一個完整的系統(tǒng),從這個角度看,大而全的插件有悖模塊化開發(fā)的原則。

對比之下,通過原生語言造輪子有以下好處:

  1. 使用原生語言編寫的插件,自身不會受依賴的插件的使用場景而影響自身的使用(現(xiàn)在依賴 jQuery 的插件非常不適合開發(fā)單頁面應(yīng)用),所以使用上更加靈活;

  2. 搭配模塊化開發(fā),使用原生語言開發(fā)的插件可以只專注一個功能,所以代碼量可以很少;

  3. 最后,隨著 JS/CSS/HTML 的發(fā)展以及瀏覽器不斷迭代更新,現(xiàn)在使用原生語言編寫插件的開發(fā)成本越來越低,那為什么不呢?

2)實現(xiàn)原理及代碼架構(gòu)

2.1 實現(xiàn)原理

實現(xiàn)原理見下圖:容器及容器內(nèi)的頁面取當前可視區(qū)高度,同時容器的父級元素 overflow 屬性值設(shè)為 hidden ,通過更改容器 top 值實現(xiàn)全屏滾動效果。

如何使用ES6寫全屏滾動插件

2.2 代碼架構(gòu)

代碼編寫的思路是通過 class 定義全屏滾動類,使用時通過 new PureFullPage().init() 使用。

/**
 * 全屏滾動類
 */
class PureFullPage {
 // 構(gòu)造函數(shù)
 constructor() {}

 // 原型方法
 methods() {}

 // 初始化函數(shù)
 init() {}
}

3)html 結(jié)構(gòu)

鑒于上述實現(xiàn)原理,對于 html 的結(jié)構(gòu)有特定要求,如下:頁面容器為 #pureFullPageContainer ,所有的頁面為其直接子元素,這里為了方便,直接取 body 為其直接父元素。

<body>
 <div id="pureFullPageContainer">
  <div class="page"></div>
  <div class="page"></div>
  <div class="page"></div>
 </div>
</body>

4)css 設(shè)置

首先,容器及容器內(nèi)的頁面取當前可視區(qū)高度,為每次切換都顯示一個完整的頁面做準備;

第二,容器的父級元素(此處是 body ) overflow 屬性值定為 hidden ,這樣可以保證每次只會顯示一個頁面,其他頁面被隱藏。

經(jīng)過上述設(shè)置,對容器 top 值,每次更改一個可視區(qū)高度的距離,便實現(xiàn)了頁面間的切換,部分代碼如下:

body {
 /* body 為容器直接的父元素 */
 overflow: hidden;
}

#pureFullPage {
 /* 只有當 position 的值不是 static 時,top 值才有效 */
 position: relative;
 /* 設(shè)置初始值 */
 top: 0;
}
.page {
 /* 此處不能為 100vh,后面詳述 */
 /* 其父元素,也就是 #pureFullPage 的高度,通過 js 動態(tài)設(shè)置*/
 height: 100%;
}

Notice:

容器的 position 屬性值需要設(shè)置為 relative ,因為 top 只有在 position 屬性值不為 static 時才有效;

頁面高度需設(shè)置為當前可視區(qū)高度,但不能直接設(shè)置為 100vh ,因為 safari 手機瀏覽器把地址欄算進去計算 100vh ,但地址欄下面的不應(yīng)該算做“可視區(qū)”,畢竟實際上是“看不見”的區(qū)域。這會導致 100vh 對應(yīng)的像素值比 document.documentElement.clientHeight 獲取的像素值大。這樣在切換 top 值時就不是全屏切換了,實際上,這種情況下切換的高度小于頁面的高度。

解決 safari 手機瀏覽器可視區(qū)高度問題:既然通過 js 獲取的 document.documentElement.clientHeight 值是符合預(yù)期的可視區(qū)高度(不包括頂部地址欄和底部工具欄),那就 將該值通過 js 設(shè)置為容器的高度,同時,容器內(nèi)的頁面高度設(shè)置為 100% ,這樣就可以保證容器及頁面的高度和切換 top 值相同了,也就保證了全屏切換。

// 偽代碼
'#pureFullPage'.style.height = document.documentElement.clientHeight + 'px';

5)監(jiān)控滾動/滑動事件

這里的滾動/滑動事件包括鼠標滾動、觸摸板滑動以及手機屏幕上下滑動。

5.1 PC 端

PC 端主要解決的問題是獲取鼠標滾動或觸摸板滑動方向,觸摸板上下滑動和鼠標滾動綁定的是同一個事件:

  1. firefox 是 DOMMouseScroll 事件,對應(yīng)的滾輪信息(向前滾還是向后滾)存儲在 detail 屬性中,向前滾,這個屬性值是 3 的倍數(shù),反之,是 -3 的倍數(shù);

  2. firefox 之外的其他瀏覽器是 mousewheel 事件,對應(yīng)的滾輪信息存儲在 wheelDelta 屬性中,向前滾,這個屬性值是 -120 的倍數(shù),反之, 120 的倍數(shù)。

macOS 如此,windows 相反?

所以,可以通過 detail 或 wheelDelta 的值判斷鼠標的滾動方向,進而控制頁面是向上還是向下滾動。在這里我們只關(guān)心正負,不關(guān)心具體值的大小,為了便于使用,下面基于這兩個事件封裝了一個函數(shù):如果鼠標往前滾動,返回負數(shù),反之,返回正數(shù),代碼如下:

// 鼠標滾輪事件
getWheelDelta(event) {
 if (event.wheelDelta) {
  return event.wheelDelta;
 } else {
  // 兼容火狐
  return -event.detail;
 }
},

有了滾動事件,就可以據(jù)此編寫頁面向上或者向下滾動的回調(diào)函數(shù)了,如下:

// 鼠標滾動邏輯(全屏滾動關(guān)鍵邏輯)
scrollMouse(event) {
 let delta = utils.getWheelDelta(event);
 // delta < 0,鼠標往前滾動,頁面向下滾動
 if (delta < 0) {
  this.goDown();
 } else {
  this.goUp();
 }
}

goDown 、 goUp 是頁面滾動的邏輯代碼,需要特別說明的是必須 判斷滾動邊界,保證容器中顯示的始終是頁面內(nèi)容 :

  1. 上邊界容易確定,為 1 個頁面(也即可視區(qū))的高度,即如果容器當前的上外邊框距離整個頁面頂部的距離(這里此值正是容器的 offsetTop 值的絕對值,因為它父元素的 offsetTop 值都是 0 )大于等于當前可視區(qū)高度時,才允許向上滾動,不然,就證明上面已經(jīng)沒有頁面了,不允許繼續(xù)向上滾動;

  2. 下邊界為 n - 2 (n 表示全屏滾動的頁面數(shù)) 個可視區(qū)的高度,當容器的 offsetTop 值的絕對值小于等于 n - 2 個可視區(qū)的高度時,表示還可以向下滾動一個頁面。

具體代碼如下:

goUp() {
 // 只有頁面頂部還有頁面時頁面向上滾動
 if (-this.container.offsetTop >= this.viewHeight) {
  // 重新指定當前頁面距視圖頂部的距離 currentPosition,實現(xiàn)全屏滾動,
  // currentPosition 為負值,越大表示超出頂部部分越少
  this.currentPosition = this.currentPosition + this.viewHeight;

  this.turnPage(this.currentPosition);
 }
}
goDown() {
 // 只有頁面底部還有頁面時頁面向下滾動
 if (-this.container.offsetTop <= this.viewHeight * (this.pagesNum - 2)) {
  // 重新指定當前頁面距視圖頂部的距離 currentPosition,實現(xiàn)全屏滾動,
  // currentPosition 為負值,越小表示超出頂部部分越多
  this.currentPosition = this.currentPosition - this.viewHeight;

  this.turnPage(this.currentPosition);
 }
}

最后添加滾動事件:

// 鼠標滾輪監(jiān)聽,火狐鼠標滾動事件不同其他
if (navigator.userAgent.toLowerCase().indexOf('firefox') === -1) {
 document.addEventListener('mousewheel', scrollMouse);
} else {
 document.addEventListener('DOMMouseScroll', scrollMouse);
}

5.2 移動端

移動端需要判斷是向上還是向下滑動,可以結(jié)合 touchstart (手指開始接觸屏幕時觸發(fā)) 和 touchend (手指離開屏幕時觸發(fā)) 兩個事件實現(xiàn)判斷:分別獲取兩個事件開始觸發(fā)時的 pageY 值,如果觸摸結(jié)束時的 pageY 大于觸摸開始時的 pageY ,表示手指向下滑動,對應(yīng)頁面向上滾動,反之亦然。

此處我們需要觸摸事件跟蹤觸摸的屬性:

  1. touches :當前跟蹤的觸摸操作的 Touch 對象的數(shù)組,用于獲取觸摸開始時的 pageY 值;

  2. changeTouches :自上次觸摸以來發(fā)生了改變的 Touch 對象的數(shù)組,用于獲取觸摸觸摸結(jié)束時的 pageY 值。

相關(guān)代碼如下:

// 手指接觸屏幕
document.addEventListener('touchstart', event => {
 this.startY = event.touches[0].pageY;
});
//手指離開屏幕
document.addEventListener('touchend', event => {
 let endY = event.changedTouches[0].pageY;
 if (endY - this.startY < 0) {
  // 手指向上滑動,對應(yīng)頁面向下滾動
  this.goDown();
 } else {
  // 手指向下滑動,對應(yīng)頁面向上滾動
  this.goUp();
 }
});

為了避免下拉刷新,可以阻止 touchmove 事件的默認行為:

// 阻止 touchmove 下拉刷新
document.addEventListener('touchmove', event => {
 event.preventDefault();
});

6)PC 端滾動事件性能優(yōu)化

6.1 防抖函數(shù)和截流函數(shù)介紹

優(yōu)化主要從兩方便入手:

  1. 更改頁面大小時,通過防抖動(debounce)函數(shù)限制 resize 事件觸發(fā)頻率;

  2. 滾動/滑動事件觸發(fā)時,通過截流(throttle)函數(shù)限制滾動/滑動事件觸發(fā)頻率。

既然都是限制觸發(fā)頻率(都通過定時器實現(xiàn)),那這兩者有什么區(qū)別?

首先,防抖動函數(shù)工作時,如果在指定的延遲時間內(nèi),某個事件連續(xù)觸發(fā),那么綁定在這個事件上的回調(diào)函數(shù)永遠不會觸發(fā),只有在延遲時間內(nèi),這個事件沒再觸發(fā),對應(yīng)的回調(diào)函數(shù)才會執(zhí)行。防抖動函數(shù)非常適合改變窗口大小這一事件,這也符合 拖動到位以后再觸發(fā)事件,如果一直拖個不停,始終不觸發(fā)事件 這一直覺。

而截流函數(shù)是在延遲時間內(nèi),綁定到事件上的回調(diào)函數(shù)能且只能觸發(fā)一次,這和截流函數(shù)不同,即便是在延遲時間內(nèi)連續(xù)觸發(fā)事件,也不會阻止在延遲時間內(nèi)有一個回調(diào)函數(shù)執(zhí)行。并且截流函數(shù)允許我們指定回調(diào)函數(shù)是在延遲時間開始時還是結(jié)束時執(zhí)行。

鑒于截流函數(shù)的上述兩個特性,尤其適合優(yōu)化滾動/滑動事件:

  1. 可以限制頻率;

  2. 不會因為滾動/滑動事件太靈敏(在延遲時間內(nèi)不斷觸發(fā))導致注冊在事件上的回調(diào)函數(shù)無法執(zhí)行;

  3. 可以設(shè)置在延遲時間開始時觸發(fā)回調(diào)函數(shù),從而避免用戶感到操作之后的短暫延時。

這里不介紹防抖動函數(shù)和截流函數(shù)的實現(xiàn)原理,感興趣的可以看 Throttling and Debouncing in JavaScript ,下面是實現(xiàn)的代碼:

// 防抖動函數(shù),method 回調(diào)函數(shù),context 上下文,event 傳入的時間,delay 延遲函數(shù)
debounce(method, context, event, delay) {
 clearTimeout(method.tId);
 method.tId = setTimeout(() => {
  method.call(context, event);
 }, delay);
},

// 截流函數(shù),method 回調(diào)函數(shù),context 上下文,delay 延遲函數(shù),
// immediate 傳入 true 表示在 delay 開始時執(zhí)行回調(diào)函數(shù)
throttle(method, context, delay, immediate) {
 return function() {
  const args = arguments;
  const later = () => {
   method.tID = null;
   if (!immediate) {
    method.apply(context, args);
   }
  };
  const callNow = immediate && !method.tID;
  clearTimeout(method.tID);
  method.tID = setTimeout(later, delay);
  if (callNow) {
   method.apply(context, args);
  }
 };
},

《JavaScript 高級程序設(shè)計 - 第三版》 22.33.3 節(jié)中介紹的 throttle 函數(shù)和此處定義的不同,高程中定義的 throttle 函數(shù)對應(yīng)此處的 debounce 函數(shù),但網(wǎng)上大多數(shù)文章都和高程中的不同,比如 lodash 中定義的 debounce 。

6.2 改造 PC 端滾動事件

通過上述說明,我們已經(jīng)知道截流函數(shù)可以通過限定滾動事件觸發(fā)頻率提升性能,同時,設(shè)置在 延遲時間開始階段立即調(diào)用滾動事件的回調(diào)函數(shù) 并不會犧牲用戶體驗。

截流函數(shù)上文已經(jīng)定義好,使用起來就很簡單了:

// 設(shè)置截流函數(shù)
let handleMouseWheel = utils.throttle(this.scrollMouse, this, this.DELAY, true);

// 鼠標滾輪監(jiān)聽,火狐鼠標滾動事件不同其他
if (navigator.userAgent.toLowerCase().indexOf('firefox') === -1) {
 document.addEventListener('mousewheel', handleMouseWheel);
} else {
 document.addEventListener('DOMMouseScroll', handleMouseWheel);
}

上面這部分代碼是寫在 class 的 init 方法中,所以截流函數(shù)的上下文(context)傳入的是 this ,表示當前 class 實例。

7)其他

7.1 導航按鈕

為了簡化 html 結(jié)構(gòu),導航按鈕通過 js 創(chuàng)建。這里的難點在于 如何實現(xiàn)點擊不同按鈕實現(xiàn)對應(yīng)頁面的跳轉(zhuǎn)并更新對應(yīng)按鈕的樣式 。

解決的思路是:

  1. 頁面跳轉(zhuǎn):頁面?zhèn)€數(shù)和導航按鈕的個數(shù)一致,所以點擊第 i 個按鈕也就是跳轉(zhuǎn)到第 i 個頁面,而第 i 個頁面對應(yīng)的容器 top 值恰好是 -(i * this.viewHeight)

  2. 更改樣式:更改樣式即先刪除所有按鈕的選中樣式,然后給當前點擊的按鈕添加選中樣式。

// 創(chuàng)建右側(cè)點式導航
createNav() {
 const nav = document.createElement('div');
 nav.className = 'nav';
 this.container.appendChild(nav);
 // 有幾頁,顯示幾個點
 for (let i = 0; i < this.pagesNum; i++) {
  nav.innerHTML += '<p class="nav-dot"><span></span></p>';
 }
 const navDots = document.querySelectorAll('.nav-dot');
 this.navDots = Array.prototype.slice.call(navDots);
 // 添加初始樣式
 this.navDots[0].classList.add('active');
 // 添加點式導航點擊事件
 this.navDots.forEach((el, i) => {
  el.addEventListener('click', event => {
   // 頁面跳轉(zhuǎn)
   this.currentPosition = -(i * this.viewHeight);
   this.turnPage(this.currentPosition);
   // 更改樣式
   this.navDots.forEach(el => {
    utils.deleteClassName(el, 'active');
   });
   event.target.classList.add('active');
  });
 });
}

7.2 自定義參數(shù)

得當?shù)淖远x參數(shù)可以增加插件的靈活性。

參數(shù)通過構(gòu)造函數(shù)傳入,并通過 Object.assign() 進行參數(shù)合并:

constructor(options) {
 // 默認配置
 const defaultOptions = {
  isShowNav: true,
  delay: 150,
  definePages: () => {},
 };
 // 合并自定義配置
 this.options = Object.assign(defaultOptions, options);
}

7.3 窗口尺寸改變時更新數(shù)據(jù)

瀏覽器窗口尺寸改變的時候,需要重新獲取可視區(qū)、頁面元素高度,并重新確定容器當前的 top 值。

同時,為了避免不必要的性能開支,這里使用了防抖動函數(shù)。

// window resize 時重新獲取位置
getNewPosition() {
 this.viewHeight = document.documentElement.clientHeight;
 this.container.style.height = this.viewHeight + 'px';
 let activeNavIndex;
 this.navDots.forEach((e, i) => {
  if (e.classList.contains('active')) {
   activeNavIndex = i;
  }
 });
 this.currentPosition = -(activeNavIndex * this.viewHeight);
 this.turnPage(this.currentPosition);
}

handleWindowResize(event) {
 // 設(shè)置防抖動函數(shù)
 utils.debounce(this.getNewPosition, this, event, this.DELAY);
}

// 窗口尺寸變化時重置位置
window.addEventListener('resize', this.handleWindowResize.bind(this));

7.4 兼容性

這里的兼容性主要指兩個方面:一是不同瀏覽器對同一行為定義了不同 API,比如上文提到的獲取鼠標滾動信息的 API Firefox 和其他瀏覽器不一樣;第二點就是 ES6 新語法、新 API 的兼容處理。

對于 class、箭頭函數(shù)這類新語法的轉(zhuǎn)換,通過 babel 就可完成,鑒于本插件代碼量很小,都處于可控的狀態(tài),并沒有引入 babel 提供的 polyfill 方案,因為新 API 只有 Object.assign() 需要做兼容處理,單獨寫個 polyfill 就好,如下:

// polyfill Object.assign
polyfill() {
 if (typeof Object.assign != 'function') {
  Object.defineProperty(Object, 'assign', {
   value: function assign(target, varArgs) {
    if (target == null) {
     throw new TypeError('Cannot convert undefined or null to object');
    }
    let to = Object(target);
    for (let index = 1; index < arguments.length; index++) {
     let nextSource = arguments[index];
     if (nextSource != null) {
      for (let nextKey in nextSource) {
       if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
        to[nextKey] = nextSource[nextKey];
       }
      }
     }
    }
    return to;
   },
   writable: true,
   configurable: true,
  });
 }
},

引用自: MDN-Object.assign()

因為本插件只兼容到 IE10,所以不打算對事件做兼容處理,畢竟IE9 都支持 addEventListener 了。

7.5 通過惰性載入進一步優(yōu)化性能

在 5.1 中寫的 getWheelDelta 函數(shù)每次執(zhí)行都需要檢測是否支持 event.wheelDelta ,實際上,瀏覽器只需在第一次加載時檢測,如果支持,接下來都會支持,再做檢測是沒必要的。

并且這個檢測在頁面的生命周期中會執(zhí)行很多次,這種情況下可以通過 惰性載入 技巧進行優(yōu)化,如下:

getWheelDelta(event) {
 if (event.wheelDelta) {
  // 第一次調(diào)用之后惰性載入,無需再做檢測
  this.getWheelDelta = event => event.wheelDelta;
  // 第一次調(diào)用使用
  return event.wheelDelta;
 } else {
  // 兼容火狐
  this.getWheelDelta = event => -event.detail;
  return -event.detail;
 }
},

以上是“如何使用ES6寫全屏滾動插件”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學習更多知識,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道!

文章標題:如何使用ES6寫全屏滾動插件
URL地址:http://muchs.cn/article6/pdjsig.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站導航、企業(yè)網(wǎng)站制作、搜索引擎優(yōu)化、網(wǎng)站策劃網(wǎng)站內(nèi)鏈、虛擬主機

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)

網(wǎng)站托管運營