亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

用ES6寫全屏滾動插件的示例代碼

 更新時間:2018年05月02日 14:04:02   作者:劉小光  
本篇文章主要介紹了用ES6寫全屏滾動插件的示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧

這篇文章將介紹如何使用原生 JS (主要使用 ES6 語法)實現(xiàn)全屏滾動插件,兼容 IE 10+、手機(jī)觸屏,Mac 觸摸板優(yōu)化,支持自定義頁面動畫,壓縮后 gzip 文件只有 2k。完整源碼在這 pure_full_page ,點這查看demo。

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)的頁面取當(dāng)前可視區(qū)高度,同時容器的父級元素 overflow 屬性值設(shè)為 hidden ,通過更改容器 top 值實現(xiàn)全屏滾動效果。

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)的頁面取當(dāng)前可視區(qū)高度,為每次切換都顯示一個完整的頁面做準(zhǔn)備;

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

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

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

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

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

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

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

這里的滾動/滑動事件包括鼠標(biāo)滾動、觸摸板滑動以及手機(jī)屏幕上下滑動。

5.1 PC 端

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

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

macOS 如此,windows 相反?

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

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

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

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

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

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

具體代碼如下:

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

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

  this.turnPage(this.currentPosition);
 }
}

最后添加滾動事件:

// 鼠標(biāo)滾輪監(jiān)聽,火狐鼠標(biāo)滾動事件不同其他
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 :當(dāng)前跟蹤的觸摸操作的 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 事件的默認(rèn)行為:

// 阻止 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ù)永遠(yuǎn)不會觸發(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ā))導(dǎo)致注冊在事件上的回調(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);

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

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

7)其他

7.1 導(dǎo)航按鈕

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

解決的思路是:

  1. 頁面跳轉(zhuǎn):頁面?zhèn)€數(shù)和導(dǎo)航按鈕的個數(shù)一致,所以點擊第 i 個按鈕也就是跳轉(zhuǎn)到第 i 個頁面,而第 i 個頁面對應(yīng)的容器 top 值恰好是 -(i * this.viewHeight)
  2. 更改樣式:更改樣式即先刪除所有按鈕的選中樣式,然后給當(dāng)前點擊的按鈕添加選中樣式。
// 創(chuàng)建右側(cè)點式導(dǎo)航
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');
 // 添加點式導(dǎo)航點擊事件
 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ù)

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

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

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

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

瀏覽器窗口尺寸改變的時候,需要重新獲取可視區(qū)、頁面元素高度,并重新確定容器當(dāng)前的 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,比如上文提到的獲取鼠標(biāo)滾動信息的 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 通過惰性載入進(jìn)一步優(yōu)化性能

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

并且這個檢測在頁面的生命周期中會執(zhí)行很多次,這種情況下可以通過 惰性載入 技巧進(jìn)行優(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;
 }
},

參考資料

純 JS 全屏滾動 / 整屏翻頁

Throttling and Debouncing in JavaScript

Debouncing and Throttling Explained Through Examples

JavaScript Debounce Function

Viewport height is taller than the visible part of the document in some mobile browsers

MDN-Object.assign()

Babel 編譯出來還是 ES 6?難道只能上 polyfill?- Henry 的回答

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

相關(guān)文章

最新評論