本身動手實現頁面滾動動畫效果

之前的我的網站是用 VuePress 的 beta 版搭建的,當時還挺新鮮,放到如今感受已經爛大街了,之後面試也撈不到好處,決定從新寫一個。
想在本身的網站上實現一個劫持鼠標滾輪,進行翻頁的效果,記錄一下踩坑之路。
先來看看模仿目標 明日方舟官網 ,記得小米和一加的手機介紹頁也用過這種滾動css

最終效果✌️
最終效果html

靜態頁面佈局

要實現 banner 圖片不跟隨內容滾動,只須要一個很是簡單的 css 屬性 background-attachment 就能實現。
固然也能夠直接在 img 標籤上經過 posistion 屬性來固定,可是須要設置其餘如 marginz-index 各類屬性,仍是不太方便。ios

<!-- demo -->
<div class="banner"></div>
<div style="height: 100vh;"></div>

<style lang="scss"> .banner { height: 80vh; background-image: linear-gradient(#000000, #ffffff); /* 保持背景圖片不跟隨內容滾動 */ background-attachment: fixed; } </style>
複製代碼

攔截鼠標滾輪默認事件

固定完背景,接下來就是攔截默認的鼠標滾輪事件,實現本身的翻頁效果,原本是很是簡單的 addEventListenergit

window.addEventListener('mousewheel', scrollFn)

function scrollFn (e) {
  e.preventDefault()
  if (e.deltaY > 0) {
    // 向下滾動...
  } else {
    // 向上滾動...
  }
}
複製代碼

可是在瀏覽器上報了這樣的錯誤,並且默認的鼠標滾輪事件仍然能觸發。。web

報錯

點進去翻了一下 chrome 的 feature ,發現 Event 多出來了一個 passive 屬性,並且 WheelEvent 的 passive 默認爲 true,
又翻了翻 addEventListenerMDN ,發現面試

mdn截圖

多了一個 options 的選項。。
由於addEventListeneruseCapture屬性用的人太少,15年末已經被規範爲可選屬性,而且可以傳入對象。passive的做用就是讓 listener 禁止調用preventDefault()
修改成 window.addEventListener('mousewheel', this.scrollFn, { passive: false }) 最終成功實現滾輪默認事件chrome

第一次嘗試:scroll-behavior: smooth

使用 css 屬性 scroll-behavior: smooth 是最方便最簡單的,效果也比第二次要好不少,可是 mac 上 safari 不支持這一特性,ios 上的 safari 卻又支持😓npm

function scrollFn (e: WheelEvent) {
  e.preventDefault()
  // 獲取視窗高度
  const windowHeight = window.innerHeight || document.body.clientHeight
  // 計算 banner 高度,css 屬性爲80vh
  const scrollHeight = windowHeight * 0.8
  window.scrollTo(0, e.deltaY > 0 ? scrollHeight : 0)
}
複製代碼

css
在搜索 safari 上的相似屬性時,發現了更方便的方法 window.scrollTo({ top: 1000, behavior: "smooth" }),嘗試了一下,mac 的 safari 也不支持。。canvas

第二次嘗試:requestAnimationFrame

爲了可以兼容 safari,這個翻頁效果仍是得本身手寫。經過使用 requestAnimationFrame 方法來進行滾動動畫操做,在瀏覽器重繪以前調用動畫函數。通常根據顯示器的幀數來進行調用,並且可以在頁面 blur 時中止動畫,節省資源。api

function scrollFn (e: WheelEvent) {
  e.preventDefault()
  let start = 0
  // 動畫函數,須要閉包訪問 start
  const step = (unix: number) => {
    if (!start) {
      start = unix
    }
    const duration = unix - start
    // 獲取視窗高度
    const windowHeight = window.innerHeight || document.body.clientHeight
    // 計算 banner 高度,css 屬性爲80vh
    const scrollHeight = windowHeight * 0.8
    // 在當前時間應該滾動的距離
    const nowY = scrollHeight / 1000 * duration
    window.scrollTo(0, e.deltaY > 0 ? nowY : scrollHeight - nowY)
    // 1000ms
    if (duration < 1000) {
      requestAnimationFrame(step)
    }
  }
  requestAnimationFrame(step)
}
複製代碼

敲完代碼一看,效果有點拉胯
這直上直下的線性效果,太呆了

requestAnimationFrame

第三次嘗試:添加緩動函數

在搜索引擎裏一頓查,可大多都是數學題和 canvas,還翻了好多關於貝塞爾曲線的解析,難度仍是有點高的嗷(方向錯了😅
在我覺得只能放棄本身手寫,藉助 npm 的力量時,最後在一堆數學題裏翻出來 這個網站,救我🐶命

現實生活中,物體並非忽然啓動或者中止,固然也不可能一直保持勻速移動。就像咱們打開抽屜的過程那樣,剛開始拉的那一下動做很快,可是當抽屜被拉出來以後咱們會不自覺的放慢動做。掉落在地板上的東西,一開始降低的速度很快,後來就會在地板上來回反彈直到中止。

經過使用網站裏的緩動函數,可以在必定程度上模擬真實環境中的物體運動效果,來給動畫添加真是的效果

function scrollFn (e: WheelEvent) {
  e.preventDefault()
  // 正在滾動中,或者到最後一頁還向下滾,或者第一頁還向上滾
  if (this.debounce || (e.deltaY > 0 && this.index >= 1) || (e.deltaY < 0 && this.index === 0)) {
    return
  }
  let start = 0
  // 動畫函數,須要閉包訪問 start 就沒有分離出來
  const step = (unix: number) => {
    if (!start) {
      start = unix
    }
    const duration = unix - start
    // 獲取視窗高度
    const windowHeight = window.innerHeight || document.body.clientHeight
    // 計算 banner 高度,css 屬性爲80vh
    const scrollHeight = windowHeight * 0.8
    // 1000ms內,duration / 1000就會在0-1之間增長,返回值也是,再乘上最終的高度
    const y = this.easeInOutCubic(duration / 1000) * scrollHeight
    window.scrollTo(0, e.deltaY > 0 ? y : scrollHeight - y)
    if (duration <= 1001) {
      requestAnimationFrame(step)
      this.debounce = true
    } else {
      this.debounce = false
      e.deltaY > 0 ? this.index++ : this.index--
      console.log(this.index)
    }
  }
  requestAnimationFrame(step)
}

// 緩動函數,x的範圍爲0-1,返回的 number 也是0-1 https://easings.net#easeInOutCubic
function easeInOutCubic (x: number): number {
  return x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2;
}
複製代碼

緩動函數 easeInOutCubic 會根據傳入的 x 的範圍 0-1 輸出相應的快慢的從 0% 到 100%,就像 easings.net#easeInOutCubic 裏的例子同樣,對 requestAnimationFrame 的速度進行控制

最終效果

效果對比

從👎到👍

requestAnimationFrame
requestAnimationFrame

scroll-behavior: smooth
css

requestAnimationFrame with easings
最終效果

總結

項目地址

  • WheelEvent 默認的 passive 爲 true,不容許 preventDefault
  • scroll-behavior: smooth 最方便,自帶緩動函數,可是 safari 不支持,完成動畫的時間也不可控
  • requestAnimationFrame 是線性動畫,動畫範圍越大會越顯得呆板,須要緩動函數控制速率,可是能夠本身控制完成動畫的時間
  • 懶,就沒有把滾動中防抖、根據頁面位置判斷可否繼續滾動這些複雜的判斷放進文章裏
相關文章
相關標籤/搜索